Beispiel #1
0
 static void ProcessLight(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleLight light, resource_info ec, double elapsed_s)
 {
     if (light.useResources && Lib.Proto.GetBool(m, "isOn"))
     {
         ec.Consume(light.resourceAmount * elapsed_s);
     }
 }
Beispiel #2
0
        // execute the recipe
        public void Execute(Vessel v, vessel_resources resources)
        {
            // determine worst input ratio
            double worst_input = 1.0;

            foreach (var pair in inputs)
            {
                if (pair.Value > double.Epsilon) //< avoid division by zero
                {
                    resource_info res = resources.Info(v, pair.Key);
                    worst_input = Math.Min(worst_input, Math.Max(0.0, res.amount + res.deferred) / pair.Value);
                }
            }

            // consume inputs
            foreach (var pair in inputs)
            {
                resource_info res = resources.Info(v, pair.Key);
                res.Consume(pair.Value * worst_input);
            }

            // produce outputs
            foreach (var pair in outputs)
            {
                resource_info res = resources.Info(v, pair.Key);
                res.Produce(pair.Value * worst_input);
            }
        }
Beispiel #3
0
 public static void BackgroundUpdate(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Deploy deploy, resource_info ec, double elapsed_s)
 {
     if (deploy.isConsuming)
     {
         ec.Consume(deploy.extra_Cost * elapsed_s);
     }
 }
Beispiel #4
0
  // implement gravity ring mechanics for unloaded vessels
  public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, GravityRing ring, vessel_resources resources, double elapsed_s)
  {
    // get protomodule data
    float speed = Lib.Proto.GetFloat(m, "speed");

    // get resource handler
    resource_info ec = resources.Info(vessel, "ElectricCharge");

    // consume ec
    ec.Consume(ring.ec_rate * speed * elapsed_s * Reliability.Penalty(p, "GravityRing", 2.0));

    // reset speed if there isn't enough ec
    // note: comparing against amount in previous simulation step
    if (ec.amount <= double.Epsilon)
    {
      speed = 0.0f;
      Lib.Proto.Set(m, "speed", speed);
    }

    // set entertainment
    // note: entertainmnent is only recomputed for loaded vessels,
    // so changing rate here does nothing until vessel is reloaded
    double rate = 1.0 + (ring.entertainment_rate - 1.0) * speed;
    Lib.Proto.Set(m, "rate", rate);
  }
Beispiel #5
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);
            }
        }
Beispiel #6
0
        public static void BackgroundUpdate(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Laboratory lab, resource_info ec, double elapsed_s)
        {
            // if enabled
            if (Lib.Proto.GetBool(m, "running"))
            {
                // if a researcher is not required, or the researcher is present
                CrewSpecs researcher_cs = new CrewSpecs(lab.researcher);
                if (!researcher_cs || researcher_cs.check(p.protoModuleCrew))
                {
                    // get sample to analyze
                    string sample_filename = next_sample(v);

                    // if there is a sample to analyze
                    if (sample_filename.Length > 0)
                    {
                        // consume EC
                        ec.Consume(lab.ec_rate * elapsed_s);

                        // if there was ec
                        // - comparing against amount in previous simulation step
                        if (ec.amount > double.Epsilon)
                        {
                            // analyze the sample
                            analyze(v, sample_filename, lab.analysis_rate * elapsed_s);
                        }
                    }
                }
            }
        }
Beispiel #7
0
 public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, GravityRing ring, resource_info 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);
     }
 }
Beispiel #8
0
 public static void BackgroundUpdate(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Emitter emitter, resource_info ec, double elapsed_s)
 {
     // if enabled, and EC is required
     if (Lib.Proto.GetBool(m, "running") && emitter.ec_rate > double.Epsilon)
     {
         // consume EC
         ec.Consume(emitter.ec_rate * elapsed_s);
     }
 }
Beispiel #9
0
        static void ProcessLab(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleScienceConverter lab, resource_info 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);
            }
        }
Beispiel #10
0
  // implement gravity ring mechanics
  public void FixedUpdate()
  {
    // reset speed when not open
    if (!opened) speed = 0.0f;

    // hide the tweakable if not open
    this.Fields["speed"].guiActive = opened;
    this.Fields["speed"].guiActiveEditor = opened;

    // manage animation
    if (rotate != null)
    {
      // set rotating animation speed
      rotate[rotate_animation].speed = speed;

      // if its open but no animations are playing, start rotating
      if (opened)
      {
        bool playing = false;
        foreach(var anim in this.part.FindModelAnimators())
        {
          playing |= anim.isPlaying;
        }
        if (!playing) rotate.Play(rotate_animation);
      }
    }

    // do nothing else in the editor
    if (HighLogic.LoadedSceneIsEditor) return;

    // get vessel info from the cache
    vessel_info vi = Cache.VesselInfo(vessel);

    // do nothing if vessel is invalid
    if (!vi.is_valid) return;

    // get resource cache
    vessel_resources resources = ResourceCache.Get(vessel);

    // get resource handler
    resource_info ec = resources.Info(vessel, "ElectricCharge");

    // consume ec
    ec.Consume(ec_rate * speed * Kerbalism.elapsed_s * vi.time_dilation);

    // reset speed if there isn't enough ec
    // note: comparing against amount in previous simulation step
    if (ec.amount <= double.Epsilon) speed = 0.0f;

    // set entertainment
    rate = 1.0 + (entertainment_rate - 1.0) * speed;
  }
