Exemple #1
0
		// return human-readable timestamp of planetarium time
		public static string PlanetariumTimestamp()
		{
			double t = Planetarium.GetUniversalTime();
			const double len_min = 60.0;
			const double len_hour = len_min * 60.0;
			double len_day = len_hour * Lib.HoursInDay();
			double len_year = len_day * Lib.DaysInYear();

			double year = Math.Floor(t / len_year);
			t -= year * len_year;
			double day = Math.Floor(t / len_day);
			t -= day * len_day;
			double hour = Math.Floor(t / len_hour);
			t -= hour * len_hour;
			double min = Math.Floor(t / len_min);

			return BuildString
			(
			  "[",
			  ((uint)year + 1).ToString("D4"),
			  "/",
			  ((uint)day + 1).ToString("D2"),
			  " ",
			  ((uint)hour).ToString("D2"),
			  ":",
			  ((uint)min).ToString("D2"),
			  "]"
			);
		}
Exemple #2
0
        static void ProcessRadioisotopeGenerator(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule radioisotope_generator, resource_info ec, double elapsed_s)
        {
            // note: doesn't support easy mode

            double power        = Lib.ReflectionValue <float>(radioisotope_generator, "BasePower");
            double half_life    = Lib.ReflectionValue <float>(radioisotope_generator, "HalfLife");
            double mission_time = v.missionTime / (3600.0 * Lib.HoursInDay() * Lib.DaysInYear());
            double remaining    = Math.Pow(2.0, (-mission_time) / half_life);

            ec.Produce(power * remaining * elapsed_s);
        }
Exemple #3
0
 protected override void OnUpdate()
 {
   foreach(Vessel v in FlightGlobals.Vessels)
   {
     vessel_info vi = Cache.VesselInfo(v);
     if (!vi.is_valid) continue;
     bool manned = v.loaded ? v.GetCrewCount() > 0 : v.protoVessel.GetVesselCrew().Count > 0;
     bool in_orbit = Sim.Apoapsis(v) > v.mainBody.atmosphereDepth && Sim.Periapsis(v) > v.mainBody.atmosphereDepth;
     bool for_30days = v.missionTime > 60.0 * 60.0 * Lib.HoursInDay() * 30.0;
     if (manned && in_orbit && for_30days && DB.Ready())
     {
       base.SetComplete();
       DB.Landmarks().manned_orbit = 1; //< remember that contract was completed
       break;
     }
   }
 }
Exemple #4
0
        static void ProcessPanel(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleDeployableSolarPanel panel, Vessel_info info, Resource_info ec, double elapsed_s)
        {
            // note: we ignore temperature curve, and make sure it is not relevant in the MM patch
            // note: cylindrical and spherical panels are not supported
            // note: we assume the tracking target is SUN
            // if in sunlight and extended
            if (info.sunlight > double.Epsilon && m.moduleValues.GetValue("deployState") == "EXTENDED")
            {
                // get panel normal/pivot direction in world space
                Transform tr  = panel.part.FindModelComponent <Transform>(panel.pivotName);
                Vector3d  dir = panel.isTracking ? tr.up : tr.forward;
                dir = (v.transform.rotation * p.rotation * dir).normalized;

                float age          = (float)(v.missionTime / (Lib.HoursInDay() * 3600));
                float effic_factor = panel.timeEfficCurve != null?panel.timeEfficCurve.Evaluate(age) : 1.0f;

                // calculate cosine factor
                // - fixed panel: clamped cosine
                // - tracking panel, tracking pivot enabled: around the pivot
                // - tracking panel, tracking pivot disabled: assume perfect alignment
                double cosine_factor =
                    !panel.isTracking
                                  ? Math.Max(Vector3d.Dot(info.sun_dir, dir), 0.0)
                                  : Settings.TrackingPivot
                                  ? Math.Cos(1.57079632679 - Math.Acos(Vector3d.Dot(info.sun_dir, dir)))
                                  : 1.0;

                // calculate normalized solar flux
                // - this include fractional sunlight if integrated over orbit
                // - this include atmospheric absorption if inside an atmosphere
                double norm_solar_flux = info.solar_flux / Sim.SolarFluxAtHome();

                // calculate output
                double output = panel.resHandler.outputResources[0].rate                              // nominal panel charge rate at 1 AU
                                * norm_solar_flux                                                     // normalized flux at panel distance from sun
                                * cosine_factor                                                       // cosine factor of panel orientation
                                * effic_factor;

                // produce EC
                ec.Produce(output * elapsed_s, "panel");
            }
        }
