Example #1
0
        public virtual void FixedUpdate()
        {
            if (!Lib.IsFlight() || module == null)
            {
                return;
            }

            if (isBroken)
            {
                if (isBroken != lastFixedBrokenState)
                {
                    lastFixedBrokenState = isBroken;
                    FixModule(!isBroken);
                }
            }
            else if (hasFixedEnergyChanged != hasEnergy)
            {
                hasFixedEnergyChanged = hasEnergy;
                lastFixedBrokenState  = false;
                // Update module
                FixModule(hasEnergy);
            }

            // If isConsuming
            if (isConsuming && resources != null)
            {
                resources.Consume(actualCost * Kerbalism.elapsed_s, "deploy");
            }
        }
Example #2
0
 public static void BackgroundUpdate(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Deploy deploy, ResourceInfo ec, double elapsed_s)
 {
     if (deploy.isConsuming)
     {
         ec.Consume(deploy.extra_Cost * elapsed_s, "deploy");
     }
 }
Example #3
0
        private static bool FixedUpdatePrefix(KerbalismSentinel __instance)
        {
            if (__instance.isTrackingEnabled)
            {
                VesselData vd = __instance.vessel.KerbalismData();
                if (!vd.Connection.linked || vd.Connection.rate < __instance.comms_rate)
                {
                    __instance.isTracking = false;
                    __instance.status     = "Comms connection too weak";
                    return(false);
                }

                ResourceInfo ec = ResourceCache.GetResource(__instance.vessel, "ElectricCharge");
                ec.Consume(__instance.ec_rate * Kerbalism.elapsed_s, ResourceBroker.Scanner);

                if (ec.Amount <= double.Epsilon)
                {
                    __instance.isTracking = false;
                    __instance.status     = Local.Module_Experiment_issue4;                 // "no Electricity"
                    return(false);
                }

                __instance.isTracking = true;
            }

            return(true);
        }
Example #4
0
 static void ProcessLight(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleLight light, ResourceInfo ec, double elapsed_s)
 {
     if (light.useResources && Lib.Proto.GetBool(m, "isOn"))
     {
         ec.Consume(light.resourceAmount * elapsed_s, ResourceBroker.Light);
     }
 }
Example #5
0
 public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, GravityRing ring, ResourceInfo ec, double elapsed_s)
 {
     // if the module is either non-deployable or deployed
     if (ring.deploy.Length == 0 || Lib.Proto.GetBool(m, "deployed"))
     {
         // consume ec
         ec.Consume(ring.ec_rate * elapsed_s, ResourceBroker.GravityRing);
     }
 }
Example #6
0
        static void ProcessStockLab(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleScienceConverter lab, ResourceInfo ec, double elapsed_s)
        {
            // note: we are only simulating the EC consumption
            // note: there is no easy way to 'stop' the lab when there isn't enough EC

            // if active
            if (Lib.Proto.GetBool(m, "IsActivated"))
            {
                // consume ec
                ec.Consume(lab.powerRequirement * elapsed_s, ResourceBroker.ScienceLab);
            }
        }
Example #7
0
        public static void Update(Vessel v)
        {
            // do nothing if not an eva kerbal
            if (!v.isEVA)
            {
                return;
            }

            // get KerbalEVA module
            KerbalEVA kerbal = Lib.FindModules <KerbalEVA>(v)[0];

            // Stock KSP adds 5 units of monoprop to EVAs. We want to limit that amount
            // to whatever was available in the ship, so we don't magically create EVA prop out of nowhere
            if (Cache.HasVesselObjectsCache(v, "eva_prop"))
            {
                var quantity = Cache.VesselObjectsCache <double>(v, "eva_prop");
                Cache.RemoveVesselObjectsCache(v, "eva_prop");
                Lib.SetResource(kerbal.part, Lib.EvaPropellantName(), quantity, Lib.EvaPropellantCapacity());
            }

            // get resource handler
            ResourceInfo ec = ResourceCache.GetResource(v, "ElectricCharge");

            // determine if headlamps need ec
            // - not required if there is no EC capacity in eva kerbal (no ec supply in profile)
            // - not required if no EC cost for headlamps is specified (set by the user)
            bool need_ec = ec.Capacity > double.Epsilon && Settings.HeadLampsCost > double.Epsilon;

            // consume EC for the headlamps
            if (need_ec && kerbal.lampOn)
            {
                ec.Consume(Settings.HeadLampsCost * Kerbalism.elapsed_s, ResourceBroker.Light);
            }

            // force the headlamps on/off
            HeadLamps(kerbal, kerbal.lampOn && (!need_ec || ec.Amount > double.Epsilon));

            // if dead
            if (IsDead(v))
            {
                // enforce freezed state
                Freeze(kerbal);

                // disable modules
                DisableModules(kerbal);

                // remove plant flag action
                kerbal.flagItems = 0;
            }
        }
Example #8
0
        public void FixedUpdate()
        {
            // do nothing in the editor
            if (Lib.IsEditor())
            {
                return;
            }

            // if has any animation playing, consume energy.
            if (Is_consuming_energy())
            {
                // get resource handler
                ResourceInfo ec = ResourceCache.GetResource(vessel, "ElectricCharge");

                // consume ec
                ec.Consume(ec_rate * Kerbalism.elapsed_s, ResourceBroker.GravityRing);
            }
        }
Example #9
0
        public static void BackgroundUpdate(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Laboratory lab, ResourceInfo ec, double elapsed_s)
        {
            // if enabled
            if (Lib.Proto.GetBool(m, "running"))
            {
                // if a researcher is not required, or the researcher is present
                background_researcher_cs = new CrewSpecs(lab.researcher);
                if (!background_researcher_cs || background_researcher_cs.Check(p.protoModuleCrew))
                {
                    double rate = lab.analysis_rate;
                    if (background_researcher_cs)
                    {
                        int    bonus     = background_researcher_cs.Bonus(p.protoModuleCrew);
                        double crew_gain = 1 + bonus * Settings.LaboratoryCrewLevelBonus;
                        crew_gain = Lib.Clamp(crew_gain, 1, Settings.MaxLaborartoryBonus);
                        rate     *= crew_gain;
                    }

                    // get sample to analyze
                    background_sample = NextSample(v);

                    // if there is a sample to analyze
                    if (background_sample != null)
                    {
                        // consume EC
                        ec.Consume(lab.ec_rate * elapsed_s, "laboratory");

                        // if there was ec
                        // - comparing against amount in previous simulation step
                        if (ec.Amount > double.Epsilon)
                        {
                            // analyze the sample
                            var status = Analyze(v, background_sample, rate * elapsed_s);
                            if (status != Status.RUNNING)
                            {
                                Lib.Proto.Set(m, "running", false);
                            }
                        }
                    }
                }
            }
        }