Beispiel #11
0
        public static void BackgroundUpdate(Vessel v, ProtoPartModuleSnapshot m, Experiment exp, resource_info ec, double elapsed_s)
        {
            // if experiment is active
            if (Lib.Proto.GetBool(m, "recording"))
            {
                // detect conditions
                // - comparing against amount in previous step
                bool   has_ec       = ec.amount > double.Epsilon;
                bool   has_operator = new CrewSpecs(exp.crew).check(v);
                string sit          = Science.situation(v, exp.situations);

                // deduce issues
                string issue = string.Empty;
                if (sit.Length == 0)
                {
                    issue = "invalid situation";
                }
                else if (!has_operator)
                {
                    issue = "no operator";
                }
                else if (!has_ec)
                {
                    issue = "missing <b>EC</b>";
                }
                Lib.Proto.Set(m, "issue", issue);

                // if there are no issues
                if (issue.Length == 0)
                {
                    // generate subject id
                    string subject_id = Science.generate_subject(exp.experiment, v.mainBody, sit, Science.biome(v, sit), Science.multiplier(v, sit));

                    // record in drive
                    if (exp.transmissible)
                    {
                        DB.Vessel(v).drive.record_file(subject_id, exp.data_rate * elapsed_s);
                    }
                    else
                    {
                        DB.Vessel(v).drive.record_sample(subject_id, exp.data_rate * elapsed_s);
                    }

                    // consume ec
                    ec.Consume(exp.ec_rate * elapsed_s);
                }
            }
        }
Beispiel #12
0
        static void ProcessCryoTank(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule simple_boiloff, vessel_resources resources, double elapsed_s)
        {
            // note: cryotank module already does a post-facto simulation of background boiling, and we could use that for the boiling
            // however, it also does simulate the ec consumption that way, so we have to disable the post-facto simulation

            IList fuels = Lib.PrivateField <IList>(simple_boiloff.GetType(), simple_boiloff, "fuels");

            foreach (object cryoFuel in fuels)
            {
                string fuel_name            = Lib.PrivateField <string> (cryoFuel.GetType(), cryoFuel, "fuelName");
                float  boiloff_rate_percent = Lib.PrivateField <float> (cryoFuel.GetType(), cryoFuel, "boiloffRate");
                // get resource handlers
                resource_info ec   = resources.Info(v, "ElectricCharge");
                resource_info fuel = resources.Info(v, fuel_name);

                // if there is some fuel
                // note: comparing against amount in previous simulation step
                if (fuel.amount > double.Epsilon)
                {
                    // get capacity in the part
                    double capacity = p.resources.Find(k => k.resourceName == fuel_name).maxAmount;

                    // if cooling is enabled and there was enough ec
                    // note: comparing against amount in previous simulation step
                    if (Lib.Proto.GetBool(m, "CoolingEnabled") && ec.amount > double.Epsilon)
                    {
                        // get cooling ec cost per 1000 units of fuel, per-second
                        double cooling_cost = Lib.ReflectionValue <float>(simple_boiloff, "CoolingCost");

                        // consume ec
                        ec.Consume(cooling_cost * capacity * 0.001 * elapsed_s);
                    }
                    // if there wasn't ec, or if cooling is disabled
                    else
                    {
                        // get boiloff rate in proportion to fuel amount, per-second
                        double boiloff_rate = boiloff_rate_percent * 0.00000277777;

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

            // disable post-facto simulation
            Lib.Proto.Set(m, "LastUpdateTime", v.missionTime);
        }
Beispiel #13
0
  public static void update(Vessel v)
  {
    // get kerbal data from db
    kerbal_data kd = KerbalData(v);

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

    // show/hide helmet, play nice with KIS
    if (!Kerbalism.detected_mods.KIS)
    {
      SetHelmet(kerbal, kd.has_helmet);
    }
    // synchronize has_helmet state with KIS (for the headlights)
    else
    {
      kd.has_helmet = HasHelmet(kerbal);
    }

    // get resource handler
    resource_info ec = ResourceCache.Info(v, "ElectricCharge");

    // consume EC for the headlamp
    if (kd.has_helmet && kerbal.lampOn) ec.Consume(Settings.HeadlightCost * Kerbalism.elapsed_s); //< ignore time dilation

    // force the headlamp lights on/off depending on ec amount left and if it has an helmet
    // synchronize helmet flares with headlamp state
    // support case when there is no ec rule (or no profile at all)
    bool b = kd.has_helmet && kerbal.lampOn && (ec.amount > double.Epsilon || ec.capacity <= double.Epsilon);
    SetHeadlamp(kerbal, b);
    SetFlares(kerbal, b);

    // if dead
    if (kd.eva_dead)
    {
      // enforce freezed state
      SetFreezed(kerbal);

      // remove plant flag action
      kerbal.flagItems = 0;

      // remove experiment actions (game engine keeps readding them)
      RemoveExperiments(kerbal);
    }
  }
Beispiel #14
0
        public void FixedUpdate()
        {
            // do nothing in the editor
            if (Lib.IsEditor())
            {
                return;
            }

            // if enabled, and there is ec consumption
            if (running && ec_rate > double.Epsilon)
            {
                // get resource cache
                resource_info ec = ResourceCache.Info(vessel, "ElectricCharge");

                // consume EC
                ec.Consume(ec_rate * Kerbalism.elapsed_s);
            }
        }
Beispiel #15
0
        public void FixedUpdate()
        {
            // do nothing in the editor
            if (Lib.IsEditor())
            {
                return;
            }

            // if the module is either non-deployable or deployed
            if (deploy.Length == 0 || deployed)
            {
                // get resource handler
                resource_info ec = ResourceCache.Info(vessel, "ElectricCharge");

                // consume ec
                ec.Consume(ec_rate * Kerbalism.elapsed_s);
            }
        }
Beispiel #16
0
  public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Emitter emitter, resource_info ec, double elapsed_s)
  {
    // if there is enough EC
    // note: comparing against amount in previous simulation step
    if (ec.amount > double.Epsilon)
    {
      // get intensity
      double intensity = Lib.Proto.GetDouble(m, "intensity");

      // consume EC
      ec.Consume(emitter.ec_rate * intensity * elapsed_s * Reliability.Penalty(p, "Emitter", 2.0));
    }
    // else disable it
    else
    {
      Lib.Proto.Set(m, "intensity", 0.0);
    }
  }
Beispiel #17
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
    resource_info ec = ResourceCache.Info(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);
    }

    // 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;
    }
  }