Exemple #5
0
  public static reliability_data analyze_reliability(List<Part> parts, ec_data ec, signal_data signal)
  {
    // store data
    reliability_data reliability = new reliability_data();

    // get manufacturing quality
    reliability.quality = Malfunction.DeduceQuality();

    // count parts that can fail
    uint components = 0;

    // scan the parts
    foreach(Part p in parts)
    {
      // for each module
      foreach(PartModule m in p.Modules)
      {
        // malfunctions
        if (m.moduleName == "Malfunction")
        {
          Malfunction mm = (Malfunction)m;
          ++components;
          double avg_lifetime = (mm.min_lifetime + mm.max_lifetime) * 0.5 * reliability.quality;
          reliability.failure_year += (60.0 * 60.0 * Lib.HoursInDay() * Lib.DaysInYear()) / avg_lifetime;
        }
      }
    }

    // calculate reliability data
    if (components > 0) reliability.failure_year /= (double)components;
    double ec_redundancy = ec.best_ec_generator < ec.generated_sunlight ? (ec.generated_sunlight - ec.best_ec_generator) / ec.generated_sunlight : 0.0;
    double antenna_redundancy = signal.second_best_range > 0.0 ? signal.second_best_range / signal.range : 0.0;
    List<string> redundancies = new List<string>();
    if (ec_redundancy >= 0.5) redundancies.Add("ec");
    if (antenna_redundancy >= 0.99) redundancies.Add("antenna");
    if (redundancies.Count == 0) redundancies.Add("none");
    reliability.redundancy = String.Join(", ", redundancies.ToArray());

    // return data
    return reliability;
  }
