// return temperature of a vessel public static double Temperature(Vessel v, Vector3d position, double sunlight, double atmo_factor, out double solar_flux, out double albedo_flux, out double body_flux, out double total_flux) { // get vessel body CelestialBody body = v.mainBody; // get solar radiation solar_flux = SolarFlux(SunDistance(position)) * sunlight * atmo_factor; // get albedo radiation albedo_flux = body.flightGlobalsIndex == 0 ? 0.0 : AlbedoFlux(body, position); // get cooling radiation from the body body_flux = body.flightGlobalsIndex == 0 ? 0.0 : BodyFlux(body, v.altitude); // calculate total flux total_flux = solar_flux + albedo_flux + body_flux + BackgroundFlux(); // calculate temperature double temp = BlackBodyTemperature(total_flux); // if inside atmosphere if (body.atmosphere && v.altitude < body.atmosphereDepth) { // calculate atmospheric temperature double atmo_temp = v.loaded ? v.rootPart.skinTemperature : body.GetTemperature(v.altitude); // mix between our temperature and the stock atmospheric model temp = Lib.Mix(atmo_temp, temp, Lib.Clamp(v.altitude / body.atmosphereDepth, 0.0, 1.0)); } // finally, return the temperature return(temp); }
// return temperature of a vessel public static double Temperature(Vessel v, Vector3d position, double solar_flux, out double albedo_flux, out double body_flux, out double total_flux) { // get vessel body CelestialBody body = v.mainBody; // get albedo radiation albedo_flux = Lib.IsSun(body) ? 0.0 : AlbedoFlux(body, position); // get cooling radiation from the body body_flux = Lib.IsSun(body) ? 0.0 : BodyFlux(body, v.altitude); // calculate total flux total_flux = solar_flux + albedo_flux + body_flux + BackgroundFlux(); // calculate temperature double temp = BlackBodyTemperature(total_flux); // if inside atmosphere if (body.atmosphere && v.altitude < body.atmosphereDepth) { // get atmospheric temperature double atmo_temp = body.GetTemperature(v.altitude); // mix between our temperature and the stock atmospheric model temp = Lib.Mix(atmo_temp, temp, Lib.Clamp(v.altitude / body.atmosphereDepth, 0.0, 1.0)); } // finally, return the temperature return(temp); }
// called every frame public void Update() { if (HighLogic.LoadedSceneIsEditor) return; // get info from cache vessel_info vi = Cache.VesselInfo(this.vessel); // do nothing if vessel is invalid if (!vi.is_valid) return; // set reading switch(type) { case "temperature": Status = Lib.HumanReadableTemp(vi.temperature); break; case "radiation": Status = vi.radiation > double.Epsilon ? Lib.HumanReadableRadiationRate(vi.radiation) : "nominal"; break; case "solar_flux": Status = Lib.HumanReadableFlux(vi.solar_flux); break; case "albedo_flux": Status = Lib.HumanReadableFlux(vi.albedo_flux); break; case "body_flux": Status = Lib.HumanReadableFlux(vi.body_flux); break; } // manage pin animation on geiger counter if (pinanim != null && type == "radiation") { pinanim["pinanim"].normalizedTime = Lib.Clamp(pinfc.Evaluate((float)(vi.radiation * 3600.0)), 0.0f, 1.0f); pinanim.Play(); } }
// produce or consume a resource on a vessel, irregardless if loaded or not public static double RequestResource(Vessel vessel, string resource_name, double quantity) { if (vessel.loaded) { return(vessel.rootPart.RequestResource(resource_name, quantity, ResourceFlowMode.ALL_VESSEL_BALANCE)); } else { double diff = quantity; double amount = 0.0; double capacity = 0.0; foreach (ProtoPartSnapshot part in vessel.protoVessel.protoPartSnapshots) { foreach (ProtoPartResourceSnapshot res in part.resources) { if (res.resourceName == resource_name && Convert.ToBoolean(res.resourceValues.GetValue("flowState"))) { amount = Convert.ToDouble(res.resourceValues.GetValue("amount")); capacity = Convert.ToDouble(res.resourceValues.GetValue("maxAmount")); double new_amount = Lib.Clamp(amount - diff, 0.0, capacity); res.resourceValues.SetValue("amount", new_amount.ToString()); diff -= amount - new_amount; if (Math.Abs(diff) <= double.Epsilon) { return(quantity); } } } } return(quantity - diff); } }
public ReliabilityInfo(ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Reliability module_prefab) { title = Lib.BuildString(p.partInfo.title, Lib.Color(" " + module_prefab.title, Lib.Kolor.LightGrey)); group = module_prefab.redundancy; broken = Lib.Proto.GetBool(m, "broken", false); critical = Lib.Proto.GetBool(m, "critical", false); partId = 0; need_maintenance = Lib.Proto.GetBool(m, "need_maintenance", false); bool quality = Lib.Proto.GetBool(m, "quality", false); if (module_prefab.rated_operation_duration > 0) { var operation_duration = Lib.Proto.GetDouble(m, "operation_duration", 0); rel_duration = operation_duration / Reliability.EffectiveDuration(quality, module_prefab.rated_operation_duration); rel_duration = Lib.Clamp(rel_duration, 0, 1); } if (module_prefab.rated_ignitions > 0) { var ignitions = Lib.Proto.GetInt(m, "ignitions", 0); rel_ignitions = (double)ignitions / Reliability.EffectiveDuration(quality, module_prefab.rated_ignitions); rel_ignitions = Lib.Clamp(rel_ignitions, 0, 1); } mtbf = Reliability.EffectiveMTBF(quality, module_prefab.mtbf); if (mtbf > 0) { var last_inspection = Lib.Proto.GetDouble(m, "last_inspection", 0); maintenance_after = last_inspection + mtbf * 0.5; } }
public ReliabilityInfo(Reliability module) { title = Lib.BuildString(module.part.partInfo.title, Lib.Color(" " + module.title, Lib.Kolor.LightGrey)); group = module.redundancy; broken = module.broken; critical = module.critical; partId = module.part.flightID; need_maintenance = module.needMaintenance; mtbf = Reliability.EffectiveMTBF(module.quality, module.mtbf); if (module.rated_operation_duration > 0) { rel_duration = module.operation_duration / Reliability.EffectiveDuration(module.quality, module.rated_operation_duration); rel_duration = Lib.Clamp(rel_duration, 0, 1); } if (module.rated_ignitions > 0) { rel_ignitions = (double)module.ignitions / Reliability.EffectiveIgnitions(module.quality, module.rated_ignitions); rel_ignitions = Lib.Clamp(rel_ignitions, 0, 1); } if (mtbf > 0) { maintenance_after = module.last_inspection + mtbf * 0.5; } }
// return the surface radiation for the body specified (used by body info panel) public static double ComputeSurface(CelestialBody b, double gamma_transparency) { // store stuff Space gsm; Vector3 p; float D; // transform to local space once Vector3d position = ScaledSpace.LocalToScaledSpace(b.position); // accumulate radiation double radiation = 0.0; CelestialBody body = b; while (body != null) { RadiationBody rb = Info(body); RadiationModel mf = rb.model; if (mf.Has_field()) { // generate radii-normalized GSM space gsm = Gsm_space(rb, true); // move the poing in GSM space p = gsm.Transform_in(position); // accumulate radiation and determine pause/belt flags if (mf.has_inner) { D = mf.Inner_func(p); radiation += Lib.Clamp(D / -0.0666f, 0.0f, 1.0f) * rb.radiation_inner; } if (mf.has_outer) { D = mf.Outer_func(p); radiation += Lib.Clamp(D / -0.0333f, 0.0f, 1.0f) * rb.radiation_outer; } if (mf.has_pause) { gsm = Gsm_space(rb, false); p = gsm.Transform_in(position); D = mf.Pause_func(p); radiation += Lib.Clamp(D / -0.1332f, 0.0f, 1.0f) * rb.radiation_pause; } } // avoid loops in the chain body = (body.referenceBody != null && body.referenceBody.referenceBody == body) ? null : body.referenceBody; } // add extern radiation radiation += PreferencesStorm.Instance.ExternRadiation; // clamp radiation to positive range // note: we avoid radiation going to zero by using a small positive value radiation = Math.Max(radiation, Nominal); // return radiation, scaled by gamma transparency if inside atmosphere return(radiation * gamma_transparency); }
// execute the recipe public bool Execute(Vessel v, Vessel_Resources resources) { // determine worst input ratio // - pure input recipes can just underflow double worst_input = left; if (outputs.Count > 0) { for (int i = 0; i < inputs.Count; ++i) { Entry e = inputs[i]; Resource_Info res = resources.Info(v, e.name); worst_input = Lib.Clamp((res.amount + res.deferred) * e.inv_quantity, 0.0, worst_input); } } // determine worst output ratio // - pure output recipes can just overflow double worst_output = left; if (inputs.Count > 0) { for (int i = 0; i < outputs.Count; ++i) { Entry e = outputs[i]; if (!e.dump) // ignore outputs that can dump overboard { Resource_Info res = resources.Info(v, e.name); worst_output = Lib.Clamp((res.capacity - (res.amount + res.deferred)) * e.inv_quantity, 0.0, worst_output); } } } // determine worst-io double worst_io = Math.Min(worst_input, worst_output); // consume inputs for (int i = 0; i < inputs.Count; ++i) { Entry e = inputs[i]; resources.Consume(v, e.name, e.quantity * worst_io); } // produce outputs for (int i = 0; i < outputs.Count; ++i) { Entry e = outputs[i]; resources.Produce(v, e.name, e.quantity * worst_io); } // update amount left to execute left -= worst_io; // the recipe was executed, at least partially return(worst_io > double.Epsilon); }
public float Outer_func(Vector3 p) { p.x *= p.x < 0.0f ? outer_extension : outer_compression; float q = Mathf.Sqrt(p.x * p.x + p.z * p.z) - outer_dist; float k = Lib.Mix(outer_border_start, outer_border_end, Lib.Clamp(q / outer_dist, 0.0f, 1.0f)); float d1 = Mathf.Sqrt(q * q + p.y * p.y) - outer_radius; float d2 = d1 + k; return(Mathf.Max(d1, -d2) + (outer_deform > 0.001 ? (Mathf.Sin(p.x * 5.0f) * Mathf.Sin(p.y * 7.0f) * Mathf.Sin(p.z * 6.0f)) * outer_deform : 0.0f)); }
public static double AdjustedRate(Harvester harvester, CrewSpecs engineer_cs, List <ProtoCrewMember> crew, double abundance) { // Bonus(..., -2): a level 0 engineer will alreaday add 2 bonus points jsut because he's there, // regardless of level. efficiency will raise further with higher levels. int bonus = engineer_cs.Bonus(crew, -2); double crew_gain = 1 + bonus * Settings.HarvesterCrewLevelBonus; crew_gain = Lib.Clamp(crew_gain, 1, Settings.MaxHarvesterBonus); return(harvester.rate * crew_gain * (abundance / harvester.abundance_rate)); }
// return cosmic radiation hitting the vessel, in rad/s public static double CosmicRadiation(Vessel v) { if (v.mainBody.flightGlobalsIndex == 0) { return(Settings.CosmicRadiation); } double magn_altitude = instance.bodies[v.mainBody.flightGlobalsIndex].magn_altitude; double magn_k = magn_altitude > double.Epsilon ? Lib.Clamp((v.altitude - magn_altitude) / (magn_altitude * Settings.MagnetosphereFalloff), 0.0, 1.0) : 1.0; return(Settings.CosmicRadiation * magn_k); }
private static double Rate(Vessel v, double chunkSize, double maxCapacity, double elapsed, Resource_info ec, double ec_rate, Vessel_resources resources, List <KeyValuePair <string, double> > resourceDefs) { double result = Lib.Clamp(maxCapacity / chunkSize, 0, 1); result = Math.Min(result, Lib.Clamp(ec.amount / (ec_rate * elapsed), 0, 1)); foreach (var p in resourceDefs) { var ri = resources.Info(v, p.Key); result = Math.Min(result, Lib.Clamp(ri.amount / (p.Value * elapsed), 0, 1)); } return(result); }
private static void RunProcessTick(Vessel v, double elapsed_s, double ec_produced, List <KeyValuePair <string, double> > resourcesProduced, double ec_consumed, List <KeyValuePair <string, double> > resourcesConsumed, Resource_info ec, Vessel_resources resources) { // evaluate process rate double rate = 1; if (ec_consumed < ec.amount) { rate = ec.amount / ec_consumed; } foreach (var consumed in resourcesConsumed) { var ri = resources.Info(v, consumed.Key); rate = Math.Min(rate, Lib.Clamp(ri.amount / (consumed.Value * elapsed_s), 0, 1)); } foreach (var produced in resourcesProduced) { var ri = resources.Info(v, produced.Key); var capacityAvailable = ri.capacity - ri.amount; var amountProduced = produced.Value * elapsed_s; if (capacityAvailable < amountProduced) { rate = Math.Min(rate, Lib.Clamp(capacityAvailable / amountProduced, 0, 1)); } } // produce/consume according to rate if (rate < double.Epsilon) { return; } ec.Consume(ec_consumed * elapsed_s * rate, "module process"); ec.Produce(ec_produced * elapsed_s * rate, "module process"); foreach (var consumed in resourcesConsumed) { resources.Info(v, consumed.Key).Consume(consumed.Value * elapsed_s * rate, "module process"); } foreach (var produced in resourcesProduced) { resources.Info(v, produced.Key).Produce(produced.Value * elapsed_s * rate, "module process"); } }
/// <summary> Return a number [-0.15 .. 1.05] that represents current solar activity, clamped to [0 .. 1] if clamp is true </summary> public double SolarActivity(bool clamp = true) { if (solar_cycle <= 0) { return(0); } var t = (solar_cycle_offset + Planetarium.GetUniversalTime()) / solar_cycle * 2 * Math.PI; // Math.Sin/Cos works with radians // this gives a pseudo-erratic curve, see https://www.desmos.com/calculator/q5flvzvxia // in range -0.15 .. 1.05 var r = (-Math.Cos(t) + Math.Sin(t * 75) / 5 + 0.9) / 2.0; if (clamp) { r = Lib.Clamp(r, 0.0, 1.0); } return(r); }
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 background_researcher_cs = new CrewSpecs(lab.researcher); if (!background_researcher_cs || background_researcher_cs.Check(p.protoModuleCrew)) { double rate = lab.analysis_rate; if (background_researcher_cs) { int bonus = background_researcher_cs.Bonus(p.protoModuleCrew); double crew_gain = 1 + bonus * Settings.LaboratoryCrewLevelBonus; crew_gain = Lib.Clamp(crew_gain, 1, Settings.MaxLaborartoryBonus); rate *= crew_gain; } // get sample to analyze background_sample = NextSample(v); // if there is a sample to analyze if (background_sample != null) { // consume EC ec.Consume(lab.ec_rate * elapsed_s); // if there was ec // - comparing against amount in previous simulation step if (ec.amount > double.Epsilon) { // analyze the sample var status = Analyze(v, background_sample, rate * elapsed_s); if (status != Status.RUNNING) { Lib.Proto.Set(m, "running", false); } } } } } }
public void Inspect() { // do nothing if something is wrong, or the eva kerbal is dead Vessel v = FlightGlobals.ActiveVessel; if (v == null || !v.isEVA || EVA.IsDead(v)) return; // if the kerbal isn't an engineer, show a message and do nothing if (v.GetVesselCrew()[0].trait != "Engineer") { Message.Post("Only <b>Engineers</b> can inspect parts"); return; } // evaluate at what point we are in the lifetime double time_k = Lib.Clamp((age - min_lifetime) / max_lifetime, 0.0, 1.0); if (time_k > 0.75) Message.Post("This is going to fail"); else if (time_k > 0.5) Message.Post("This is reaching its limit"); else if (time_k > 0.25) Message.Post("This is still okay"); else Message.Post("This is in good shape"); }
private static void ResourceUpdate(Vessel v, Harvester harvester, double min_abundance, double elapsed_s) { double abundance = SampleAbundance(v, harvester); if (abundance > min_abundance) { double rate = harvester.rate; // Bonus(..., -2): a level 0 engineer will alreaday add 2 bonus points jsut because he's there, // regardless of level. efficiency will raise further with higher levels. int bonus = engineer_cs.Bonus(v, -2); double crew_gain = 1 + bonus * Settings.HarvesterCrewLevelBonus; crew_gain = Lib.Clamp(crew_gain, 1, Settings.MaxHarvesterBonus); rate *= crew_gain; Resource_recipe recipe = new Resource_recipe(harvester.part); recipe.Input("ElectricCharge", harvester.ec_rate * elapsed_s); recipe.Output(harvester.resource, harvester.rate * (abundance/harvester.abundance_rate) * elapsed_s, false); ResourceCache.Transform(v, recipe); } }
public override void SetDifficultyPreset(GameParameters.Preset preset) { switch (preset) { case GameParameters.Preset.Easy: lifetime = false; stormFrequency = Settings.StormFrequency * 0.9f; stormRadiation = Settings.StormRadiation * 0.9f; shieldingEfficiency = Lib.Clamp(Settings.ShieldingEfficiency * 1.1f, 0.0f, 0.99f); break; case GameParameters.Preset.Normal: lifetime = false; stormFrequency = Settings.StormFrequency; stormRadiation = Settings.StormRadiation; shieldingEfficiency = Lib.Clamp(Settings.ShieldingEfficiency, 0.0f, 0.99f); break; case GameParameters.Preset.Moderate: lifetime = false; stormFrequency = Settings.StormFrequency * 1.3f; stormRadiation = Settings.StormRadiation * 1.2f; shieldingEfficiency = Lib.Clamp(Settings.ShieldingEfficiency * 0.9f, 0.0f, 0.99f); break; case GameParameters.Preset.Hard: lifetime = true; stormFrequency = Settings.StormFrequency * 1.5f; stormRadiation = Settings.StormRadiation * 1.5f; shieldingEfficiency = Lib.Clamp(Settings.ShieldingEfficiency * 0.8f, 0.0f, 0.99f); break; default: break; } }
// return living space factor in a vessel public static double Living_space(Vessel v) { // living space is the volume per-capita normalized against an 'ideal living space' and clamped in an acceptable range return(Lib.Clamp((Tot_volume(v) / Lib.CrewCount(v)) / PreferencesComfort.Instance.livingSpace, 0.1, 1.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); } }
public void save(int integer) { integer = Lib.Clamp(integer + 32, 32, 255); sb.Append((char)integer); }
public override void ctrl(double value) { ring.speed = (float)Lib.Clamp(value, 0.0, 1.0); if (!ring.opened) ring.Open(); }
// return the total environent radiation at position specified public static double Compute(Vessel v, Vector3d position, double gamma_transparency, double sunlight, out bool blackout, out bool magnetosphere, out bool inner_belt, out bool outer_belt, out bool interstellar) { // prepare out parameters blackout = false; magnetosphere = false; inner_belt = false; outer_belt = false; interstellar = false; // no-op when Radiation is disabled if (!Features.Radiation) { return(0.0); } // store stuff Space gsm; Vector3 p; float D; // transform to local space once position = ScaledSpace.LocalToScaledSpace(position); // accumulate radiation double radiation = 0.0; CelestialBody body = v.mainBody; while (body != null) { RadiationBody rb = Info(body); RadiationModel mf = rb.model; if (mf.Has_field()) { // generate radii-normalized GSM space gsm = Gsm_space(rb.body, FlightGlobals.Bodies[rb.reference]); // move the poing in GSM space p = gsm.Transform_in(position); // accumulate radiation and determine pause/belt flags if (mf.has_inner) { D = mf.Inner_func(p); radiation += Lib.Clamp(D / -0.0666f, 0.0f, 1.0f) * rb.radiation_inner; inner_belt |= D < 0.0f; } if (mf.has_outer) { D = mf.Outer_func(p); radiation += Lib.Clamp(D / -0.0333f, 0.0f, 1.0f) * rb.radiation_outer; outer_belt |= D < 0.0f; } if (mf.has_pause) { D = mf.Pause_func(p); radiation += Lib.Clamp(D / -0.1332f, 0.0f, 1.0f) * rb.radiation_pause; magnetosphere |= D < 0.0f && rb.body.flightGlobalsIndex != 0; //< ignore heliopause interstellar |= D > 0.0f && rb.body.flightGlobalsIndex == 0; //< outside heliopause } } // avoid loops in the chain body = (body.referenceBody != null && body.referenceBody.referenceBody == body) ? null : body.referenceBody; } // add extern radiation radiation += PreferencesStorm.Instance.ExternRadiation; // add emitter radiation radiation += Emitter.Total(v); // if there is a storm in progress if (Storm.InProgress(v)) { // inside a magnetopause (except heliosphere), blackout the signal // outside, add storm radiations modulated by sun visibility if (magnetosphere) { blackout = true; } else { radiation += PreferencesStorm.Instance.StormRadiation * sunlight; } } // clamp radiation to positive range // note: we avoid radiation going to zero by using a small positive value radiation = Math.Max(radiation, Nominal); // return radiation, scaled by gamma transparency if inside atmosphere return(radiation * gamma_transparency); }
public Comforts(Vessel v, bool env_firm_ground, bool env_not_alone, bool env_call_home) { // environment factors firm_ground = env_firm_ground; not_alone = env_not_alone; call_home = env_call_home; // if loaded if (v.loaded) { // scan parts for comfort foreach (Comfort c in Lib.FindModules <Comfort>(v)) { switch (c.bonus) { case "firm-ground": firm_ground = true; break; case "not-alone": not_alone = true; break; case "call-home": call_home = true; break; case "exercise": exercise = true; break; case "panorama": panorama = true; break; case "plants": plants = true; break; } } // scan parts for gravity ring if (ResourceCache.Info(v, "ElectricCharge").amount >= 0.01) { firm_ground |= Lib.HasModule <GravityRing>(v, k => k.deployed); } } // if not loaded else { // scan parts for comfort foreach (ProtoPartModuleSnapshot m in Lib.FindModules(v.protoVessel, "Comfort")) { switch (Lib.Proto.GetString(m, "bonus")) { case "firm-ground": firm_ground = true; break; case "not-alone": not_alone = true; break; case "call-home": call_home = true; break; case "exercise": exercise = true; break; case "panorama": panorama = true; break; case "plants": plants = true; break; } } // scan parts for gravity ring if (ResourceCache.Info(v, "ElectricCharge").amount >= 0.01) { firm_ground |= Lib.HasModule(v.protoVessel, "GravityRing", k => Lib.Proto.GetBool(k, "deployed")); } } // calculate factor factor = 0.1; if (firm_ground) { factor += PreferencesComfort.Instance.firmGround; } if (not_alone) { factor += PreferencesComfort.Instance.notAlone; } if (call_home) { factor += PreferencesComfort.Instance.callHome; } if (exercise) { factor += PreferencesComfort.Instance.exercise; } if (panorama) { factor += PreferencesComfort.Instance.panorama; } if (plants) { factor += PreferencesComfort.Instance.plants; } factor = Lib.Clamp(factor, 0.1, 1.0); }
public Comforts(List <Part> parts, bool env_firm_ground, bool env_not_alone, bool env_call_home) { // environment factors firm_ground = env_firm_ground; not_alone = env_not_alone; call_home = env_call_home; // for each parts foreach (Part p in parts) { // for each modules in part foreach (PartModule m in p.Modules) { // skip disabled modules if (!m.isEnabled) { continue; } // comfort if (m.moduleName == "Comfort") { Comfort c = m as Comfort; switch (c.bonus) { case "firm-ground": firm_ground = true; break; case "not-alone": not_alone = true; break; case "call-home": call_home = true; break; case "exercise": exercise = true; break; case "panorama": panorama = true; break; case "plants": plants = true; break; } } // gravity ring // - ignoring if ec is present or not here else if (m.moduleName == "GravityRing") { GravityRing ring = m as GravityRing; firm_ground |= ring.deployed; } } } // calculate factor factor = 0.1; if (firm_ground) { factor += PreferencesComfort.Instance.firmGround; } if (not_alone) { factor += PreferencesComfort.Instance.notAlone; } if (call_home) { factor += PreferencesComfort.Instance.callHome; } if (exercise) { factor += PreferencesComfort.Instance.exercise; } if (panorama) { factor += PreferencesComfort.Instance.panorama; } factor = Lib.Clamp(factor, 0.1, 1.0); }
public void Execute(Vessel v, VesselData vd, VesselResources resources, double elapsed_s) { // store list of crew to kill List <ProtoCrewMember> deferred_kills = new List <ProtoCrewMember>(); // get input resource handler ResourceInfo res = input.Length > 0 ? resources.GetResource(v, input) : null; // determine message variant uint variant = vd.EnvTemperature < Settings.LifeSupportSurvivalTemperature ? 0 : 1u; // get product of all environment modifiers double k = Modifiers.Evaluate(v, vd, resources, modifiers); bool lifetime_enabled = PreferencesRadiation.Instance.lifetime; // for each crew foreach (ProtoCrewMember c in Lib.CrewList(v)) { // get kerbal data KerbalData kd = DB.Kerbal(c.name); // skip rescue kerbals if (kd.rescue) { continue; } // skip disabled kerbals if (kd.disabled) { continue; } // get kerbal property data from db RuleData rd = kd.Rule(name); rd.lifetime = lifetime_enabled && lifetime; // influence consumption by elapsed time double step = elapsed_s; // if interval-based if (interval > 0.0) { // accumulate time rd.time_since += elapsed_s; // determine number of intervals that has passed (can be 2 or more if elapsed_s > interval * 2) step = Math.Floor(rd.time_since / interval); // consume time rd.time_since -= step * interval; } // if there is a resource specified if (res != null && rate > double.Epsilon) { // get rate including per-kerbal variance double resRate = rate // consumption rate * Variance(name, c, individuality) // kerbal-specific variance * k; // product of environment modifiers // determine amount of resource to consume double required = resRate * step; // seconds elapsed or interval amount // remember if a meal is consumed/produced in this simulation step if (interval > 0.0) { double ratePerStep = resRate / interval; res.UpdateIntervalRule(-required, -ratePerStep, name); if (output.Length > 0) { ResourceCache.GetResource(v, output).UpdateIntervalRule(required * ratio, ratePerStep * ratio, name); } } // if continuous, or if one or more intervals elapsed if (step > 0.0) { // if there is no output if (output.Length == 0) { // simply consume (that is faster) res.Consume(required, name); } // if there is an output else { // transform input into output resource // - rules always dump excess overboard (because it is waste) ResourceRecipe recipe = new ResourceRecipe(name); recipe.AddInput(input, required); recipe.AddOutput(output, required * ratio, true); resources.AddRecipe(recipe); } } } // if continuous, or if one or more intervals elapsed if (step > 0.0) { // degenerate: // - if the environment modifier is not telling to reset (by being zero) // - if this rule is resource-less, or if there was not enough resource in the vessel if (k > 0.0 && (input.Length == 0 || res.Amount <= double.Epsilon)) { rd.problem += degeneration // degeneration rate per-second or per-interval * k // product of environment modifiers * step // seconds elapsed or by number of steps * Variance(name, c, variance); // kerbal-specific variance } // else slowly recover else { rd.problem *= 1.0 / (1.0 + Math.Max(interval, 1.0) * step * 0.002); } } bool do_breakdown = false; if (breakdown) { // don't do breakdowns and don't show stress message if disabled if (!PreferencesComfort.Instance.stressBreakdowns) { return; } // stress level double breakdown_probability = rd.problem / warning_threshold; breakdown_probability = Lib.Clamp(breakdown_probability, 0.0, 1.0); // use the stupidity of a kerbal. // however, nobody is perfect - not even a kerbal with a stupidity of 0. breakdown_probability *= c.stupidity * 0.6 + 0.4; // apply the weekly error rate breakdown_probability *= PreferencesComfort.Instance.stressBreakdownRate; // now we have the probability for one failure per week, based on the // individual stupidity and stress level of the kerbal. breakdown_probability = (breakdown_probability * elapsed_s) / (Lib.DaysInYear * Lib.HoursInDay * 3600); if (breakdown_probability > Lib.RandomDouble()) { do_breakdown = true; // we're stressed out and just made a major mistake, this further increases the stress level... rd.problem += warning_threshold * 0.05; // add 5% of the warning treshold to current stress level } } // kill kerbal if necessary if (rd.problem >= fatal_threshold) { #if DEBUG || DEVBUILD Lib.Log("Rule " + name + " kills " + c.name + " at " + rd.problem + " " + degeneration + "/" + k + "/" + step + "/" + Variance(name, c, variance)); #endif if (fatal_message.Length > 0) { Message.Post(breakdown ? Severity.breakdown : Severity.fatality, Lib.ExpandMsg(fatal_message, v, c, variant)); } if (breakdown) { do_breakdown = true; // move back between warning and danger level rd.problem = (warning_threshold + danger_threshold) * 0.5; // make sure next danger message is shown rd.message = 1; } else { deferred_kills.Add(c); } } // show messages else if (rd.problem >= danger_threshold && rd.message < 2) { if (danger_message.Length > 0) { Message.Post(Severity.danger, Lib.ExpandMsg(danger_message, v, c, variant)); } rd.message = 2; } else if (rd.problem >= warning_threshold && rd.message < 1) { if (warning_message.Length > 0) { Message.Post(Severity.warning, Lib.ExpandMsg(warning_message, v, c, variant)); } rd.message = 1; } else if (rd.problem < warning_threshold && rd.message > 0) { if (relax_message.Length > 0) { Message.Post(Severity.relax, Lib.ExpandMsg(relax_message, v, c, variant)); } rd.message = 0; } if (do_breakdown) { // trigger breakdown event Misc.Breakdown(v, c); } } // execute the deferred kills foreach (ProtoCrewMember c in deferred_kills) { Misc.Kill(v, c); } }
public void FixedUpdate() { // do nothing in the editor if (Lib.IsEditor()) { return; } // if enabled if (running) { // if a researcher is not required, or the researcher is present if (!researcher_cs || researcher_cs.Check(part.protoModuleCrew)) { // get next sample to analyze current_sample = NextSample(vessel); double rate = analysis_rate; if (researcher_cs) { int bonus = researcher_cs.Bonus(part.protoModuleCrew); double crew_gain = 1 + bonus * Settings.LaboratoryCrewLevelBonus; crew_gain = Lib.Clamp(crew_gain, 1, Settings.MaxLaborartoryBonus); rate *= crew_gain; } // if there is a sample to analyze if (current_sample != null) { // consume EC ec = ResourceCache.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 status = Analyze(vessel, current_sample, rate * Kerbalism.elapsed_s); running = status == Status.RUNNING; } // if there was no ec else { status = Status.NO_EC; } } // if there is no sample to analyze else { status = Status.NO_SAMPLE; } } // if a researcher is required, but missing else { status = Status.NO_RESEARCHER; } } // if disabled else { status = Status.DISABLED; } }
/// <summary> /// Set the current sun observation quality (ranges from 0 to 1). this is /// the probability that the player will get a warning for an incoming CME /// </summary> /// <param name="quality">Quality.</param> public static void SetStormObservationQuality(float quality) { Storm.sun_observation_quality = Lib.Clamp(quality, 0.0f, 1.0f); }
// return living space factor in a vessel public static double living_space(Vessel v) { // living space is the volume per-capita normalized against an 'ideal living space' and clamped in an acceptable range return Lib.Clamp((tot_volume(v) / Lib.CrewCount(v)) / Settings.IdealLivingSpace, 0.1, 1.0); }
public override void ctrl(double value) { Lib.Proto.Set(ring, "speed", (float)Lib.Clamp(value, 0.0, 1.0)); Lib.Proto.Set(ring, "opened", true); }