Example #10
0
        public static void Update(Vessel v)
        {
            // do nothing if not an eva kerbal
            if (!v.isEVA)
            {
                return;
            }

            // get KerbalEVA module
            KerbalEVA kerbal = Lib.FindModules <KerbalEVA>(v)[0];

            // get resource handler
            ResourceInfo ec = ResourceCache.GetResource(v, "ElectricCharge");

            // determine if headlamps need ec
            // - not required if there is no EC capacity in eva kerbal (no ec supply in profile)
            // - not required if no EC cost for headlamps is specified (set by the user)
            bool need_ec = ec.Capacity > double.Epsilon && Settings.HeadLampsCost > double.Epsilon;

            // consume EC for the headlamps
            if (need_ec && kerbal.lampOn)
            {
                ec.Consume(Settings.HeadLampsCost * Kerbalism.elapsed_s, ResourceBroker.Light);
            }

            // force the headlamps on/off
            HeadLamps(kerbal, kerbal.lampOn && (!need_ec || ec.Amount > double.Epsilon));

            // if dead
            if (IsDead(v))
            {
                // enforce freezed state
                Freeze(kerbal);

                // disable modules
                DisableModules(kerbal);

                // remove plant flag action
                kerbal.flagItems = 0;
            }
        }
Example #11
0
        public static void BackgroundUpdate(Vessel v, ProtoPartModuleSnapshot m, KerbalismSentinel prefab, VesselData vd, ResourceInfo ec, double elapsed_s)
        {
            if (Lib.Proto.GetBool(m, "isTrackingEnabled"))
            {
                if (!vd.Connection.linked || vd.Connection.rate < prefab.comms_rate)
                {
                    Lib.Proto.Set(m, "isTracking", false);
                    return;
                }

                ec.Consume(prefab.ec_rate * elapsed_s, ResourceBroker.Scanner);

                if (ec.Amount <= double.Epsilon)
                {
                    Lib.Proto.Set(m, "isTracking", false);
                    return;
                }

                Lib.Proto.Set(m, "isTracking", true);
            }
        }
Example #12
0
        // consume EC for transmission, and transmit science data
        public static void Update(Vessel v, VesselData vd, ResourceInfo ec, double elapsed_s)
        {
            // do nothing if science system is disabled
            if (!Features.Science)
            {
                return;
            }

            // consume ec for transmitters
            ec.Consume(vd.Connection.ec_idle * elapsed_s, ResourceBroker.CommsIdle);

            // avoid corner-case when RnD isn't live during scene changes
            // - this avoid losing science if the buffer reach threshold during a scene change
            if (HighLogic.CurrentGame.Mode != Game.Modes.SANDBOX && ResearchAndDevelopment.Instance == null)
            {
                return;
            }

            // clear list of files transmitted
            vd.filesTransmitted.Clear();

            // check connection
            if (vd.Connection == null ||
                !vd.Connection.linked ||
                vd.Connection.rate <= 0.0 ||
                !vd.deviceTransmit ||
                ec.Amount < vd.Connection.ec_idle * elapsed_s)
            {
                // reset all files transmit rate
                foreach (Drive drive in Drive.GetDrives(vd, true))
                {
                    foreach (File f in drive.files.Values)
                    {
                        f.transmitRate = 0.0;
                    }
                }

                // do nothing else
                return;
            }

            double totalTransmitCapacity     = vd.Connection.rate * elapsed_s;
            double remainingTransmitCapacity = totalTransmitCapacity;

            GetFilesToTransmit(v, vd);

            if (xmitFiles.Count == 0)
            {
                return;
            }

            UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.Science.Update-Loop");

            // traverse the list in reverse because :
            // - warp cache files are at the end, and they are always transmitted regerdless of transmit capacity
            // - others files are next, sorted in science value per MB ascending order
            for (int i = xmitFiles.Count - 1; i >= 0; i--)
            {
                XmitFile xmitFile = xmitFiles[i];

                if (xmitFile.file.size == 0.0)
                {
                    continue;
                }

                // always transmit everything in the warp cache
                if (!xmitFile.isInWarpCache && remainingTransmitCapacity <= 0.0)
                {
                    break;
                }

                // determine how much data is transmitted
                double transmitted = xmitFile.isInWarpCache ? xmitFile.file.size : Math.Min(xmitFile.file.size, remainingTransmitCapacity);

                if (transmitted == 0.0)
                {
                    continue;
                }

                // consume transmit capacity
                remainingTransmitCapacity -= transmitted;

                // get science value
                double xmitScienceValue = transmitted * xmitFile.sciencePerMB;

                // consume data in the file
                xmitFile.file.size -= transmitted;

                // remove science collected (ignoring final science value clamped to subject completion)
                xmitFile.file.subjectData.RemoveScienceCollectedInFlight(xmitScienceValue);

                // save transmit rate for the file, and add it to the VesselData list of files being transmitted
                if (xmitFile.isInWarpCache && xmitFile.realDriveFile != null)
                {
                    xmitFile.realDriveFile.transmitRate = transmitted / elapsed_s;
                    vd.filesTransmitted.Add(xmitFile.realDriveFile);
                }
                else
                {
                    xmitFile.file.transmitRate = transmitted / elapsed_s;
                    vd.filesTransmitted.Add(xmitFile.file);
                }

                if (xmitScienceValue > 0.0)
                {
                    // add science to the subject (and eventually included subjects), trigger completion events, credit the science, return how much has been credited.
                    vd.scienceTransmitted += xmitFile.file.subjectData.RetrieveScience(xmitScienceValue, true, v.protoVessel, xmitFile.file);
                }
            }

            UnityEngine.Profiling.Profiler.EndSample();

            // consume EC cost for transmission (ec_idle is consumed above)
            double transmittedCapacity = totalTransmitCapacity - remainingTransmitCapacity;
            double transmissionCost    = (vd.Connection.ec - vd.Connection.ec_idle) * (transmittedCapacity / (vd.Connection.rate * elapsed_s));

            ec.Consume(transmissionCost * elapsed_s, ResourceBroker.CommsXmit);
        }