Exemple #6
0
        public void Start()
        {
            // shortcut to the resource library
            var reslib = PartResourceLibrary.Instance.resourceDefinitions;

            // length of a day in seconds
            double daylen = 60.0 * 60.0 * Lib.HoursInDay();


            // log version
            Lib.Log("version " + Assembly.GetExecutingAssembly().GetName().Version);

            // parse detected mods
            var mods = Lib.ParseConfig("Kerbalism/Patches/System/DetectedMods");

            detected_mods.ModuleManager        = Lib.ConfigValue(mods, "ModuleManager", false);
            detected_mods.SCANsat              = Lib.ConfigValue(mods, "SCANsat", false);
            detected_mods.CLS                  = Lib.ConfigValue(mods, "CLS", false);
            detected_mods.KIS                  = Lib.ConfigValue(mods, "KIS", false);
            detected_mods.CRP                  = Lib.ConfigValue(mods, "CRP", false);
            detected_mods.RemoteTech           = Lib.ConfigValue(mods, "RemoteTech", false);
            detected_mods.AntennaRange         = Lib.ConfigValue(mods, "AntennaRange", false);
            detected_mods.DangIt               = Lib.ConfigValue(mods, "DangIt", false);
            detected_mods.BackgroundProcessing = Lib.ConfigValue(mods, "BackgroundProcessing", false);
            detected_mods.RealFuels            = Lib.ConfigValue(mods, "RealFuels", false);
            detected_mods.DeepFreeze           = Lib.ConfigValue(mods, "DeepFreeze", false);
            detected_mods.TestFlight           = Lib.ConfigValue(mods, "TestFlight", false);
            Lib.Log("detected:");
            Lib.Log("- ModuleManager: " + detected_mods.ModuleManager);
            Lib.Log("- SCANsat: " + detected_mods.SCANsat);
            Lib.Log("- CLS: " + detected_mods.CLS);
            Lib.Log("- KIS: " + detected_mods.KIS);
            Lib.Log("- CRP: " + detected_mods.CRP);
            Lib.Log("- RemoteTech: " + detected_mods.RemoteTech);
            Lib.Log("- AntennaRange: " + detected_mods.AntennaRange);
            Lib.Log("- DangIt: " + detected_mods.DangIt);
            Lib.Log("- BackgroundProcessing: " + detected_mods.BackgroundProcessing);
            Lib.Log("- RealFuels: " + detected_mods.RealFuels);
            Lib.Log("- DeepFreeze: " + detected_mods.DeepFreeze);
            Lib.Log("- TestFlight: " + detected_mods.TestFlight);

            // determine features
            var f_cfg = Lib.ParseConfig("Kerbalism/Patches/System/Features");

            features.signal      = Lib.ConfigValue(f_cfg, "signal", false);
            features.reliability = Lib.ConfigValue(f_cfg, "reliability", false);
            features.scrubber    = Lib.ConfigValue(f_cfg, "scrubber", false);
            features.shielding   = Lib.ConfigValue(f_cfg, "shielding", false);
            Lib.Log("features:");
            Lib.Log("- signal: " + features.signal);
            Lib.Log("- reliability: " + features.reliability);
            Lib.Log("- scrubber: " + features.scrubber);
            Lib.Log("- shielding: " + features.shielding);

            // get all the rules
            var r_cfg = Lib.ParseConfigs("Rule");

            foreach (var node in r_cfg)
            {
                Rule r = new Rule(node);
                if (r.name.Length > 0 && rules.Find(k => k.name == r.name) == null)
                {
                    rules.Add(r);
                    if (r.resource_name == "ElectricCharge")
                    {
                        ec_rule = r;
                    }
                    if (r.modifier.Contains("temperature") && r.resource_name == "ElectricCharge")
                    {
                        temp_rule = r;
                    }
                    if (r.modifier.Contains("qol"))
                    {
                        qol_rule = r;
                    }
                    if (r.modifier.Contains("radiation"))
                    {
                        rad_rule = r;
                    }
                    if (r.resource_name.Length > 0 && reslib.Contains(r.resource_name) && r.resource_name != "ElectricCharge")
                    {
                        supply_rules.Add(r);
                    }
                }
            }
            Lib.Log("rules:");
            foreach (Rule r in rules)
            {
                string modifiers = r.modifier.Count > 0 ? string.Join(", ", r.modifier.ToArray()) : "none";
                Lib.Log("- " + r.name + " (modifier: " + modifiers + ")");
            }
            if (rules.Count == 0)
            {
                Lib.Log("- none");
            }

            // add resources to manned parts
            foreach (AvailablePart part in PartLoader.LoadedPartsList)
            {
                // get the prefab
                Part prefab = part.partPrefab;

                // avoid problems with some parts that don't have a resource container (EVA kerbals, flags)
                if (prefab.Resources == null)
                {
                    continue;
                }

                // if manned
                if (prefab.CrewCapacity > 0)
                {
                    double crew_capacity = (double)prefab.CrewCapacity;
                    double extra_cost    = 0.0;
                    foreach (Rule r in rules)
                    {
                        // add rule's resource
                        if (r.resource_name.Length > 0 && r.on_pod > 0.0 && reslib.Contains(r.resource_name) && !prefab.Resources.Contains(r.resource_name))
                        {
                            var res = new ConfigNode("RESOURCE");
                            res.AddValue("name", r.resource_name);
                            res.AddValue("amount", r.on_pod * crew_capacity);
                            res.AddValue("maxAmount", r.on_pod * crew_capacity);
                            prefab.Resources.Add(res);
                            extra_cost += (double)reslib[r.resource_name].unitCost * r.on_pod * crew_capacity;
                        }
                    }
                    // loop the rules a second time to give the resources a nice order
                    foreach (Rule r in rules)
                    {
                        // add rule's waste resource
                        double waste_per_day = (r.rate / (r.interval > 0.0 ? r.interval : 1.0)) * daylen * r.waste_ratio;
                        double waste_amount  = waste_per_day * r.waste_buffer * crew_capacity;
                        if (r.waste_name.Length > 0 && waste_amount > double.Epsilon && reslib.Contains(r.waste_name) && !prefab.Resources.Contains(r.waste_name))
                        {
                            var res = new ConfigNode("RESOURCE");
                            res.AddValue("name", r.waste_name);
                            res.AddValue("amount", "0.0");
                            res.AddValue("maxAmount", waste_amount);
                            if (r.hidden_waste)
                            {
                                res.AddValue("isTweakable", false.ToString());
                                res.AddValue("isVisible", false.ToString());
                            }
                            if (r.massless_waste)
                            {
                                res.AddValue("density", 0.0.ToString());
                                res.AddValue("unitCost", 0.0.ToString());
                            }
                            prefab.Resources.Add(res);
                            extra_cost += (double)reslib[r.waste_name].unitCost * waste_amount;
                        }
                    }

                    // add shielding
                    if (features.shielding && reslib.Contains("Shielding") && !prefab.Resources.Contains("Shielding"))
                    {
                        var res = new ConfigNode("RESOURCE");
                        res.AddValue("name", "Shielding");
                        res.AddValue("amount", "0.0");
                        res.AddValue("maxAmount", crew_capacity);
                        prefab.Resources.Add(res);
                        extra_cost += (double)reslib["Shielding"].unitCost * crew_capacity;
                    }

                    // add the extra cost
                    part.cost += (float)extra_cost;
                    part.cost  = Mathf.Round(part.cost + 0.5f);
                }
            }

            // set callbacks
            GameEvents.onCrewOnEva.Add(this.toEVA);
            GameEvents.onCrewBoardVessel.Add(this.fromEVA);
            GameEvents.onVesselRecovered.Add(this.vesselRecovered);
            GameEvents.onVesselTerminated.Add(this.vesselTerminated);
            GameEvents.onVesselWillDestroy.Add(this.vesselDestroyed);
            GameEvents.onPartCouple.Add(this.vesselDock);
            GameEvents.OnTechnologyResearched.Add(this.techResearched);
            GameEvents.onGUIEditorToolbarReady.Add(this.addEditorCategory);

            // precompute 1 AU
            AU = Lib.PlanetarySystem(FlightGlobals.GetHomeBody()).orbit.semiMajorAxis;
        }
