Beispiel #1
0
        public void DumpPartToBuffer(StringBuilder buffer, String prefix, List <PartSim> allParts = null)
        {
            buffer.Append(prefix);
            buffer.Append(name);
            buffer.AppendFormat(":[id = {0:d}, decouple = {1:d}, invstage = {2:d}", partId, decoupledInStage, inverseStage);

            buffer.AppendFormat(", vesselName = '{0}'", vesselName);
            buffer.AppendFormat(", vesselType = {0}", SimManager.GetVesselTypeString(vesselType));
            buffer.AppendFormat(", initialVesselName = '{0}'", initialVesselName);

            buffer.AppendFormat(", fuelCF = {0}", fuelCrossFeed);
            buffer.AppendFormat(", noCFNKey = '{0}'", noCrossFeedNodeKey);

            buffer.AppendFormat(", isSep = {0}", isSepratron);

            foreach (int type in resources.Types)
            {
                buffer.AppendFormat(", {0} = {1:g6}", ResourceContainer.GetResourceName(type), resources[type]);
            }

            if (attachNodes.Count > 0)
            {
                buffer.Append(", attached = <");
                attachNodes[0].DumpToBuffer(buffer);
                for (int i = 1; i < attachNodes.Count; i++)
                {
                    buffer.Append(", ");
                    attachNodes[i].DumpToBuffer(buffer);
                }
                buffer.Append(">");
            }

            // Add more info here

            buffer.Append("]\n");

            if (allParts != null)
            {
                String newPrefix = prefix + " ";
                foreach (PartSim partSim in allParts)
                {
                    if (partSim.parent == this)
                    {
                        partSim.DumpPartToBuffer(buffer, newPrefix, allParts);
                    }
                }
            }
        }
Beispiel #2
0
        public void SetResourceDrainRates(List <PartSim> partSims)
        {
            foreach (int type in resourceConsumptions.Types)
            {
                double amount = resourceConsumptions[type];

                switch (ResourceContainer.GetResourceFlowMode(type))
                {
                case ResourceFlowMode.NO_FLOW:
                    this.resourceDrains.Add(type, amount);
                    break;

                case ResourceFlowMode.ALL_VESSEL:
                    SetResourceDrainRateAllVessel(type, amount, partSims);
                    break;

                case ResourceFlowMode.STACK_PRIORITY_SEARCH:
                    SetResourceDrainRateRecursive(type, amount);
                    break;
                }
            }
        }
Beispiel #3
0
        public bool CanDrawNeededResources(List <PartSim> partSims)
        {
            foreach (int type in resourceConsumptions.Types)
            {
                switch (ResourceContainer.GetResourceFlowMode(type))
                {
                case ResourceFlowMode.NO_FLOW:
                    if (resources[type] < 1f)
                    {
                        return(false);
                    }
                    break;

                case ResourceFlowMode.ALL_VESSEL:
                    foreach (PartSim partSim in partSims)
                    {
                        if (partSim.resources[type] > 1f)
                        {
                            return(true);
                        }
                    }
                    return(false);

                case ResourceFlowMode.STACK_PRIORITY_SEARCH:
                    if (!CanSupplyResourceRecursive(type))
                    {
                        return(false);
                    }
                    break;

                default:
                    return(false);
                }
            }
            return(true);
        }