Beispiel #18
0
  public void FixedUpdate()
  {
    // in any scene: update the RMB ui
    Status = Lib.HumanReadableRadiationRate(Math.Abs(radiation) * intensity);

    // do nothing else in the editor
    if (HighLogic.LoadedSceneIsEditor) return;

    // if there is ec consumption
    if (ec_rate > double.Epsilon)
    {
      // get vessel info from the cache
      vessel_info vi = Cache.VesselInfo(vessel);

      // do nothing if vessel is invalid
      if (!vi.is_valid) return;

      // get resource cache
      resource_info ec = ResourceCache.Info(vessel, "ElectricCharge");

      // get elapsed time
      double elapsed_s = Kerbalism.elapsed_s * vi.time_dilation;

      // if there is enough EC
      // note: comparing against amount in previous simulation step
      if (ec.amount > double.Epsilon)
      {
        // consume EC
        ec.Consume(ec_rate * intensity * elapsed_s);
      }
      // else disable it
      else
      {
        intensity = 0.0f;
      }
    }
  }
Beispiel #19
0
        public void FixedUpdate()
        {
            // in flight
            if (Lib.IsFlight())
            {
                // if we are transmitting using the stock system
                if (stream.transmitting())
                {
                    // get ec resource handler
                    resource_info ec = ResourceCache.Info(vessel, "ElectricCharge");

                    // if we are still linked, and there is ec left
                    if (CanTransmit() && ec.amount > double.Epsilon)
                    {
                        // compression factor
                        // - used to avoid making the user wait too much for transmissions that
                        //   don't happen in background, while keeping transmission rates realistic
                        const double compression = 16.0;

                        // transmit using the data stream
                        stream.update(DataRate * Kerbalism.elapsed_s * compression, vessel);

                        // consume ec
                        ec.Consume(DataResourceCost * Kerbalism.elapsed_s);
                    }
                    else
                    {
                        // abort transmission, return data to the vessel
                        stream.abort(vessel);

                        // inform the user
                        ScreenMessages.PostScreenMessage("Transmission aborted", 5.0f, ScreenMessageStyle.UPPER_LEFT);
                    }
                }
            }
        }