Exemple #7
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 < PreferencesLifeSupport.Instance.survivalTemperature ? 0 : 1u;

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

            bool lifetime_enabled = PreferencesBasic.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;

                // 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
                    if (step > 0.99)
                    {
                        res.SetMealHappened();
                    }
                    if (output.Length > 0 && step > 0.99)
                    {
                        ResourceCache.Info(v, output).SetMealHappened();
                    }
                }

                // if continuous, or if one or more intervals elapsed
                if (step > double.Epsilon)
                {
                    double r = rate * Variance(name, c, individuality);                      // kerbal-specific variance

                    // if there is a resource specified
                    if (res != null && r > double.Epsilon)
                    {
                        // determine amount of resource to consume
                        double required = r                                   // 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 monitor is false
                        else if (!monitor)
                        {
                            // transform input into output resource
                            // - rules always dump excess overboard (because it is waste)
                            Resource_recipe recipe = new Resource_recipe((Part)null);                              // kerbals are not associated with a part
                            recipe.Input(input, required);
                            recipe.Output(output, required * ratio, true);
                            resources.Transform(recipe);
                        }
                        // if monitor then do not consume input resource and only produce output if resource percentage + monitor_offset is < 100%
                        else if ((res.amount / res.capacity) + monitor_offset < 1.0)
                        {
                            // simply produce (that is faster)
                            resources.Produce(v, output, required * ratio);
                        }
                    }

                    // 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) + monitor_offset >= 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(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 && PreferencesBasic.Instance.stressBreakdowns)
                {
                    // 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 *= PreferencesBasic.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 (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);
            }
        }
  // called at every simulation step
  public void FixedUpdate()
  {
    // do nothing if paused
    if (Lib.IsPaused()) return;

    // do nothing if DB isn't ready
    if (!DB.Ready()) return;

    // for each vessel
    foreach(Vessel vessel in FlightGlobals.Vessels)
    {
      // skip invalid vessels
      if (!Lib.IsVessel(vessel)) continue;

      // skip loaded vessels
      if (vessel.loaded) continue;

      // get vessel data from the db
      vessel_data vd = DB.VesselData(vessel.id);

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

      // calculate atmospheric factor (proportion of flux not blocked by atmosphere)
      double atmo_factor = Sim.AtmosphereFactor(vessel.mainBody, info.position, info.sun_dir);

      // for each part
      foreach(ProtoPartSnapshot part in vessel.protoVessel.protoPartSnapshots)
      {
        // get part prefab (required for module properties)
        Part part_prefab = PartLoader.getPartInfoByName(part.partName).partPrefab;

        // store index of ModuleResourceConverter to process
        // rationale: a part can contain multiple resource converters
        int converter_index = 0;

        // for each module
        foreach(ProtoPartModuleSnapshot module in part.modules)
        {
          // something weird is going on, skip this
          if (!part_prefab.Modules.Contains(module.moduleName)) continue;

          // command module
          if (module.moduleName == "ModuleCommand")
          {
            // get module from prefab
            ModuleCommand command = part_prefab.Modules.GetModules<ModuleCommand>()[0];

            // do not consume if this is a MCM with no crew
            // rationale: for consistency, the game doesn't consume resources for MCM without crew in loaded vessels
            //            this make some sense: you left a vessel with some battery and nobody on board, you expect it to not consume EC
            if (command.minimumCrew == 0 || part.protoModuleCrew.Count > 0)
            {
              // for each input resource
              foreach(ModuleResource ir in command.inputResources)
              {
                // consume the resource
                Lib.RequestResource(vessel, ir.name, ir.rate * TimeWarp.fixedDeltaTime);
              }
            }
          }
          // solar panel
          else if (module.moduleName == "ModuleDeployableSolarPanel")
          {
            // determine if extended
            bool extended = module.moduleValues.GetValue("stateString") == ModuleDeployableSolarPanel.panelStates.EXTENDED.ToString();

            // if in sunlight and extended
            if (info.sunlight && extended)
            {
              // get module from prefab
              ModuleDeployableSolarPanel panel = part_prefab.Modules.GetModules<ModuleDeployableSolarPanel>()[0];

              // produce electric charge
              Lib.RequestResource(vessel, "ElectricCharge", -PanelOutput(vessel, part, panel, info.sun_dir, info.sun_dist, atmo_factor) * TimeWarp.fixedDeltaTime * Malfunction.Penalty(part));
            }
          }
          // generator
          // note: assume generators require all input
          else if (module.moduleName == "ModuleGenerator")
          {
            // determine if active
            bool activated = Convert.ToBoolean(module.moduleValues.GetValue("generatorIsActive"));

            // if active
            if (activated)
            {
              // get module from prefab
              ModuleGenerator generator = part_prefab.Modules.GetModules<ModuleGenerator>()[0];

              // determine if vessel is full of all output resources
              bool full = true;
              foreach(var or in generator.outputList)
              {
                double amount = Lib.GetResourceAmount(vessel, or.name);
                double capacity = Lib.GetResourceCapacity(vessel, or.name);
                double perc = capacity > 0.0 ? amount / capacity : 0.0;
                full &= (perc >= 1.0 - double.Epsilon);
              }

              // if not full
              if (!full)
              {
                // calculate worst required resource percentual
                double worst_input = 1.0;
                foreach(var ir in generator.inputList)
                {
                  double required = ir.rate * TimeWarp.fixedDeltaTime;
                  double amount = Lib.GetResourceAmount(vessel, ir.name);
                  worst_input = Math.Min(worst_input, amount / required);
                }

                // for each input resource
                foreach(var ir in generator.inputList)
                {
                  // consume the resource
                  Lib.RequestResource(vessel, ir.name, ir.rate * worst_input * TimeWarp.fixedDeltaTime);
                }

                // for each output resource
                foreach(var or in generator.outputList)
                {
                  // produce the resource
                  Lib.RequestResource(vessel, or.name, -or.rate * worst_input * TimeWarp.fixedDeltaTime * Malfunction.Penalty(part));
                }
              }
            }
          }
          // converter
          // note: support multiple resource converters
          // note: ignore stock temperature mechanic of converters
          // note: ignore autoshutdown
          // note: ignore crew experience bonus (seem that stock ignore it too)
          // note: 'undo' stock behaviour by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation)
          // note: support PlanetaryBaseSystem converters
          // note: support NearFuture reactors
          else if (module.moduleName == "ModuleResourceConverter" || module.moduleName == "ModuleKPBSConverter" || module.moduleName == "FissionReactor")
          {
            // get module from prefab
            ModuleResourceConverter converter = part_prefab.Modules.GetModules<ModuleResourceConverter>()[converter_index++];

            // determine if active
            bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated"));

            // if active
            if (activated)
            {
              // determine if vessel is full of all output resources
              bool full = true;
              foreach(var or in converter.outputList)
              {
                double amount = Lib.GetResourceAmount(vessel, or.ResourceName);
                double capacity = Lib.GetResourceCapacity(vessel, or.ResourceName);
                double perc = capacity > 0.0 ? amount / capacity : 0.0;
                full &= (perc >= converter.FillAmount - double.Epsilon);
              }

              // if not full
              if (!full)
              {
                // calculate worst required resource percentual
                double worst_input = 1.0;
                foreach(var ir in converter.inputList)
                {
                  double required = ir.Ratio * TimeWarp.fixedDeltaTime;
                  double amount = Lib.GetResourceAmount(vessel, ir.ResourceName);
                  worst_input = Math.Min(worst_input, amount / required);
                }

                // for each input resource
                foreach(var ir in converter.inputList)
                {
                  // consume the resource
                  Lib.RequestResource(vessel, ir.ResourceName, ir.Ratio * worst_input * TimeWarp.fixedDeltaTime);
                }

                // for each output resource
                foreach(var or in converter.outputList)
                {
                  // produce the resource
                  Lib.RequestResource(vessel, or.ResourceName, -or.Ratio * worst_input * TimeWarp.fixedDeltaTime * Malfunction.Penalty(part));
                }
              }

              // undo stock behaviour by forcing last_update_time to now
              module.moduleValues.SetValue("lastUpdateTime", Planetarium.GetUniversalTime().ToString());
            }
          }
          // drill
          // note: ignore stock temperature mechanic of harvesters
          // note: ignore autoshutdown
          // note: ignore depletion (stock seem to do the same)
          // note: 'undo' stock behaviour by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation)
          else if (module.moduleName == "ModuleResourceHarvester")
          {
            // determine if active
            bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated"));

            // if active
            if (activated)
            {
              // get module from prefab
              ModuleResourceHarvester harvester = part_prefab.Modules.GetModules<ModuleResourceHarvester>()[0];

              // [disabled] reason: not working
              // deduce crew bonus
              /*double experience_bonus = 0.0;
              if (harvester.UseSpecialistBonus)
              {
                foreach(ProtoCrewMember c in vessel.protoVessel.GetVesselCrew())
                {
                  experience_bonus = Math.Max(experience_bonus, (c.trait == harvester.Specialty) ? (double)c.experienceLevel : 0.0);
                }
              }*/
              const double crew_bonus = 1.0; //harvester.SpecialistBonusBase + (experience_bonus + 1.0) * harvester.SpecialistEfficiencyFactor;

              // detect amount of ore in the ground
              AbundanceRequest request = new AbundanceRequest
              {
                Altitude = vessel.altitude,
                BodyId = vessel.mainBody.flightGlobalsIndex,
                CheckForLock = false,
                Latitude = vessel.latitude,
                Longitude = vessel.longitude,
                ResourceType = (HarvestTypes)harvester.HarvesterType,
                ResourceName = harvester.ResourceName
              };
              double abundance = ResourceMap.Instance.GetAbundance(request);

              // if there is actually something (should be if active when unloaded)
              if (abundance > harvester.HarvestThreshold)
              {
                // calculate worst required resource percentual
                double worst_input = 1.0;
                foreach(var ir in harvester.inputList)
                {
                  double required = ir.Ratio * TimeWarp.fixedDeltaTime;
                  double amount = Lib.GetResourceAmount(vessel, ir.ResourceName);
                  worst_input = Math.Min(worst_input, amount / required);
                }

                // for each input resource
                foreach(var ir in harvester.inputList)
                {
                  // consume the resource
                  Lib.RequestResource(vessel, ir.ResourceName, ir.Ratio * worst_input * TimeWarp.fixedDeltaTime);
                }

                // determine resource produced
                double res = abundance * harvester.Efficiency * crew_bonus * worst_input * Malfunction.Penalty(part);

                // accumulate ore
                Lib.RequestResource(vessel, harvester.ResourceName, -res * TimeWarp.fixedDeltaTime);
              }

              // undo stock behaviour by forcing last_update_time to now
              module.moduleValues.SetValue("lastUpdateTime", Planetarium.GetUniversalTime().ToString());
            }
          }
          // asteroid drill
          // note: untested
          // note: ignore stock temperature mechanic of asteroid drills
          // note: ignore autoshutdown
          // note: 'undo' stock behaviour by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation)
          else if (module.moduleName == "ModuleAsteroidDrill")
          {
            // determine if active
            bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated"));

            // if active
            if (activated)
            {
              // get module from prefab
              ModuleAsteroidDrill asteroid_drill = part_prefab.Modules.GetModules<ModuleAsteroidDrill>()[0];

              // [disabled] reason: not working
              // deduce crew bonus
              /*double experience_bonus = 0.0;
              if (asteroid_drill.UseSpecialistBonus)
              {
                foreach(ProtoCrewMember c in vessel.protoVessel.GetVesselCrew())
                {
                  experience_bonus = Math.Max(experience_bonus, (c.trait == asteroid_drill.Specialty) ? (double)c.experienceLevel : 0.0);
                }
              }*/
              const double crew_bonus = 1.0; //asteroid_drill.SpecialistBonusBase + (experience_bonus + 1.0) * asteroid_drill.SpecialistEfficiencyFactor;

              // get asteroid data
              ProtoPartModuleSnapshot asteroid_info = null;
              ProtoPartModuleSnapshot asteroid_resource = null;
              foreach(ProtoPartSnapshot p in vessel.protoVessel.protoPartSnapshots)
              {
                if (asteroid_info == null) asteroid_info = p.modules.Find(k => k.moduleName == "ModuleAsteroidInfo");
                if (asteroid_resource == null) asteroid_resource = p.modules.Find(k => k.moduleName == "ModuleAsteroidResource");
              }

              // if there is actually an asteroid attached to this active asteroid drill (it should)
              if (asteroid_info != null && asteroid_resource != null)
              {
                // get some data
                double mass_threshold = Convert.ToDouble(asteroid_info.moduleValues.GetValue("massThresholdVal"));
                double mass = Convert.ToDouble(asteroid_info.moduleValues.GetValue("currentMassVal"));
                double abundance = Convert.ToDouble(asteroid_resource.moduleValues.GetValue("abundance"));
                string res_name = asteroid_resource.moduleValues.GetValue("resourceName");
                double res_density = PartResourceLibrary.Instance.GetDefinition(res_name).density;

                // if asteroid isn't depleted
                if (mass > mass_threshold && abundance > double.Epsilon)
                {
                  // consume EC
                  double ec_required = asteroid_drill.PowerConsumption * TimeWarp.fixedDeltaTime;
                  double ec_consumed = Lib.RequestResource(vessel, "ElectricCharge", ec_required);
                  double ec_ratio = ec_consumed / ec_required;

                  // determine resource extracted
                  double res_amount = abundance * asteroid_drill.Efficiency * crew_bonus * ec_ratio * TimeWarp.fixedDeltaTime;

                  // produce mined resource
                  Lib.RequestResource(vessel, res_name, -res_amount);

                  // consume asteroid mass
                  asteroid_info.moduleValues.SetValue("currentMassVal", (mass - res_density * res_amount).ToString());
                }
              }

              // undo stock behaviour by forcing last_update_time to now
              module.moduleValues.SetValue("lastUpdateTime", Planetarium.GetUniversalTime().ToString());
            }
          }
          // science lab
          // note: we are only simulating the EC consumption
          // note: there is no easy way to 'stop' the lab when there isn't enough EC
          else if (module.moduleName == "ModuleScienceConverter")
          {
            // get module from prefab
            ModuleScienceConverter lab = part_prefab.Modules.GetModules<ModuleScienceConverter>()[0];

            // determine if active
            bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated"));

            // if active
            if (activated)
            {
              Lib.RequestResource(vessel, "ElectricCharge", lab.powerRequirement * TimeWarp.fixedDeltaTime);
            }
          }
          // SCANSAT support
          else if (module.moduleName == "SCANsat" || module.moduleName == "ModuleSCANresourceScanner")
          {
            // get ec consumption rate
            PartModule scansat = part_prefab.Modules[module.moduleName];
            double power = Lib.ReflectionValue<float>(scansat, "power");
            double ec_required = power * TimeWarp.fixedDeltaTime;
            bool is_scanning = Lib.GetProtoValue<bool>(module, "scanning");
            bool was_disabled = vd.scansat_id.Contains(part.flightID);

            // if its scanning
            if (Lib.GetProtoValue<bool>(module, "scanning"))
            {
              // consume ec
              double ec_consumed = Lib.RequestResource(vessel, "ElectricCharge", ec_required);

              // if there isn't enough ec
              if (ec_consumed < ec_required * 0.99 && ec_required > double.Epsilon)
              {
                // unregister scanner
                SCANsat.stopScanner(vessel, module, part_prefab);

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

                // give the user some feedback
                if (DB.VesselData(vessel.id).cfg_ec == 1)
                  Message.Post("SCANsat sensor was disabled on <b>" + vessel.vesselName + "</b>");
              }
            }
            // if it was disabled
            else if (vd.scansat_id.Contains(part.flightID))
            {
              // if there is enough ec
              double ec_amount = Lib.GetResourceAmount(vessel, "ElectricCharge");
              double ec_capacity = Lib.GetResourceCapacity(vessel, "ElectricCharge");
              if (ec_capacity > double.Epsilon && ec_amount / ec_capacity > 0.25) //< re-enable at 25% EC
              {
                // re-enable the scanner
                SCANsat.resumeScanner(vessel, module, part_prefab);

                // give the user some feedback
                if (DB.VesselData(vessel.id).cfg_ec == 1)
                  Message.Post("SCANsat sensor resumed operations on <b>" + vessel.vesselName + "</b>");
              }
            }

            // forget active scanners
            if (Lib.GetProtoValue<bool>(module, "scanning")) vd.scansat_id.Remove(part.flightID);
          }
          // NearFutureSolar support
          // note: we assume deployed, this is a current limitation
          else if (module.moduleName == "ModuleCurvedSolarPanel")
          {
            // if in sunlight
            if (info.sunlight)
            {
              PartModule curved_panel = part_prefab.Modules[module.moduleName];
              double output = CurvedPanelOutput(vessel, part, part_prefab, curved_panel, info.sun_dir, info.sun_dist, atmo_factor) * Malfunction.Penalty(part);
              Lib.RequestResource(vessel, "ElectricCharge", -output * TimeWarp.fixedDeltaTime);
            }
          }
          // NearFutureElectrical support
          // note: fission generator ignore heat
          // note: radioisotope generator doesn't support easy mode
          else if (module.moduleName == "FissionGenerator")
          {
            PartModule generator = part_prefab.Modules[module.moduleName];
            double power = Lib.ReflectionValue<float>(generator, "PowerGeneration");

            // get fission reactor tweakable, will default to 1.0 for other modules
            var reactor = part.modules.Find(k => k.moduleName == "FissionReactor");
            double tweakable = reactor == null ? 1.0 : Lib.ConfigValue(reactor.moduleValues, "CurrentPowerPercent", 100.0) * 0.01;
            Lib.RequestResource(vessel, "ElectricCharge", -power * tweakable * TimeWarp.fixedDeltaTime);
          }
          else if (module.moduleName == "ModuleRadioisotopeGenerator")
          {
            double mission_time = vessel.missionTime / (3600.0 * Lib.HoursInDay() * Lib.DaysInYear());
            PartModule generator = part_prefab.Modules[module.moduleName];
            double half_life = Lib.ReflectionValue<float>(generator, "HalfLife");
            double remaining = Math.Pow(2.0, (-mission_time) / half_life);
            double power = Lib.ReflectionValue<float>(generator, "BasePower");
            Lib.RequestResource(vessel, "ElectricCharge", -power * remaining * TimeWarp.fixedDeltaTime);
          }
          // KERBALISM modules
          else if (module.moduleName == "Scrubber") { Scrubber.BackgroundUpdate(vessel, part.flightID); }
          else if (module.moduleName == "Greenhouse") { Greenhouse.BackgroundUpdate(vessel, part.flightID); }
          else if (module.moduleName == "GravityRing") { GravityRing.BackgroundUpdate(vessel, part.flightID); }
          else if (module.moduleName == "Malfunction") { Malfunction.BackgroundUpdate(vessel, part.flightID); }
        }
      }
    }
  }