Beispiel #4
0
        // 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)
        {
            if (log != null)
            {
                log.buf.AppendLine(indent + "GetSourceSet(" + ResourceContainer.GetResourceName(type) + ") for " + name + ":" + partId);
                indent += "  ";
            }

            HashSet <PartSim> allSources  = new HashSet <PartSim>();
            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.
            if (visited.Contains(this))
            {
                if (log != null)
                {
                    log.buf.AppendLine(indent + "Returning empty set, already visited (" + name + ":" + partId + ")");
                }

                return(allSources);
            }

            //if (log != null)
            //    log.buf.AppendLine(indent + "Adding this to visited");

            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.
            //MonoBehaviour.print("foreach fuel line");

            foreach (PartSim partSim in fuelTargets)
            {
                if (visited.Contains(partSim))
                {
                    //if (log != null)
                    //    log.buf.AppendLine(indent + "Fuel target already visited, skipping (" + partSim.name + ":" + partSim.partId + ")");
                }
                else
                {
                    //if (log != null)
                    //    log.buf.AppendLine(indent + "Adding fuel target as source (" + partSim.name + ":" + partSim.partId + ")");

                    partSources = partSim.GetSourceSet(type, allParts, visited, log, indent);
                    if (partSources.Count > 0)
                    {
                        allSources.UnionWith(partSources);
                        partSources.Clear();
                    }
                }
            }

            if (allSources.Count > 0)
            {
                if (log != null)
                {
                    log.buf.AppendLine(indent + "Returning " + allSources.Count + " fuel target sources (" + name + ":" + partId + ")");
                }

                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 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]
            //  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]
            if (fuelCrossFeed)
            {
                //MonoBehaviour.print("foreach attach node");
                foreach (AttachNodeSim attachSim in attachNodes)
                {
                    if (attachSim.attachedPartSim != null)
                    {
                        if (/*attachSim.nodeType != AttachNode.NodeType.Surface &&*/
                            !(noCrossFeedNodeKey != null && noCrossFeedNodeKey.Length > 0 && attachSim.id.Contains(noCrossFeedNodeKey)))
                        {
                            if (visited.Contains(attachSim.attachedPartSim))
                            {
                                //if (log != null)
                                //    log.buf.AppendLine(indent + "Attached part already visited, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
                            }
                            else
                            {
                                //if (log != null)
                                //    log.buf.AppendLine(indent + "Adding attached part as source (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");

                                partSources = attachSim.attachedPartSim.GetSourceSet(type, allParts, visited, log, indent);
                                if (partSources.Count > 0)
                                {
                                    allSources.UnionWith(partSources);
                                    partSources.Clear();
                                }
                            }
                        }
                        else
                        {
                            //if (log != null)
                            //    log.buf.AppendLine(indent + "AttachNode is noCrossFeedKey, skipping (" + attachSim.attachedPartSim.name + ":" + attachSim.attachedPartSim.partId + ")");
                        }
                    }
                }

                if (allSources.Count > 0)
                {
                    if (log != null)
                    {
                        log.buf.AppendLine(indent + "Returning " + allSources.Count + " attached sources (" + name + ":" + partId + ")");
                    }

                    return(allSources);
                }
            }
            else
            {
                //if (log != null)
                //    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 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 (resources.HasType(type) && resourceFlowStates[type] != 0)
            {
                if (resources[type] > SimManager.RESOURCE_MIN)
                {
                    allSources.Add(this);

                    if (log != null)
                    {
                        log.buf.AppendLine(indent + "Returning enabled tank as only source (" + name + ":" + partId + ")");
                    }
                }
                else
                {
                    //if (log != null)
                    //    log.buf.AppendLine(indent + "Returning empty set, enabled tank is empty (" + name + ":" + partId + ")");
                }

                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]
            if (parent != null)
            {
                if (fuelCrossFeed)
                {
                    if (visited.Contains(parent))
                    {
                        //if (log != null)
                        //    log.buf.AppendLine(indent + "Parent part already visited, skipping (" + parent.name + ":" + parent.partId + ")");
                    }
                    else
                    {
                        allSources = parent.GetSourceSet(type, allParts, visited, log, indent);
                        if (allSources.Count > 0)
                        {
                            if (log != null)
                            {
                                log.buf.AppendLine(indent + "Returning " + allSources.Count + " parent sources (" + name + ":" + partId + ")");
                            }

                            return(allSources);
                        }
                    }
                }
                else
                {
                    //if (log != null)
                    //    log.buf.AppendLine(indent + "Crossfeed disabled, skipping radial parent (" + name + ":" + partId + ")");
                }
            }

            // Rule 8: If all preceding rules failed, part returns empty list.
            //if (log != null)
            //    log.buf.AppendLine(indent + "Returning empty set, no sources found (" + name + ":" + partId + ")");

            return(allSources);
        }
Beispiel #5
0
        public void SetResourceConsumptions()
        {
            if (this.part.HasModule <MultiModeEngine>())
            {
                string mode = this.part.GetModule <MultiModeEngine>().mode;

                foreach (ModuleEnginesFX engine in this.part.GetModules <ModuleEnginesFX>())
                {
                    if (engine.engineID == mode)
                    {
                        double flowRate = 0d;
                        if (part.vessel != null)
                        {
                            if (engine.throttleLocked)
                            {
                                flowRate = engine.maxThrust * (engine.thrustPercentage / 100f) / (isp * 9.81d);
                            }
                            else
                            {
                                if (part.vessel.Landed)
                                {
                                    flowRate = Math.Max(0.000001d, engine.maxThrust * (engine.thrustPercentage / 100f) * FlightInputHandler.state.mainThrottle) / (isp * 9.81d);
                                }
                                else
                                {
                                    if (engine.requestedThrust > 0)
                                    {
                                        flowRate = engine.requestedThrust / (isp * 9.81d);
                                    }
                                    else
                                    {
                                        flowRate = engine.maxThrust * (engine.thrustPercentage / 100f) / (isp * 9.81d);
                                    }
                                }
                            }
                        }
                        else
                        {
                            flowRate = engine.maxThrust * (engine.thrustPercentage / 100f) / (isp * 9.81d);
                        }

                        float flowMass = 0f;

                        foreach (Propellant propellant in engine.propellants)
                        {
                            flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id);
                        }

                        foreach (Propellant propellant in engine.propellants)
                        {
                            if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir")
                            {
                                continue;
                            }

                            double consumptionRate = propellant.ratio * flowRate / flowMass;
                            resourceConsumptions.Add(propellant.id, consumptionRate);
                        }
                    }
                }
            }
            else if (this.part.HasModule <ModuleEnginesFX>())
            {
                foreach (ModuleEnginesFX engine in part.GetModules <ModuleEnginesFX>())
                {
                    double flowRate = 0d;
                    if (part.vessel != null)
                    {
                        if (engine.throttleLocked)
                        {
                            flowRate = engine.maxThrust * (engine.thrustPercentage / 100f) / (isp * 9.81d);
                        }
                        else
                        {
                            if (part.vessel.Landed)
                            {
                                flowRate = Math.Max(0.000001d, engine.maxThrust * (engine.thrustPercentage / 100f) * FlightInputHandler.state.mainThrottle) / (isp * 9.81d);
                            }
                            else
                            {
                                if (engine.requestedThrust > 0)
                                {
                                    flowRate = engine.requestedThrust / (isp * 9.81d);
                                }
                                else
                                {
                                    flowRate = engine.maxThrust * (engine.thrustPercentage / 100f) / (isp * 9.81d);
                                }
                            }
                        }
                    }
                    else
                    {
                        flowRate = engine.maxThrust * (engine.thrustPercentage / 100f) / (isp * 9.81d);
                    }

                    float flowMass = 0f;

                    foreach (Propellant propellant in engine.propellants)
                    {
                        flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id);
                    }

                    foreach (Propellant propellant in engine.propellants)
                    {
                        if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir")
                        {
                            continue;
                        }

                        double consumptionRate = propellant.ratio * flowRate / flowMass;
                        resourceConsumptions.Add(propellant.id, consumptionRate);
                    }
                }
            }
            else if (this.part.HasModule <ModuleEngines>())
            {
                foreach (ModuleEngines engine in part.GetModules <ModuleEngines>())
                {
                    double flowRate = 0d;
                    if (part.vessel != null)
                    {
                        if (engine.throttleLocked)
                        {
                            flowRate = engine.maxThrust * (engine.thrustPercentage / 100f) / (isp * 9.81d);
                        }
                        else
                        {
                            if (part.vessel.Landed)
                            {
                                flowRate = Math.Max(0.000001d, engine.maxThrust * (engine.thrustPercentage / 100f) * FlightInputHandler.state.mainThrottle) / (isp * 9.81d);
                            }
                            else
                            {
                                if (engine.requestedThrust > 0)
                                {
                                    flowRate = engine.requestedThrust / (isp * 9.81d);
                                }
                                else
                                {
                                    flowRate = engine.maxThrust * (engine.thrustPercentage / 100f) / (isp * 9.81d);
                                }
                            }
                        }
                    }
                    else
                    {
                        flowRate = engine.maxThrust * (engine.thrustPercentage / 100f) / (isp * 9.81d);
                    }

                    float flowMass = 0f;

                    foreach (Propellant propellant in engine.propellants)
                    {
                        flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id);
                    }

                    foreach (Propellant propellant in engine.propellants)
                    {
                        if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir")
                        {
                            continue;
                        }

                        double consumptionRate = propellant.ratio * flowRate / flowMass;
                        resourceConsumptions.Add(propellant.id, consumptionRate);
                    }
                }
            }
        }