Beispiel #20
0
        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)
                vessel_info vi = Cache.VesselInfo(vessel);

                // get resource cache
                vessel_resources resources = ResourceCache.Get(vessel);
                resource_info    ec        = resources.Info(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    = vi.solar_flux;
                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);
                }

                // 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
                resource_recipe recipe = new resource_recipe();
                foreach (ModuleResource input in resHandler.inputResources)
                {
                    recipe.Input(input.name, input.rate * Kerbalism.elapsed_s);
                }
                foreach (ModuleResource output in resHandler.outputResources)
                {
                    recipe.Output(output.name, output.rate * Kerbalism.elapsed_s, true);
                }
                resources.Transform(recipe);

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

                // determine input resources conditions
                // - comparing against amounts in previous simulation step
                bool   inputs      = true;
                string missing_res = string.Empty;
                foreach (ModuleResource input in resHandler.inputResources)
                {
                    if (resources.Info(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(Lib.BuildString("On <b>", vessel.vesselName, "</b> 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("missing ", missing_res)
                        : !lighting  ? "insufficient lighting"
      : !pressure  ? "insufficient pressure"
      : !radiation ? "excessive radiation"
      : string.Empty;
            }
        }
Beispiel #21
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 info
            Rule          supply = supply_rules.Count > 0 ? supply_rules[Lib.RandomInt(supply_rules.Count)] : null;
            resource_info res    = supply != null?ResourceCache.Info(v, supply.resource_name) : null;

            // compile list of events with condition satisfied
            List <KerbalBreakdown> events = new List <KerbalBreakdown>();

            events.Add(KerbalBreakdown.mumbling); //< do nothing, here so there is always something that can happen
            if (Lib.CrewCount(v) > 1)
            {
                events.Add(KerbalBreakdown.argument);               //< do nothing, add some variation to messages
            }
            if (Lib.HasData(v))
            {
                events.Add(KerbalBreakdown.fat_finger);
            }
            if (Reliability.CanMalfunction(v))
            {
                events.Add(KerbalBreakdown.rage);
            }
            if (supply != null && res.amount > double.Epsilon)
            {
                events.Add(KerbalBreakdown.depressed);
                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.argument:    text = "$ON_VESSEL$KERBAL had an argument with the rest of the crew"; subtext = "Morale is degenerating at an alarming rate"; 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.depressed:   text = "$ON_VESSEL$KERBAL is not respecting the rationing guidelines"; subtext = supply.resource_name + " has been lost"; break;

            case KerbalBreakdown.wrong_valve: text = "$ON_VESSEL$KERBAL opened the wrong valve"; subtext = supply.resource_name + " 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.argument: break; // do nothing

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

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

            case KerbalBreakdown.depressed:
            case KerbalBreakdown.wrong_valve: res.Consume(res.amount * res_penalty); break;
            }

            // remove reputation
            if (HighLogic.CurrentGame.Mode == Game.Modes.CAREER)
            {
                Reputation.Instance.AddReputation(-Settings.BreakdownReputationPenalty, TransactionReasons.Any);
            }
        }
Beispiel #22
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
                    string sample_filename = next_sample(vessel);

                    // if there is a sample to analyze
                    if (sample_filename.Length > 0)
                    {
                        // consume EC
                        resource_info ec = ResourceCache.Info(vessel, "ElectricCharge");
                        ec.Consume(ec_rate * Kerbalism.elapsed_s);

                        // if there was ec
                        // - comparing against amount in previous simulation step
                        if (ec.amount > double.Epsilon)
                        {
                            // analyze the sample
                            analyze(vessel, sample_filename, analysis_rate * Kerbalism.elapsed_s);

                            // update status
                            status = Science.experiment(sample_filename).name;
                        }
                        // if there was no ec
                        else
                        {
                            // update status
                            status = "<color=yellow>no electric charge</color>";
                        }
                    }
                    // if there is no sample to analyze
                    else
                    {
                        // update status
                        status = "no samples to analyze";
                    }
                }
                // if a researcher is required, but missing
                else
                {
                    // update status
                    status = Lib.BuildString("<color=yellow>", researcher_cs.warning(), "</color>");
                }
            }
            // if disabled
            else
            {
                // update status
                status = "disabled";
            }
        }
Beispiel #23
0
        public void Execute(Vessel v, vessel_info vi, vessel_resources resources, double elapsed_s)
        {
            // store list of crew to kill
            List <ProtoCrewMember> deferred_kills = new List <ProtoCrewMember>();

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

            // determine message variant
            uint variant = vi.temperature < Settings.SurvivalTemperature ? 0 : 1u;

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

            // 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);

                // if continuous
                double step;
                if (interval <= double.Epsilon)
                {
                    // influence consumption by elapsed time
                    step = elapsed_s;
                }
                // if interval-based
                else
                {
                    // accumulate time
                    rd.time_since += elapsed_s;

                    // determine number of steps
                    step = Math.Floor(rd.time_since / interval);

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

                    // remember if a meal is consumed/produced in this simulation step
                    res.meal_happened |= step > 0.99;
                    if (output.Length > 0)
                    {
                        ResourceCache.Info(v, output).meal_happened |= step > 0.99;
                    }
                }

                // if continuous, or if one or more intervals elapsed
                if (step > double.Epsilon)
                {
                    // if there is a resource specified
                    if (res != null && rate > double.Epsilon)
                    {
                        // determine amount of resource to consume
                        double required = rate                                    // consumption rate
                                          * k                                     // product of environment modifiers
                                          * step;                                 // seconds elapsed or number of steps

                        // if there is no output
                        if (output.Length == 0)
                        {
                            // simply consume (that is faster)
                            res.Consume(required);
                        }
                        // if there is an output and output_only is false
                        else if (!output_only)
                        {
                            // transform input into output resource
                            // - rules always dump excess overboard (because it is waste)
                            resource_recipe recipe = new resource_recipe();
                            recipe.Input(input, required);
                            recipe.Output(output, required * ratio, true);
                            resources.Transform(recipe);
                        }
                        // if output_only then do not consume input resource
                        else
                        {
                            // simply produce (that is faster)
                            resources.Produce(v, output, required);
                        }
                    }

                    // degenerate:
                    // - if the environment modifier is not telling to reset (by being zero)
                    // - if the input threshold is reached if used
                    // - if this rule is resource-less, or if there was not enough resource in the vessel
                    if (input_threshold >= double.Epsilon)
                    {
                        if (res.amount >= double.Epsilon && res.capacity >= double.Epsilon)
                        {
                            trigger = res.amount / res.capacity >= input_threshold;
                        }
                        else
                        {
                            trigger = false;
                        }
                    }
                    else
                    {
                        trigger = input.Length == 0 || res.amount <= double.Epsilon;
                    }

                    if (k > 0.0 && trigger)
                    {
                        rd.problem += degeneration                                   // degeneration rate per-second or per-interval
                                      * k                                            // product of environment modifiers
                                      * step                                         // seconds elapsed or by number of steps
                                      * Variance(c, variance);                       // kerbal-specific variance
                    }
                    // else slowly recover
                    else
                    {
                        rd.problem *= 1.0 / (1.0 + Math.Max(interval, 1.0) * step * 0.002);
                        rd.problem  = Math.Max(rd.problem, 0.0);
                    }
                }

                // kill kerbal if necessary
                if (rd.problem >= fatal_threshold)
                {
                    if (fatal_message.Length > 0)
                    {
                        Message.Post(breakdown ? Severity.breakdown : Severity.fatality, Lib.ExpandMsg(fatal_message, v, c, variant));
                    }

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

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

                        // make sure next danger messagen 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;
                }
            }

            // execute the deferred kills
            foreach (ProtoCrewMember c in deferred_kills)
            {
                Misc.Kill(v, c);
            }
        }