Example #13
0
        // trigger a random breakdown event
        public static void Breakdown(Vessel v, ProtoCrewMember c)
        {
            // constants
            const double res_penalty = 0.1;                    // proportion of food lost on 'depressed' and 'wrong_valve'

            // get a supply resource at random
            ResourceInfo res = null;

            if (Profile.supplies.Count > 0)
            {
                Supply supply = Profile.supplies[Lib.RandomInt(Profile.supplies.Count)];
                res = ResourceCache.GetResource(v, supply.resource);
            }

            // compile list of events with condition satisfied
            List <KerbalBreakdown> events = new List <KerbalBreakdown>
            {
                KerbalBreakdown.mumbling                 //< do nothing, here so there is always something that can happen
            };

            if (Lib.HasData(v))
            {
                events.Add(KerbalBreakdown.fat_finger);
            }
            if (Reliability.CanMalfunction(v))
            {
                events.Add(KerbalBreakdown.rage);
            }
            if (res != null && res.Amount > double.Epsilon)
            {
                events.Add(KerbalBreakdown.wrong_valve);
            }

            // choose a breakdown event
            KerbalBreakdown breakdown = events[Lib.RandomInt(events.Count)];

            // generate message
            string text    = "";
            string subtext = "";

            switch (breakdown)
            {
            case KerbalBreakdown.mumbling:
                text    = "$ON_VESSEL$KERBAL has been in space for too long";
                subtext = "Mumbling incoherently";
                break;

            case KerbalBreakdown.fat_finger:
                text    = "$ON_VESSEL$KERBAL is pressing buttons at random on the control panel";
                subtext = "Science data has been lost";
                break;

            case KerbalBreakdown.rage:
                text    = "$ON_VESSEL$KERBAL is possessed by a blind rage";
                subtext = "A component has been damaged";
                break;

            case KerbalBreakdown.wrong_valve:
                text    = "$ON_VESSEL$KERBAL opened the wrong valve";
                subtext = res.ResourceName + " has been lost";
                break;
            }

            // post message first so this one is shown before malfunction message
            Message.Post(Severity.breakdown, Lib.ExpandMsg(text, v, c), subtext);

            // trigger the event
            switch (breakdown)
            {
            case KerbalBreakdown.mumbling:
                break;                         // do nothing

            case KerbalBreakdown.fat_finger:
                Lib.RemoveData(v);
                break;

            case KerbalBreakdown.rage:
                Reliability.CauseMalfunction(v);
                break;

            case KerbalBreakdown.wrong_valve:
                res.Consume(res.Amount * res_penalty, "breakdown");
                break;
            }

            // remove reputation
            if (HighLogic.CurrentGame.Mode == Game.Modes.CAREER)
            {
                Reputation.Instance.AddReputation(-Settings.KerbalBreakdownReputationPenalty, TransactionReasons.Any);
            }
        }
Example #14
0
        public void FixedUpdate()
        {
            // do nothing in the editor
            if (Lib.IsEditor())
            {
                return;
            }

            // if enabled
            if (running)
            {
                // if a researcher is not required, or the researcher is present
                if (!researcher_cs || researcher_cs.Check(part.protoModuleCrew))
                {
                    // get next sample to analyze
                    current_sample = NextSample(vessel);

                    double rate = analysis_rate;
                    if (researcher_cs)
                    {
                        int    bonus     = researcher_cs.Bonus(part.protoModuleCrew);
                        double crew_gain = 1 + bonus * Settings.LaboratoryCrewLevelBonus;
                        crew_gain = Lib.Clamp(crew_gain, 1, Settings.MaxLaborartoryBonus);
                        rate     *= crew_gain;
                    }

                    // if there is a sample to analyze
                    if (current_sample != null)
                    {
                        // consume EC
                        ec = ResourceCache.GetResource(vessel, "ElectricCharge");
                        ec.Consume(ec_rate * Kerbalism.elapsed_s, "laboratory");

                        // if there was ec
                        // - comparing against amount in previous simulation step
                        if (ec.Amount > double.Epsilon)
                        {
                            // analyze the sample
                            status  = Analyze(vessel, current_sample, rate * Kerbalism.elapsed_s);
                            running = status == Status.RUNNING;
                        }
                        // if there was no ec
                        else
                        {
                            status = Status.NO_EC;
                        }
                    }
                    // if there is no sample to analyze
                    else
                    {
                        status = Status.NO_SAMPLE;
                    }
                }
                // if a researcher is required, but missing
                else
                {
                    status = Status.NO_RESEARCHER;
                }
            }
            // if disabled
            else
            {
                status = Status.DISABLED;
            }
        }