Beispiel #6
0
        public EngineSim(PartSim theEngine,
                         double atmosphere,
                         double velocity,
                         float maxThrust,
                         float minThrust,
                         float thrustPercentage,
                         float requestedThrust,
                         Vector3 vecThrust,
                         float realIsp,
                         FloatCurve atmosphereCurve,
                         FloatCurve velocityCurve,
                         bool throttleLocked,
                         List <Propellant> propellants,
                         bool active,
                         bool correctThrust)
        {
            StringBuilder buffer = null;

            //MonoBehaviour.print("Create EngineSim for " + theEngine.name);
            //MonoBehaviour.print("maxThrust = " + maxThrust);
            //MonoBehaviour.print("minThrust = " + minThrust);
            //MonoBehaviour.print("thrustPercentage = " + thrustPercentage);
            //MonoBehaviour.print("requestedThrust = " + requestedThrust);
            //MonoBehaviour.print("velocity = " + velocity);

            partSim = theEngine;

            isActive = active;
            thrust   = (maxThrust - minThrust) * (thrustPercentage / 100f) + minThrust;
            //MonoBehaviour.print("thrust = " + thrust);

            thrustVec = vecThrust;

            double flowRate = 0d;

            if (partSim.hasVessel)
            {
                //MonoBehaviour.print("hasVessel is true");
                actualThrust = requestedThrust;
                if (velocityCurve != null)
                {
                    actualThrust *= velocityCurve.Evaluate((float)velocity);
                    //MonoBehaviour.print("actualThrust at velocity = " + actualThrust);
                }

                isp = atmosphereCurve.Evaluate((float)partSim.part.staticPressureAtm);
                if (isp == 0d)
                {
                    MonoBehaviour.print("Isp at " + partSim.part.staticPressureAtm + " is zero. Flow rate will be NaN");
                }

                if (correctThrust && realIsp == 0)
                {
                    float ispsl = atmosphereCurve.Evaluate(0);
                    if (ispsl != 0)
                    {
                        thrust = thrust * isp / ispsl;
                    }
                    else
                    {
                        MonoBehaviour.print("Isp at sea level is zero. Unable to correct thrust.");
                    }
                    //MonoBehaviour.print("corrected thrust = " + thrust);
                }

                if (velocityCurve != null)
                {
                    thrust *= velocityCurve.Evaluate((float)velocity);
                    //MonoBehaviour.print("thrust at velocity = " + thrust);
                }

                if (throttleLocked)
                {
                    //MonoBehaviour.print("throttleLocked is true");
                    flowRate = thrust / (isp * 9.81d);
                }
                else
                {
                    if (partSim.isLanded)
                    {
                        //MonoBehaviour.print("partSim.isLanded is true, mainThrottle = " + FlightInputHandler.state.mainThrottle);
                        flowRate = Math.Max(0.000001d, thrust * FlightInputHandler.state.mainThrottle) / (isp * 9.81d);
                    }
                    else
                    {
                        if (requestedThrust > 0)
                        {
                            if (velocityCurve != null)
                            {
                                requestedThrust *= velocityCurve.Evaluate((float)velocity);
                                //MonoBehaviour.print("requestedThrust at velocity = " + requestedThrust);
                            }

                            //MonoBehaviour.print("requestedThrust > 0");
                            flowRate = requestedThrust / (isp * 9.81d);
                        }
                        else
                        {
                            //MonoBehaviour.print("requestedThrust <= 0");
                            flowRate = thrust / (isp * 9.81d);
                        }
                    }
                }
            }
            else
            {
                //MonoBehaviour.print("hasVessel is false");
                isp = atmosphereCurve.Evaluate((float)atmosphere);
                if (isp == 0d)
                {
                    MonoBehaviour.print("Isp at " + atmosphere + " is zero. Flow rate will be NaN");
                }
                if (correctThrust)
                {
                    float ispsl = atmosphereCurve.Evaluate(0);
                    if (ispsl != 0)
                    {
                        thrust = thrust * isp / ispsl;
                    }
                    else
                    {
                        MonoBehaviour.print("Isp at sea level is zero. Unable to correct thrust.");
                    }
                    //MonoBehaviour.print("corrected thrust = " + thrust);
                }

                if (velocityCurve != null)
                {
                    thrust *= velocityCurve.Evaluate((float)velocity);
                    //MonoBehaviour.print("thrust at velocity = " + thrust);
                }

                flowRate = thrust / (isp * 9.81d);
            }

            if (SimManager.logOutput)
            {
                buffer = new StringBuilder(1024);
                buffer.AppendFormat("flowRate = {0:g6}\n", flowRate);
            }

            float flowMass = 0f;

            foreach (Propellant propellant in propellants)
            {
                flowMass += propellant.ratio * ResourceContainer.GetResourceDensity(propellant.id);
            }

            if (SimManager.logOutput)
            {
                buffer.AppendFormat("flowMass = {0:g6}\n", flowMass);
            }

            foreach (Propellant propellant in propellants)
            {
                if (propellant.name == "ElectricCharge" || propellant.name == "IntakeAir")
                {
                    continue;
                }

                double consumptionRate = propellant.ratio * flowRate / flowMass;
                if (SimManager.logOutput)
                {
                    buffer.AppendFormat("Add consumption({0}, {1}:{2:d}) = {3:g6}\n", ResourceContainer.GetResourceName(propellant.id), theEngine.name, theEngine.partId, consumptionRate);
                }
                resourceConsumptions.Add(propellant.id, consumptionRate);
            }

            if (SimManager.logOutput)
            {
                MonoBehaviour.print(buffer);
            }
        }