Beispiel #24
0
        public void FixedUpdate()
        {
            // do nothing in the editor
            if (Lib.IsEditor())
            {
                return;
            }

            // do nothing if vessel is invalid
            if (!Cache.VesselInfo(vessel).is_valid)
            {
                return;
            }

            // get ec handler
            resource_info ec = ResourceCache.Info(vessel, "ElectricCharge");

            // if experiment is active
            if (recording)
            {
                // detect conditions
                // - comparing against amount in previous step
                bool   has_ec       = ec.amount > double.Epsilon;
                bool   has_operator = operator_cs.check(vessel);
                string sit          = Science.situation(vessel, situations);

                // deduce issues
                issue = string.Empty;
                if (sit.Length == 0)
                {
                    issue = "invalid situation";
                }
                else if (!has_operator)
                {
                    issue = "no operator";
                }
                else if (!has_ec)
                {
                    issue = "missing <b>EC</b>";
                }

                // if there are no issues
                if (issue.Length == 0)
                {
                    // generate subject id
                    string subject_id = Science.generate_subject(experiment, vessel.mainBody, sit, Science.biome(vessel, sit), Science.multiplier(vessel, sit));

                    // record in drive
                    if (transmissible)
                    {
                        DB.Vessel(vessel).drive.record_file(subject_id, data_rate * Kerbalism.elapsed_s);
                    }
                    else
                    {
                        DB.Vessel(vessel).drive.record_sample(subject_id, data_rate * Kerbalism.elapsed_s);
                    }

                    // consume ec
                    ec.Consume(ec_rate * Kerbalism.elapsed_s);
                }
            }
        }
Beispiel #25
0
		public void FixedUpdate()
		{
			// do nothing in the editor
			if (Lib.IsEditor()) return;

			// if deployed
			if (deployed || (animBackwards && !deployed))
			{
				// if there is no ec
				if (ResourceCache.Info(vessel, "ElectricCharge").amount < 0.01)
				{
					// pause rotate animation
					// - safe to pause multiple times
					if (rotateIsTransform && rotate_transf.IsRotating() && !rotate_transf.IsStopping()) rotate_transf.Stop();
					else rotate_anim.pause();
				}
				// if there is enough ec instead and is not deploying
				else if (!deploy_anim.playing())
				{
					// resume rotate animation
					// - safe to resume multiple times
					if (rotateIsTransform && (!rotate_transf.IsRotating() || rotate_transf.IsStopping())) rotate_transf.Play();
					else rotate_anim.resume(false);
				}
			}
			// stop loop animation if exist and we are retracting
			else
			{
				// Call transform.stop() if it is rotating and the Stop method wasn't called.
				if (rotateIsTransform && rotate_transf.IsRotating() && !rotate_transf.IsStopping()) rotate_transf.Stop();
				else rotate_anim.stop();
			}

			// When is not rotating
			if (waitRotation)
			{
				if (rotateIsTransform && !rotate_transf.IsRotating())
				{
					// start retract animation in the correct direction, when is not rotating
					if (animBackwards) deploy_anim.play(deployed, false);
					else deploy_anim.play(!deployed, false);
					waitRotation = false;
				}
				else if (!rotateIsTransform && !rotate_anim.playing())
				{
					if (animBackwards) deploy_anim.play(deployed, false);
					else deploy_anim.play(!deployed, false);
					waitRotation = false;
				}
			}

			// if has any animation playing, consume energy.
			if (deploy_anim.playing() || (rotate_transf.IsRotating() && !rotate_transf.IsStopping()) || rotate_anim.playing())
			{
				// get resource handler
				resource_info ec = ResourceCache.Info(vessel, "ElectricCharge");

				// consume ec
				ec.Consume(ec_rate * Kerbalism.elapsed_s);
			}

			if (rotateIsTransform && rotate_transf != null) rotate_transf.DoSpin();
		}