Example #15
0
        // consume EC for transmission, and transmit science data
        public static void Update(Vessel v, VesselData vd, ResourceInfo ec, double elapsed_s)
        {
            // do nothing if science system is disabled
            if (!Features.Science)
            {
                return;
            }

            // consume ec for transmitters
            ec.Consume(vd.Connection.ec_idle * elapsed_s, ResourceBroker.CommsIdle);

            // avoid corner-case when RnD isn't live during scene changes
            // - this avoid losing science if the buffer reach threshold during a scene change
            if (HighLogic.CurrentGame.Mode != Game.Modes.SANDBOX && ResearchAndDevelopment.Instance == null)
            {
                return;
            }

            // clear list of files transmitted
            vd.filesTransmitted.Clear();

            // check connection
            if (vd.Connection == null ||
                !vd.Connection.linked ||
                vd.Connection.rate <= 0.0 ||
                !vd.deviceTransmit ||
                ec.Amount < vd.Connection.ec_idle * elapsed_s)
            {
                // reset all files transmit rate
                foreach (Drive drive in Drive.GetDrives(vd, true))
                {
                    foreach (File f in drive.files.Values)
                    {
                        f.transmitRate = 0.0;
                    }
                }

                // do nothing else
                return;
            }

            double totalTransmitCapacity     = vd.Connection.rate * elapsed_s;
            double remainingTransmitCapacity = totalTransmitCapacity;
            double scienceCredited           = 0.0;

            GetFilesToTransmit(v, vd);

            if (xmitFiles.Count == 0)
            {
                return;
            }

            UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.Science.Update-Loop");

            // traverse the list in reverse because :
            // - warp cache files are at the end, and they are always transmitted regerdless of transmit capacity
            // - others files are next, sorted in science value per MB ascending order
            for (int i = xmitFiles.Count - 1; i >= 0; i--)
            {
                XmitFile xmitFile = xmitFiles[i];

                if (xmitFile.file.size == 0.0)
                {
                    continue;
                }

                // always transmit everything in the warp cache
                if (!xmitFile.isInWarpCache && remainingTransmitCapacity <= 0.0)
                {
                    break;
                }

                // determine how much data is transmitted
                double transmitted = xmitFile.isInWarpCache ? xmitFile.file.size : Math.Min(xmitFile.file.size, remainingTransmitCapacity);

                if (transmitted == 0.0)
                {
                    continue;
                }

                // consume transmit capacity
                remainingTransmitCapacity -= transmitted;

                // get science value
                double xmitScienceValue = transmitted * xmitFile.sciencePerMB;

                // consume data in the file
                xmitFile.file.size -= transmitted;

                // remove science collected (ignoring final science value clamped to subject completion)
                xmitFile.file.subjectData.RemoveScienceCollectedInFlight(xmitScienceValue);

                // fire subject completed events
                int timesCompleted = xmitFile.file.subjectData.UpdateSubjectCompletion(xmitScienceValue);
                if (timesCompleted > 0)
                {
                    SubjectXmitCompleted(xmitFile.file, timesCompleted, v);
                }

                // save transmit rate for the file, and add it to the VesselData list of files being transmitted
                if (xmitFile.isInWarpCache && xmitFile.realDriveFile != null)
                {
                    xmitFile.realDriveFile.transmitRate = transmitted / elapsed_s;
                    vd.filesTransmitted.Add(xmitFile.realDriveFile);
                }
                else
                {
                    xmitFile.file.transmitRate = transmitted / elapsed_s;
                    vd.filesTransmitted.Add(xmitFile.file);
                }

                // clamp science value to subject max value
                xmitScienceValue = Math.Min(xmitScienceValue, xmitFile.file.subjectData.ScienceRemainingToRetrieve);

                if (xmitScienceValue > 0.0)
                {
                    // add credits
                    scienceCredited += xmitScienceValue;

                    // credit the subject
                    xmitFile.file.subjectData.AddScienceToRnDSubject(xmitScienceValue);
                }
            }

            UnityEngine.Profiling.Profiler.EndSample();

            vd.scienceTransmitted += scienceCredited;

            // consume EC cost for transmission (ec_idle is consumed above)
            double transmittedCapacity = totalTransmitCapacity - remainingTransmitCapacity;
            double transmissionCost    = (vd.Connection.ec - vd.Connection.ec_idle) * (transmittedCapacity / vd.Connection.rate);

            ec.Consume(transmissionCost, ResourceBroker.CommsXmit);

            UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.Science.Update-AddScience");

            // Add science points, but wait until we have at least 0.1 points to add because AddScience is VERY slow
            // We don't use "TransactionReasons.ScienceTransmission" because AddScience fire multiple events not meant to be fired continuously
            // this avoid many side issues (ex : chatterer transmit sound playing continously, strategia "+0.0 science" popup...)
            ScienceDB.uncreditedScience += scienceCredited;
            if (ScienceDB.uncreditedScience > 0.1)
            {
                if (GameHasRnD)
                {
                    ResearchAndDevelopment.Instance.AddScience((float)ScienceDB.uncreditedScience, TransactionReasons.None);
                }

                ScienceDB.uncreditedScience = 0.0;
            }

            UnityEngine.Profiling.Profiler.EndSample();
        }
Example #16
0
        public void Execute(Vessel v, VesselData vd, VesselResources resources, double elapsed_s)
        {
            // store list of crew to kill
            List <ProtoCrewMember> deferred_kills = new List <ProtoCrewMember>();

            // get input resource handler
            ResourceInfo res = input.Length > 0 ? resources.GetResource(v, input) : null;

            // determine message variant
            uint variant = vd.EnvTemperature < Settings.LifeSupportSurvivalTemperature ? 0 : 1u;

            // get product of all environment modifiers
            double k = Modifiers.Evaluate(v, vd, resources, modifiers);

            bool lifetime_enabled = PreferencesRadiation.Instance.lifetime;

            // for each crew
            foreach (ProtoCrewMember c in Lib.CrewList(v))
            {
                // get kerbal data
                KerbalData kd = DB.Kerbal(c.name);

                // skip rescue kerbals
                if (kd.rescue)
                {
                    continue;
                }

                // skip disabled kerbals
                if (kd.disabled)
                {
                    continue;
                }

                // get kerbal property data from db
                RuleData rd = kd.Rule(name);
                rd.lifetime = lifetime_enabled && lifetime;

                // influence consumption by elapsed time
                double step = elapsed_s;

                // if interval-based
                if (interval > 0.0)
                {
                    // accumulate time
                    rd.time_since += elapsed_s;

                    // determine number of intervals that has passed (can be 2 or more if elapsed_s > interval * 2)
                    step = Math.Floor(rd.time_since / interval);

                    // consume time
                    rd.time_since -= step * interval;
                }

                // if there is a resource specified
                if (res != null && rate > double.Epsilon)
                {
                    // get rate including per-kerbal variance
                    double resRate =
                        rate                                                        // consumption rate
                        * Variance(name, c, individuality)                          // kerbal-specific variance
                        * k;                                                        // product of environment modifiers

                    // determine amount of resource to consume

                    double required = resRate * step;                           // seconds elapsed or interval amount

                    // remember if a meal is consumed/produced in this simulation step
                    if (interval > 0.0)
                    {
                        double ratePerStep = resRate / interval;
                        res.UpdateIntervalRule(-required, -ratePerStep, name);
                        if (output.Length > 0)
                        {
                            ResourceCache.GetResource(v, output).UpdateIntervalRule(required * ratio, ratePerStep * ratio, name);
                        }
                    }

                    // if continuous, or if one or more intervals elapsed
                    if (step > 0.0)
                    {
                        // if there is no output
                        if (output.Length == 0)
                        {
                            // simply consume (that is faster)
                            res.Consume(required, name);
                        }
                        // if there is an output
                        else
                        {
                            // transform input into output resource
                            // - rules always dump excess overboard (because it is waste)
                            ResourceRecipe recipe = new ResourceRecipe(name);
                            recipe.AddInput(input, required);
                            recipe.AddOutput(output, required * ratio, true);
                            resources.AddRecipe(recipe);
                        }
                    }
                }

                // if continuous, or if one or more intervals elapsed
                if (step > 0.0)
                {
                    // degenerate:
                    // - if the environment modifier is not telling to reset (by being zero)
                    // - if this rule is resource-less, or if there was not enough resource in the vessel
                    if (k > 0.0 && (input.Length == 0 || res.Amount <= double.Epsilon))
                    {
                        rd.problem += degeneration                                   // degeneration rate per-second or per-interval
                                      * k                                            // product of environment modifiers
                                      * step                                         // seconds elapsed or by number of steps
                                      * Variance(name, c, variance);                 // kerbal-specific variance
                    }
                    // else slowly recover
                    else
                    {
                        rd.problem *= 1.0 / (1.0 + Math.Max(interval, 1.0) * step * 0.002);
                    }
                }

                bool do_breakdown = false;

                if (breakdown)
                {
                    // don't do breakdowns and don't show stress message if disabled
                    if (!PreferencesComfort.Instance.stressBreakdowns)
                    {
                        return;
                    }

                    // stress level
                    double breakdown_probability = rd.problem / warning_threshold;
                    breakdown_probability = Lib.Clamp(breakdown_probability, 0.0, 1.0);

                    // use the stupidity of a kerbal.
                    // however, nobody is perfect - not even a kerbal with a stupidity of 0.
                    breakdown_probability *= c.stupidity * 0.6 + 0.4;

                    // apply the weekly error rate
                    breakdown_probability *= PreferencesComfort.Instance.stressBreakdownRate;

                    // now we have the probability for one failure per week, based on the
                    // individual stupidity and stress level of the kerbal.

                    breakdown_probability = (breakdown_probability * elapsed_s) / (Lib.DaysInYear * Lib.HoursInDay * 3600);
                    if (breakdown_probability > Lib.RandomDouble())
                    {
                        do_breakdown = true;

                        // we're stressed out and just made a major mistake, this further increases the stress level...
                        rd.problem += warning_threshold * 0.05;                         // add 5% of the warning treshold to current stress level
                    }
                }

                // kill kerbal if necessary
                if (rd.problem >= fatal_threshold)
                {
#if DEBUG || DEVBUILD
                    Lib.Log("Rule " + name + " kills " + c.name + " at " + rd.problem + " " + degeneration + "/" + k + "/" + step + "/" + Variance(name, c, variance));
#endif
                    if (fatal_message.Length > 0)
                    {
                        Message.Post(breakdown ? Severity.breakdown : Severity.fatality, Lib.ExpandMsg(fatal_message, v, c, variant));
                    }

                    if (breakdown)
                    {
                        do_breakdown = true;

                        // move back between warning and danger level
                        rd.problem = (warning_threshold + danger_threshold) * 0.5;

                        // make sure next danger message is shown
                        rd.message = 1;
                    }
                    else
                    {
                        deferred_kills.Add(c);
                    }
                }
                // show messages
                else if (rd.problem >= danger_threshold && rd.message < 2)
                {
                    if (danger_message.Length > 0)
                    {
                        Message.Post(Severity.danger, Lib.ExpandMsg(danger_message, v, c, variant));
                    }
                    rd.message = 2;
                }
                else if (rd.problem >= warning_threshold && rd.message < 1)
                {
                    if (warning_message.Length > 0)
                    {
                        Message.Post(Severity.warning, Lib.ExpandMsg(warning_message, v, c, variant));
                    }
                    rd.message = 1;
                }
                else if (rd.problem < warning_threshold && rd.message > 0)
                {
                    if (relax_message.Length > 0)
                    {
                        Message.Post(Severity.relax, Lib.ExpandMsg(relax_message, v, c, variant));
                    }
                    rd.message = 0;
                }

                if (do_breakdown)
                {
                    // trigger breakdown event
                    Misc.Breakdown(v, c);
                }
            }

            // execute the deferred kills
            foreach (ProtoCrewMember c in deferred_kills)
            {
                Misc.Kill(v, c);
            }
        }