Beispiel #7
0
        public bool SetResourceDrains(List <PartSim> allParts, List <PartSim> allFuelLines, HashSet <PartSim> drainingParts)
        {
            LogMsg log = null;

            // A dictionary to hold a set of parts for each resource
            Dictionary <int, HashSet <PartSim> > sourcePartSets = new Dictionary <int, HashSet <PartSim> >();

            foreach (int type in resourceConsumptions.Types)
            {
                HashSet <PartSim> sourcePartSet = null;
                switch (ResourceContainer.GetResourceFlowMode(type))
                {
                case ResourceFlowMode.NO_FLOW:
                    if (partSim.resources[type] > SimManager.RESOURCE_MIN)
                    {
                        sourcePartSet = new HashSet <PartSim>();
                        //MonoBehaviour.print("SetResourceDrains(" + name + ":" + partId + ") setting sources to just this");
                        sourcePartSet.Add(partSim);
                    }
                    break;

                case ResourceFlowMode.ALL_VESSEL:
                    foreach (PartSim aPartSim in allParts)
                    {
                        if (aPartSim.resources[type] > SimManager.RESOURCE_MIN)
                        {
                            if (sourcePartSet == null)
                            {
                                sourcePartSet = new HashSet <PartSim>();
                            }

                            sourcePartSet.Add(aPartSim);
                        }
                    }
                    break;

                case ResourceFlowMode.STAGE_PRIORITY_FLOW:
                {
                    Dictionary <int, HashSet <PartSim> > stagePartSets = new Dictionary <int, HashSet <PartSim> >();
                    int maxStage = -1;
                    foreach (PartSim aPartSim in allParts)
                    {
                        if (aPartSim.resources[type] > SimManager.RESOURCE_MIN)
                        {
                            //int stage = aPartSim.decoupledInStage;            // Use the number of the stage the tank is decoupled in
                            int stage = aPartSim.DecouplerCount();                      // Use the count of decouplers between tank and root
                            if (stage > maxStage)
                            {
                                maxStage = stage;
                            }
                            if (stagePartSets.ContainsKey(stage))
                            {
                                sourcePartSet = stagePartSets[stage];
                            }
                            else
                            {
                                sourcePartSet = new HashSet <PartSim>();
                                stagePartSets.Add(stage, sourcePartSet);
                            }

                            sourcePartSet.Add(aPartSim);
                        }
                    }

                    while (maxStage >= 0)
                    {
                        if (stagePartSets.ContainsKey(maxStage))
                        {
                            if (stagePartSets[maxStage].Count() > 0)
                            {
                                sourcePartSet = stagePartSets[maxStage];
                                break;
                            }
                        }
                        maxStage--;
                    }
                }
                break;

                case ResourceFlowMode.STACK_PRIORITY_SEARCH:
                    HashSet <PartSim> visited = new HashSet <PartSim>();

                    if (SimManager.logOutput)
                    {
                        log = new LogMsg();
                        log.buf.AppendLine("Find " + ResourceContainer.GetResourceName(type) + " sources for " + partSim.name + ":" + partSim.partId);
                    }
                    sourcePartSet = partSim.GetSourceSet(type, allParts, visited, log, "");
                    if (SimManager.logOutput)
                    {
                        MonoBehaviour.print(log.buf);
                    }
                    break;

                default:
                    MonoBehaviour.print("SetResourceDrains(" + partSim.name + ":" + partSim.partId + ") Unexpected flow type for " + ResourceContainer.GetResourceName(type) + ")");
                    break;
                }

                if (sourcePartSet != null && sourcePartSet.Count > 0)
                {
                    sourcePartSets[type] = sourcePartSet;
                    if (SimManager.logOutput)
                    {
                        log = new LogMsg();
                        log.buf.AppendLine("Source parts for " + ResourceContainer.GetResourceName(type) + ":");
                        foreach (PartSim partSim in sourcePartSet)
                        {
                            log.buf.AppendLine(partSim.name + ":" + partSim.partId);
                        }
                        MonoBehaviour.print(log.buf);
                    }
                }
            }

            // If we don't have sources for all the needed resources then return false without setting up any drains
            foreach (int type in resourceConsumptions.Types)
            {
                if (!sourcePartSets.ContainsKey(type))
                {
                    if (SimManager.logOutput)
                    {
                        MonoBehaviour.print("No source of " + ResourceContainer.GetResourceName(type));
                    }

                    isActive = false;
                    return(false);
                }
            }

            // Now we set the drains on the members of the sets and update the draining parts set
            foreach (int type in resourceConsumptions.Types)
            {
                HashSet <PartSim> sourcePartSet = sourcePartSets[type];
                // Loop through the members of the set
                double amount = resourceConsumptions[type] / sourcePartSet.Count;
                foreach (PartSim partSim in sourcePartSet)
                {
                    if (SimManager.logOutput)
                    {
                        MonoBehaviour.print("Adding drain of " + amount + " " + ResourceContainer.GetResourceName(type) + " to " + partSim.name + ":" + partSim.partId);
                    }

                    partSim.resourceDrains.Add(type, amount);
                    drainingParts.Add(partSim);
                }
            }

            return(true);
        }