Beispiel #26
0
  // implement greenhouse mechanics
  public void FixedUpdate()
  {
    // set emissive intensity from lamp tweakable
    if (emissive_object.Length > 0)
    {
      foreach(var rdr in part.GetComponentsInChildren<UnityEngine.Renderer>())
      {
        if (rdr.name == emissive_object) { rdr.material.SetColor("_EmissiveColor", new Color(lamps, lamps, lamps, 1.0f)); break; }
      }
    }

    // do nothing else in the editor
    if (HighLogic.LoadedSceneIsEditor) return;

    // get vessel info from the cache
    vessel_info vi = Cache.VesselInfo(vessel);

    // do nothing if vessel is invalid
    if (!vi.is_valid) return;

    // get resource cache
    vessel_resources resources = ResourceCache.Get(vessel);

    // get elapsed time
    double elapsed_s = Kerbalism.elapsed_s * vi.time_dilation;

    // when the greenhouse is assembled using KIS, the growth field is set to NaN
    // at that point it remain NaN forever, so we fix it
    // also, a report indicated -infinity in growth
    if (double.IsNaN(growth) || double.IsInfinity(growth)) growth = 0.0;

    // if lamp is on
    if (lamps > float.Epsilon)
    {
      // get resource handler
      resource_info ec = resources.Info(vessel, "ElectricCharge");

      // consume ec
      ec.Consume(ec_rate * lamps * elapsed_s);

      // shut down the light if there isn't enough ec
      // note: comparing against amount at previous simulation step
      if (ec.amount <= double.Epsilon) lamps = 0.0f;
    }

    // determine lighting conditions
    // note: we ignore sun direction for gameplay reasons: else the user must reorient the greenhouse as the planets dance over time
    lighting = vi.solar_flux / Sim.SolarFluxAtHome() * (door_opened ? 1.0 : 0.0) + lamps;

    // if can use waste, and there is some lighting
    double waste_perc = 0.0;
    if (waste_name.Length > 0 && waste_rate > double.Epsilon && lighting > double.Epsilon)
    {
      // get resource handler
      resource_info waste = resources.Info(vessel, waste_name);

      // consume waste
      waste.Consume(waste_rate * elapsed_s);

      // determine waste bonus
      // note: comparing against amount from previous simulation step
      waste_perc = Math.Min(waste.amount / waste_rate, 1.0);
    }

    // determine growth bonus
    double growth_bonus = soil_bonus * (vi.landed ? 1.0 : 0.0) + waste_bonus * waste_perc;

    // grow the crop
    growing = growth_rate * (1.0 + growth_bonus) * lighting;
    growth += elapsed_s * growing;

    // if it is harvest time
    if (growth >= 1.0)
    {
      // reset growth
      growth = 0.0;

      // produce food
      resources.Produce(vessel, resource_name, harvest_size);

      // show a message to the user
      Message.Post(Lib.BuildString("On <color=FFFFFF>", vessel.vesselName, "</color> the crop harvest produced <color=FFFFFF>",
        harvest_size.ToString("F0"), " ", resource_name, "</color>"));

      // record first space harvest
      if (!vi.landed && DB.Ready()) DB.Landmarks().space_harvest = 1;
    }

    // set rmb ui status
    GrowthStatus = Lib.HumanReadablePerc(growth);
    LightStatus = Lib.HumanReadablePerc(lighting);
    WasteStatus = Lib.HumanReadablePerc(waste_perc);
    SoilStatus = vi.landed ? "yes" : "no";
    TTAStatus = Lib.HumanReadableDuration(growing > double.Epsilon ? (1.0 - growth) / growing : 0.0);


    // enable/disable emergency harvest
    Events["EmergencyHarvest"].active = (growth >= 0.5);
  }
Beispiel #27
0
  // implement greenhouse mechanics for unloaded vessels
  public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Greenhouse greenhouse, vessel_info info, vessel_resources resources, double elapsed_s)
  {
    // get protomodule data
    bool door_opened = Lib.Proto.GetBool(m, "door_opened");
    double growth = Lib.Proto.GetDouble(m, "growth");
    float lamps = Lib.Proto.GetFloat(m, "lamps");
    double lighting = Lib.Proto.GetDouble(m, "lighting");

    // if lamp is on
    if (lamps > float.Epsilon)
    {
      // get resource handler
      resource_info ec = resources.Info(vessel, "ElectricCharge");

      // consume ec
      ec.Consume(greenhouse.ec_rate * lamps * elapsed_s * Reliability.Penalty(p, "Greenhouse", 2.0));

      // shut down the light if there isn't enough ec
      // note: comparing against amount at previous simulation step
      if (ec.amount <= double.Epsilon) lamps = 0.0f;
    }

    // determine lighting conditions
    // note: we ignore sun direction for gameplay reasons: else the user must reorient the greenhouse as the planets dance over time
    // - natural light depend on: distance from sun, direct sunlight, door status
    // - artificial light depend on: lamps tweakable and ec available, door status
    lighting = info.solar_flux / Sim.SolarFluxAtHome() * (door_opened ? 1.0 : 0.0) + lamps;

    // if can use waste, and there is some lighting
    double waste_perc = 0.0;
    if (greenhouse.waste_name.Length > 0 && lighting > double.Epsilon)
    {
      // get resource handler
      resource_info waste = resources.Info(vessel, greenhouse.waste_name);

      // consume waste
      waste.Consume(greenhouse.waste_rate * elapsed_s);

      // determine waste bonus
      // note: comparing against amount from previous simulation step
      waste_perc = Math.Min(waste.amount / greenhouse.waste_rate, 1.0);
    }

    // determine growth bonus
    double growth_bonus = greenhouse.soil_bonus * (info.landed ? 1.0 : 0.0) + greenhouse.waste_bonus * waste_perc;

    // grow the crop
    double growing = greenhouse.growth_rate * (1.0 + growth_bonus) * lighting;
    growth += elapsed_s * growing;

    // if it is harvest time
    if (growth >= 1.0)
    {
      // reset growth
      growth = 0.0;

      // produce food
      resources.Produce(vessel, greenhouse.resource_name, greenhouse.harvest_size);

      // show a message to the user
      Message.Post(Lib.BuildString("On <color=FFFFFF>", vessel.vesselName, "</color> the crop harvest produced <color=FFFFFF>",
        greenhouse.harvest_size.ToString("F0"), " ", greenhouse.resource_name, "</color>"));

      // record first space harvest
      if (!info.landed && DB.Ready()) DB.Landmarks().space_harvest = 1;
    }

    // store data
    Lib.Proto.Set(m, "growth", growth);
    Lib.Proto.Set(m, "lamps", lamps);
    Lib.Proto.Set(m, "lighting", lighting);
    Lib.Proto.Set(m, "growth_diff", growing);
  }