Example #17
0
        static void ProcessCryoTank(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule cryotank, VesselResources resources, ResourceInfo ec, double elapsed_s)
        {
            // Note. Currently background simulation of Cryotanks has an irregularity in that boiloff of a fuel type in a tank removes resources from all tanks
            // but at least some simulation is better than none ;)

            // get list of fuels, do nothing if no fuels
            IList fuels = Lib.ReflectionValue <IList>(cryotank, "fuels");

            if (fuels == null)
            {
                return;
            }

            // is cooling available, note: comparing against amount in previous simulation step
            bool available = (Lib.Proto.GetBool(m, "CoolingEnabled") && ec.Amount > double.Epsilon);

            // get cooling cost
            double cooling_cost = Lib.ReflectionValue <float>(cryotank, "CoolingCost");

            string fuel_name    = "";
            double amount       = 0.0;
            double total_cost   = 0.0;
            double boiloff_rate = 0.0;

            foreach (var item in fuels)
            {
                fuel_name = Lib.ReflectionValue <string>(item, "fuelName");
                // if fuel_name is null, don't do anything
                if (fuel_name == null)
                {
                    continue;
                }

                //get fuel resource
                ResourceInfo fuel = resources.GetResource(v, fuel_name);

                // if there is some fuel
                // note: comparing against amount in previous simulation step
                if (fuel.Amount > double.Epsilon)
                {
                    // Try to find resource "fuel_name" in PartResources
                    ProtoPartResourceSnapshot proto_fuel = p.resources.Find(k => k.resourceName == fuel_name);

                    // If part doesn't have the fuel, don't do anything.
                    if (proto_fuel == null)
                    {
                        continue;
                    }

                    // get amount in the part
                    amount = proto_fuel.amount;

                    // if cooling is enabled and there is enough EC
                    if (available)
                    {
                        // calculate ec consumption
                        total_cost += cooling_cost * amount * 0.001;
                    }
                    // if cooling is disabled or there wasn't any EC
                    else
                    {
                        // get boiloff rate per-second
                        boiloff_rate = Lib.ReflectionValue <float>(item, "boiloffRate") / 360000.0f;

                        // let it boil off
                        fuel.Consume(amount * (1.0 - Math.Pow(1.0 - boiloff_rate, elapsed_s)), ResourceBroker.Boiloff);
                    }
                }
            }

            // apply EC consumption
            ec.Consume(total_cost * elapsed_s, ResourceBroker.Cryotank);
        }
        public void FixedUpdate()
        {
            // do nothing in the editor
            if (Lib.IsEditor())
            {
                return;
            }

            // if enabled and not ready for harvest
            if (active && growth < 0.99)
            {
                // get vessel info from the cache
                // - if the vessel is not valid (eg: flagged as debris) then solar flux will be 0 and landed false (but that's okay)
                VesselData vd = vessel.KerbalismData();

                // get resource cache
                VesselResources resources = ResourceCache.Get(vessel);
                ResourceInfo    ec        = resources.GetResource(vessel, "ElectricCharge");

                // deal with corner cases when greenhouse is assembled using KIS
                if (double.IsNaN(growth) || double.IsInfinity(growth))
                {
                    growth = 0.0;
                }

                // calculate natural and artificial lighting
                natural    = vd.EnvSolarFluxTotal;
                artificial = Math.Max(light_tolerance - natural, 0.0);

                // consume EC for the lamps, scaled by artificial light intensity
                if (artificial > double.Epsilon)
                {
                    ec.Consume(ec_rate * (artificial / light_tolerance) * Kerbalism.elapsed_s, ResourceBroker.Greenhouse);
                }

                // reset artificial lighting if there is no ec left
                // - comparing against amount in previous simulation step
                if (ec.Amount <= double.Epsilon)
                {
                    artificial = 0.0;
                }

                // execute recipe
                ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.Greenhouse);
                foreach (ModuleResource input in resHandler.inputResources)
                {
                    // WasteAtmosphere is primary combined input
                    if (WACO2 && input.name == "WasteAtmosphere")
                    {
                        recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * Kerbalism.elapsed_s, "CarbonDioxide");
                    }
                    // CarbonDioxide is secondary combined input
                    else if (WACO2 && input.name == "CarbonDioxide")
                    {
                        recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * Kerbalism.elapsed_s, "");
                    }
                    // if atmosphere is breathable disable WasteAtmosphere / CO2
                    else if (!WACO2 && (input.name == "CarbonDioxide" || input.name == "WasteAtmosphere"))
                    {
                        recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate, "");
                    }
                    else
                    {
                        recipe.AddInput(input.name, input.rate * Kerbalism.elapsed_s);
                    }
                }
                foreach (ModuleResource output in resHandler.outputResources)
                {
                    // if atmosphere is breathable disable Oxygen
                    if (output.name == "Oxygen")
                    {
                        recipe.AddOutput(output.name, vd.EnvBreathable ? 0.0 : output.rate * Kerbalism.elapsed_s, true);
                    }
                    else
                    {
                        recipe.AddOutput(output.name, output.rate * Kerbalism.elapsed_s, true);
                    }
                }
                resources.AddRecipe(recipe);

                // determine environment conditions
                bool lighting  = natural + artificial >= light_tolerance;
                bool pressure  = pressure_tolerance <= double.Epsilon || vd.Pressure >= pressure_tolerance;
                bool radiation = radiation_tolerance <= double.Epsilon || (1.0 - vd.Shielding) * vd.EnvHabitatRadiation < radiation_tolerance;

                // determine input resources conditions
                // - comparing against amounts in previous simulation step
                bool   inputs      = true;
                string missing_res = string.Empty;
                bool   dis_WACO2   = false;
                foreach (ModuleResource input in resHandler.inputResources)
                {
                    // combine WasteAtmosphere and CO2 if both exist
                    if (input.name == "WasteAtmosphere" || input.name == "CarbonDioxide")
                    {
                        if (dis_WACO2 || vd.EnvBreathable)
                        {
                            continue;                                                           // skip if already checked or atmosphere is breathable
                        }
                        if (WACO2)
                        {
                            if (resources.GetResource(vessel, "WasteAtmosphere").Amount <= double.Epsilon && resources.GetResource(vessel, "CarbonDioxide").Amount <= double.Epsilon)
                            {
                                inputs      = false;
                                missing_res = "CarbonDioxide";
                                break;
                            }
                            dis_WACO2 = true;
                            continue;
                        }
                    }
                    if (resources.GetResource(vessel, input.name).Amount <= double.Epsilon)
                    {
                        inputs      = false;
                        missing_res = input.name;
                        break;
                    }
                }

                // if growing
                if (lighting && pressure && radiation && inputs)
                {
                    // increase growth
                    growth += crop_rate * Kerbalism.elapsed_s;
                    growth  = Math.Min(growth, 1.0);

                    // notify the user when crop can be harvested
                    if (growth >= 0.99)
                    {
                        Message.Post(Local.harvestedready_msg.Format("<b>" + vessel.vesselName + "</b>"));                        //Lib.BuildString("On <<1>> the crop is ready to be harvested")
                        growth = 1.0;
                    }
                }

                // update time-to-harvest
                tta = (1.0 - growth) / crop_rate;

                // update issues
                issue =
                    !inputs?Lib.BuildString(Local.Greenhouse_resoucesmissing.Format(missing_res)) //"missing <<1>>"
                        : !lighting ? Local.Greenhouse_issue1                                     //"insufficient lighting"
                                : !pressure ? Local.Greenhouse_issue2                             //"insufficient pressure"
                                : !radiation ? Local.Greenhouse_issue3                            //"excessive radiation"
                                : string.Empty;
            }
        }
Example #19
0
        public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, KerbalismScansat kerbalismScansat,
                                            Part part_prefab, VesselData vd, ResourceInfo ec, double elapsed_s)
        {
            List <ProtoPartModuleSnapshot> scanners = Cache.VesselObjectsCache <List <ProtoPartModuleSnapshot> >(vessel, "scansat_" + p.flightID);

            if (scanners == null)
            {
                scanners = Lib.FindModules(p, "SCANsat");
                if (scanners.Count == 0)
                {
                    scanners = Lib.FindModules(p, "ModuleSCANresourceScanner");
                }
                Cache.SetVesselObjectsCache(vessel, "scansat_" + p.flightID, scanners);
            }

            if (scanners.Count == 0)
            {
                return;
            }
            var scanner = scanners[0];

            bool is_scanning = Lib.Proto.GetBool(scanner, "scanning");

            if (is_scanning && kerbalismScansat.ec_rate > double.Epsilon)
            {
                ec.Consume(kerbalismScansat.ec_rate * elapsed_s, ResourceBroker.Scanner);
            }

            if (!Features.Science)
            {
                if (is_scanning && ec.Amount < double.Epsilon)
                {
                    SCANsat.StopScanner(vessel, scanner, part_prefab);
                    is_scanning = false;

                    // remember disabled scanner
                    vd.scansat_id.Add(p.flightID);

                    // give the user some feedback
                    if (vd.cfg_ec)
                    {
                        Message.Post(Local.Scansat_sensordisabled.Format("<b>" + vessel.vesselName + "</b>"));                           //Lib.BuildString("SCANsat sensor was disabled on <<1>>)
                    }
                }
                else if (vd.scansat_id.Contains(p.flightID))
                {
                    // if there is enough ec
                    // note: comparing against amount in previous simulation step
                    // re-enable at 25% EC
                    if (ec.Level > 0.25)
                    {
                        // re-enable the scanner
                        SCANsat.ResumeScanner(vessel, m, part_prefab);
                        is_scanning = true;

                        // give the user some feedback
                        if (vd.cfg_ec)
                        {
                            Message.Post(Local.Scansat_sensorresumed.Format("<b>" + vessel.vesselName + "</b>"));                               //Lib.BuildString("SCANsat sensor resumed operations on <<1>>)
                        }
                    }
                }

                // forget active scanners
                if (is_scanning)
                {
                    vd.scansat_id.Remove(p.flightID);
                }

                return;
            }             // if(!Feature.Science)

            string body_name     = Lib.Proto.GetString(m, "body_name");
            int    sensorType    = (int)Lib.Proto.GetUInt(m, "sensorType");
            double body_coverage = Lib.Proto.GetDouble(m, "body_coverage");
            double warp_buffer   = Lib.Proto.GetDouble(m, "warp_buffer");

            double new_coverage = SCANsat.Coverage(sensorType, vessel.mainBody);

            if (body_name == vessel.mainBody.name && new_coverage < body_coverage)
            {
                // SCANsat sometimes reports a coverage of 0, which is wrong
                new_coverage = body_coverage;
            }

            if (vessel.mainBody.name != body_name)
            {
                body_name     = vessel.mainBody.name;
                body_coverage = new_coverage;
            }
            else
            {
                double coverage_delta = new_coverage - body_coverage;
                body_coverage = new_coverage;

                if (is_scanning)
                {
                    ExperimentInfo expInfo = ScienceDB.GetExperimentInfo(kerbalismScansat.experimentType);
                    SubjectData    subject = ScienceDB.GetSubjectData(expInfo, vd.VesselSituations.GetExperimentSituation(expInfo));
                    if (subject == null)
                    {
                        return;
                    }

                    double size = expInfo.DataSize * coverage_delta / 100.0;                     // coverage is 0-100%
                    size += warp_buffer;

                    if (size > double.Epsilon)
                    {
                        // store what we can
                        foreach (var d in Drive.GetDrives(vd))
                        {
                            var available = d.FileCapacityAvailable();
                            var chunk     = Math.Min(size, available);
                            if (!d.Record_file(subject, chunk, true))
                            {
                                break;
                            }
                            size -= chunk;

                            if (size < double.Epsilon)
                            {
                                break;
                            }
                        }
                    }

                    if (size > double.Epsilon)
                    {
                        // we filled all drives up to the brim but were unable to store everything
                        if (warp_buffer < double.Epsilon)
                        {
                            // warp buffer is empty, so lets store the rest there
                            warp_buffer = size;
                            size        = 0;
                        }
                        else
                        {
                            // warp buffer not empty. that's ok if we didn't get new data
                            if (coverage_delta < double.Epsilon)
                            {
                                size = 0;
                            }
                            // else we're scanning too fast. stop.
                        }
                    }

                    // we filled all drives up to the brim but were unable to store everything
                    // cancel scanning and annoy the user
                    if (size > double.Epsilon || ec.Amount < double.Epsilon)
                    {
                        warp_buffer = 0;
                        SCANsat.StopScanner(vessel, scanner, part_prefab);
                        vd.scansat_id.Add(p.flightID);
                        if (vd.cfg_ec)
                        {
                            Message.Post(Local.Scansat_sensordisabled.Format("<b>" + vessel.vesselName + "</b>"));                               //Lib.BuildString("SCANsat sensor was disabled on <<1>>)
                        }
                    }
                }
                else if (vd.scansat_id.Contains(p.flightID))
                {
                    if (ec.Level >= 0.25 && (vd.DrivesFreeSpace / vd.DrivesCapacity > 0.9))
                    {
                        SCANsat.ResumeScanner(vessel, scanner, part_prefab);
                        vd.scansat_id.Remove(p.flightID);
                        if (vd.cfg_ec)
                        {
                            Message.Post(Local.Scansat_sensorresumed.Format("<b>" + vessel.vesselName + "</b>"));                               //Lib.BuildString("SCANsat sensor resumed operations on <<1>>)
                        }
                    }
                }
            }

            Lib.Proto.Set(m, "warp_buffer", warp_buffer);
            Lib.Proto.Set(m, "body_coverage", body_coverage);
            Lib.Proto.Set(m, "body_name", body_name);
        }
        public static void BackgroundUpdate(Vessel v, ProtoPartModuleSnapshot m, Greenhouse g,
                                            VesselData vd, VesselResources resources, double elapsed_s)
        {
            // get protomodule data
            bool   active = Lib.Proto.GetBool(m, "active");
            double growth = Lib.Proto.GetDouble(m, "growth");

            // if enabled and not ready for harvest
            if (active && growth < 0.99)
            {
                // get resource handler
                ResourceInfo ec = resources.GetResource(v, "ElectricCharge");

                // calculate natural and artificial lighting
                double natural    = vd.EnvSolarFluxTotal;
                double artificial = Math.Max(g.light_tolerance - natural, 0.0);

                // consume EC for the lamps, scaled by artificial light intensity
                if (artificial > double.Epsilon)
                {
                    ec.Consume(g.ec_rate * (artificial / g.light_tolerance) * elapsed_s, ResourceBroker.Greenhouse);
                }

                // reset artificial lighting if there is no ec left
                // note: comparing against amount in previous simulation step
                if (ec.Amount <= double.Epsilon)
                {
                    artificial = 0.0;
                }

                // execute recipe
                ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.Greenhouse);
                foreach (ModuleResource input in g.resHandler.inputResources)                 //recipe.Input(input.name, input.rate * elapsed_s);
                {
                    // WasteAtmosphere is primary combined input
                    if (g.WACO2 && input.name == "WasteAtmosphere")
                    {
                        recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * elapsed_s, "CarbonDioxide");
                    }
                    // CarbonDioxide is secondary combined input
                    else if (g.WACO2 && input.name == "CarbonDioxide")
                    {
                        recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * elapsed_s, "");
                    }
                    // if atmosphere is breathable disable WasteAtmosphere / CO2
                    else if (!g.WACO2 && (input.name == "CarbonDioxide" || input.name == "WasteAtmosphere"))
                    {
                        recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate, "");
                    }
                    else
                    {
                        recipe.AddInput(input.name, input.rate * elapsed_s);
                    }
                }
                foreach (ModuleResource output in g.resHandler.outputResources)
                {
                    // if atmosphere is breathable disable Oxygen
                    if (output.name == "Oxygen")
                    {
                        recipe.AddOutput(output.name, vd.EnvBreathable ? 0.0 : output.rate * elapsed_s, true);
                    }
                    else
                    {
                        recipe.AddOutput(output.name, output.rate * elapsed_s, true);
                    }
                }
                resources.AddRecipe(recipe);

                // determine environment conditions
                bool lighting  = natural + artificial >= g.light_tolerance;
                bool pressure  = g.pressure_tolerance <= 0 || vd.Pressure >= g.pressure_tolerance;
                bool radiation = g.radiation_tolerance <= 0 || vd.EnvRadiation * (1.0 - vd.Shielding) < g.radiation_tolerance;

                // determine inputs conditions
                // note: comparing against amounts in previous simulation step
                bool   inputs      = true;
                string missing_res = string.Empty;
                bool   dis_WACO2   = false;
                foreach (ModuleResource input in g.resHandler.inputResources)
                {
                    // combine WasteAtmosphere and CO2 if both exist
                    if (input.name == "WasteAtmosphere" || input.name == "CarbonDioxide")
                    {
                        if (dis_WACO2 || vd.EnvBreathable)
                        {
                            continue;                                                           // skip if already checked or atmosphere is breathable
                        }
                        if (g.WACO2)
                        {
                            if (resources.GetResource(v, "WasteAtmosphere").Amount <= double.Epsilon && resources.GetResource(v, "CarbonDioxide").Amount <= double.Epsilon)
                            {
                                inputs      = false;
                                missing_res = "CarbonDioxide";
                                break;
                            }
                            dis_WACO2 = true;
                            continue;
                        }
                    }
                    if (resources.GetResource(v, input.name).Amount <= double.Epsilon)
                    {
                        inputs      = false;
                        missing_res = input.name;
                        break;
                    }
                }

                // if growing
                if (lighting && pressure && radiation && inputs)
                {
                    // increase growth
                    growth += g.crop_rate * elapsed_s;
                    growth  = Math.Min(growth, 1.0);

                    // notify the user when crop can be harvested
                    if (growth >= 0.99)
                    {
                        Message.Post(Local.harvestedready_msg.Format("<b>" + v.vesselName + "</b>"));                        //Lib.BuildString("On <<1>> the crop is ready to be harvested")
                        growth = 1.0;
                    }
                }

                // update time-to-harvest
                double tta = (1.0 - growth) / g.crop_rate;

                // update issues
                string issue =
                    !inputs?Lib.BuildString(Local.Greenhouse_resoucesmissing.Format(missing_res)) //"missing ", missing_res
                        : !lighting ? Local.Greenhouse_issue1                                     //"insufficient lighting"
                                : !pressure ? Local.Greenhouse_issue2                             //"insufficient pressure"
                                : !radiation ? Local.Greenhouse_issue3                            //"excessive radiation"
                                : string.Empty;

                // update protomodule data
                Lib.Proto.Set(m, "natural", natural);
                Lib.Proto.Set(m, "artificial", artificial);
                Lib.Proto.Set(m, "tta", tta);
                Lib.Proto.Set(m, "issue", issue);
                Lib.Proto.Set(m, "growth", growth);
            }
        }