Beispiel #28
0
  public static void applyRules(Vessel v, vessel_info vi, vessel_data vd, vessel_resources resources, double elapsed_s)
  {
    // get crew
    List<ProtoCrewMember> crew = v.loaded ? v.GetVesselCrew() : v.protoVessel.GetVesselCrew();

    // get breathable modifier
    double breathable = vi.breathable ? 0.0 : 1.0;

    // get temp diff modifier
    double temp_diff = v.altitude < 2000.0 && v.mainBody == FlightGlobals.GetHomeBody() ? 0.0 : Sim.TempDiff(vi.temperature);

    // for each rule
    foreach(Rule r in Kerbalism.rules)
    {
      // get resource handler
      resource_info res = r.resource_name.Length > 0 ? resources.Info(v, r.resource_name) : null;

      // if a resource is specified
      if (res != null)
      {
        // get data from db
        vmon_data vmon = DB.VmonData(v.id, r.name);

        // message obey user config
        bool show_msg = (r.resource_name == "ElectricCharge" ? vd.cfg_ec > 0 : vd.cfg_supply > 0);

        // no messages with no capacity
        if (res.capacity > double.Epsilon)
        {
          // manned/probe message variant
          uint variant = crew.Count > 0 ? 0 : 1u;

          // manage messages
          if (res.level <= double.Epsilon && vmon.message < 2)
          {
            if (r.empty_message.Length > 0 && show_msg) Message.Post(Severity.danger, Lib.ExpandMsg(r.empty_message, v, null, variant));
            vmon.message = 2;
          }
          else if (res.level < r.low_threshold && vmon.message < 1)
          {
            if (r.low_message.Length > 0 && show_msg) Message.Post(Severity.warning, Lib.ExpandMsg(r.low_message, v, null, variant));
            vmon.message = 1;
          }
          else if (res.level > r.low_threshold && vmon.message > 0)
          {
            if (r.refill_message.Length > 0 && show_msg) Message.Post(Severity.relax, Lib.ExpandMsg(r.refill_message, v, null, variant));
            vmon.message = 0;
          }
        }
      }

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

        // skip resque kerbals
        if (kd.resque == 1) continue;

        // skip disabled kerbals
        if (kd.disabled == 1) continue;

        // get supply data from db
        kmon_data kmon = DB.KmonData(c.name, r.name);


        // get product of all environment modifiers
        double k = 1.0;
        foreach(string modifier in r.modifier)
        {
          switch(modifier)
          {
            case "breathable":  k *= breathable;                              break;
            case "temperature": k *= temp_diff;                               break;
            case "radiation":   k *= vi.radiation * (1.0 - kd.shielding);     break;
            case "qol":         k /= QualityOfLife.Bonus(kd.living_space, kd.entertainment, vi.landed, vi.link.linked, vi.crew_count == 1); break;
          }
        }


        // if continuous
        double step;
        if (r.interval <= double.Epsilon)
        {
          // influence consumption by elapsed time
          step = elapsed_s;
        }
        // if interval-based
        else
        {
          // accumulate time
          kmon.time_since += elapsed_s;

          // determine number of steps
          step = Math.Floor(kmon.time_since / r.interval);

          // consume time
          kmon.time_since -= step * r.interval;

          // remember if a meal is consumed in this simulation step
          res.meal_consumed |= step > 0.99;
        }


        // if continuous, or if one or more intervals elapsed
        if (step > double.Epsilon)
        {
          // indicate if we must degenerate
          bool must_degenerate = true;

          // if there is a resource specified, and this isn't just a monitoring rule
          if (res != null && r.rate > double.Epsilon)
          {
            // determine amount of resource to consume
            double required = r.rate          // rate per-second or per interval
                            * k               // product of environment modifiers
                            * step;           // seconds elapsed or number of steps

            // if there is no waste
            if (r.waste_name.Length == 0)
            {
              // simply consume (that is faster)
              res.Consume(required);

            }
            // if there is waste
            else
            {
              // transform resource into waste
              resource_recipe recipe = new resource_recipe(resource_recipe.rule_priority);
              recipe.Input(r.resource_name, required);
              recipe.Output(r.waste_name, required * r.waste_ratio);
              resources.Transform(recipe);
            }

            // reset degeneration when consumed, or when not required at all
            // note: evaluating amount from previous simulation step
            if (required <= double.Epsilon || res.amount > double.Epsilon)
            {
              // slowly recover instead of instant reset
              kmon.problem *= 1.0 / (1.0 + Math.Max(r.interval, 1.0) * step * 0.002);
              kmon.problem = Math.Max(kmon.problem, 0.0);

              // do not degenerate
              must_degenerate = false;
            }
          }

          // degenerate if this rule is resource-less, or if there was not enough resource in the vessel
          if (must_degenerate)
          {
            kmon.problem += r.degeneration            // degeneration rate per-second or per-interval
                          * k                         // product of environment modifiers
                          * step                      // seconds elapsed or by number of steps
                          * Variance(c, r.variance);  // kerbal-specific variance
          }


          // determine message variant
          uint variant = vi.temperature < Settings.SurvivalTemperature ? 0 : 1u;

          // kill kerbal if necessary
          if (kmon.problem >= r.fatal_threshold)
          {
            if (r.fatal_message.Length > 0)
              Message.Post(r.breakdown ? Severity.breakdown : Severity.fatality, Lib.ExpandMsg(r.fatal_message, v, c, variant));

            if (r.breakdown)
            {
              Kerbalism.Breakdown(v, c);
              kmon.problem = r.danger_threshold * 1.01; //< move back to danger threshold
            }
            else
            {
              Kerbalism.Kill(v, c);
            }
          }
          // show messages
          else if (kmon.problem >= r.danger_threshold && kmon.message < 2)
          {
            if (r.danger_message.Length > 0) Message.Post(Severity.danger, Lib.ExpandMsg(r.danger_message, v, c, variant));
            kmon.message = 2;
          }
          else if (kmon.problem >= r.warning_threshold && kmon.message < 1)
          {
            if (r.warning_message.Length > 0) Message.Post(Severity.warning, Lib.ExpandMsg(r.warning_message, v, c, variant));
            kmon.message = 1;
          }
          else if (kmon.problem < r.warning_threshold && kmon.message > 0)
          {
            if (r.relax_message.Length > 0) Message.Post(Severity.relax, Lib.ExpandMsg(r.relax_message, v, c, variant));
            kmon.message = 0;
          }
        }
      }
    }
  }