Example #21
0
        /// <summary>
        /// Execute the recipe and record deferred consumption/production for inputs/ouputs.
        /// This need to be called multiple times until left &lt;= 0.0 for complete execution of the recipe.
        /// return true if recipe execution is completed, false otherwise
        /// </summary>
        private bool ExecuteRecipeStep(Vessel v, VesselResources resources)
        {
            // determine worst input ratio
            // - pure input recipes can just underflow
            double worst_input = left;

            if (outputs.Count > 0)
            {
                for (int i = 0; i < inputs.Count; ++i)
                {
                    Entry        e   = inputs[i];
                    ResourceInfo res = resources.GetResource(v, e.name);

                    // handle combined inputs
                    if (e.combined != null)
                    {
                        // is combined resource the primary
                        if (e.combined != "")
                        {
                            Entry        sec_e     = inputs.Find(x => x.name.Contains(e.combined));
                            ResourceInfo sec       = resources.GetResource(v, sec_e.name);
                            double       pri_worst = Lib.Clamp((res.Amount + res.Deferred) * e.inv_quantity, 0.0, worst_input);
                            if (pri_worst > 0.0)
                            {
                                worst_input = pri_worst;
                            }
                            else
                            {
                                worst_input = Lib.Clamp((sec.Amount + sec.Deferred) * sec_e.inv_quantity, 0.0, worst_input);
                            }
                        }
                    }
                    else
                    {
                        worst_input = Lib.Clamp((res.Amount + res.Deferred) * e.inv_quantity, 0.0, worst_input);
                    }
                }
            }

            // determine worst output ratio
            // - pure output recipes can just overflow
            double worst_output = left;

            if (inputs.Count > 0)
            {
                for (int i = 0; i < outputs.Count; ++i)
                {
                    Entry e = outputs[i];
                    if (!e.dump)                     // ignore outputs that can dump overboard
                    {
                        ResourceInfo res = resources.GetResource(v, e.name);
                        worst_output = Lib.Clamp((res.Capacity - (res.Amount + res.Deferred)) * e.inv_quantity, 0.0, worst_output);
                    }
                }
            }

            // determine worst-io
            double worst_io = Math.Min(worst_input, worst_output);

            // consume inputs
            for (int i = 0; i < inputs.Count; ++i)
            {
                Entry        e   = inputs[i];
                ResourceInfo res = resources.GetResource(v, e.name);
                // handle combined inputs
                if (e.combined != null)
                {
                    // is combined resource the primary
                    if (e.combined != "")
                    {
                        Entry        sec_e = inputs.Find(x => x.name.Contains(e.combined));
                        ResourceInfo sec   = resources.GetResource(v, sec_e.name);
                        double       need  = (e.quantity * worst_io) + (sec_e.quantity * worst_io);
                        // do we have enough primary to satisfy needs, if so don't consume secondary
                        if (res.Amount + res.Deferred >= need)
                        {
                            resources.Consume(v, e.name, need, name);
                        }
                        // consume primary if any available and secondary
                        else
                        {
                            need -= res.Amount + res.Deferred;
                            res.Consume(res.Amount + res.Deferred, name);
                            sec.Consume(need, name);
                        }
                    }
                }
                else
                {
                    res.Consume(e.quantity * worst_io, name);
                }
            }

            // produce outputs
            for (int i = 0; i < outputs.Count; ++i)
            {
                Entry        e   = outputs[i];
                ResourceInfo res = resources.GetResource(v, e.name);
                res.Produce(e.quantity * worst_io, name);
            }

            // produce cures
            for (int i = 0; i < cures.Count; ++i)
            {
                Entry           entry       = cures[i];
                List <RuleData> curingRules = new List <RuleData>();
                foreach (ProtoCrewMember crew in v.GetVesselCrew())
                {
                    KerbalData kd = DB.Kerbal(crew.name);
                    if (kd.sickbay.IndexOf(entry.combined + ",", StringComparison.Ordinal) >= 0)
                    {
                        curingRules.Add(kd.Rule(entry.name));
                    }
                }

                foreach (RuleData rd in curingRules)
                {
                    rd.problem -= entry.quantity * worst_io / curingRules.Count;
                    rd.problem  = Math.Max(rd.problem, 0);
                }
            }

            // update amount left to execute
            left -= worst_io;

            // the recipe was executed, at least partially
            return(worst_io > double.Epsilon);
        }