Beispiel #29
0
        static void ProcessScanner(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule scanner, Part part_prefab, vessel_data vd, resource_info ec, double elapsed_s)
        {
            // get ec consumption rate
            double power = SCANsat.EcConsumption(scanner);

            // if the scanner doesn't require power to operate, we aren't interested in simulating it
            if (power <= double.Epsilon)
            {
                return;
            }

            // get scanner state
            bool is_scanning = Lib.Proto.GetBool(m, "scanning");

            // if its scanning
            if (is_scanning)
            {
                // consume ec
                ec.Consume(power * elapsed_s);

                // if there isn't ec
                // note: comparing against amount in previous simulation step
                if (ec.amount <= double.Epsilon)
                {
                    // unregister scanner
                    SCANsat.stopScanner(v, m, part_prefab, scanner);
                    is_scanning = false;

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

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

                    // give the user some feedback
                    if (vd.cfg_ec == 1)
                    {
                        Message.Post(Lib.BuildString("SCANsat sensor resumed operations on <b>", v.vesselName, "</b>"));
                    }
                }
            }

            // forget active scanners
            if (is_scanning)
            {
                vd.scansat_id.Remove(p.flightID);
            }
        }
Beispiel #30
0
        public static void BackgroundUpdate(Vessel v, ProtoPartModuleSnapshot m, Greenhouse g,
                                            vessel_info vi, vessel_resources 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
                resource_info ec = resources.Info(v, "ElectricCharge");

                // calculate natural and artificial lighting
                double natural    = vi.solar_flux;
                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);
                }

                // 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
                resource_recipe recipe = new resource_recipe();
                foreach (ModuleResource input in g.resHandler.inputResources)
                {
                    recipe.Input(input.name, input.rate * elapsed_s);
                }
                foreach (ModuleResource output in g.resHandler.outputResources)
                {
                    recipe.Output(output.name, output.rate * elapsed_s, true);
                }
                resources.Transform(recipe);

                // determine environment conditions
                bool lighting  = natural + artificial >= g.light_tolerance;
                bool pressure  = g.pressure_tolerance <= double.Epsilon || vi.pressure >= g.pressure_tolerance;
                bool radiation = g.radiation_tolerance <= double.Epsilon || vi.radiation * (1.0 - vi.shielding) < g.radiation_tolerance;

                // determine inputs conditions
                // note: comparing against amounts in previous simulation step
                bool   inputs      = true;
                string missing_res = string.Empty;
                foreach (ModuleResource input in g.resHandler.inputResources)
                {
                    if (resources.Info(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(Lib.BuildString("On <b>", v.vesselName, "</b> 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("missing ", missing_res)
                        : !lighting  ? "insufficient lighting"
      : !pressure  ? "insufficient pressure"
      : !radiation ? "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);
            }
        }