public KeyValuePair <bool, double> modReturn; // Return from DeviceEC public override void OnStart(StartState state) { // don't break tutorial scenarios & do something only in Flight scenario if (Lib.DisableScenario(this) || !Lib.IsFlight()) { return; } // cache list of modules module = part.FindModulesImplementing <PartModule>().FindLast(k => k.moduleName == type); // get energy from cache resources = ResourceCache.GetResource(vessel, "ElectricCharge"); hasEnergy = resources.Amount > double.Epsilon; // Force the update to run at least once lastBrokenState = !isBroken; hasEnergyChanged = !hasEnergy; hasFixedEnergyChanged = !hasEnergy; #if DEBUG // setup UI Fields["actualCost"].guiActive = true; Fields["isBroken"].guiActive = true; #endif }
void Indicator_ec(Panel p, Vessel v, VesselData vd) { #if !KSP15_16 if (v.vesselType == VesselType.DeployedScienceController) { return; } #endif ResourceInfo ec = ResourceCache.GetResource(v, "ElectricCharge"); Supply supply = Profile.supplies.Find(k => k.resource == "ElectricCharge"); double low_threshold = supply != null ? supply.low_threshold : 0.15; double depletion = ec.DepletionTime(); string tooltip = Lib.BuildString ( "<align=left /><b>", Local.Monitor_name, "\t", Local.Monitor_level, "\t" + Local.Monitor_duration, "</b>\n", //name"level"duration Lib.Color(Lib.BuildString("EC\t", Lib.HumanReadablePerc(ec.Level), "\t", depletion <= double.Epsilon ? Local.Monitor_depleted : Lib.HumanReadableDuration(depletion)), //"depleted" ec.Level <= 0.005 ? Lib.Kolor.Red : ec.Level <= low_threshold ? Lib.Kolor.Orange : Lib.Kolor.None) ); Texture2D image = ec.Level <= 0.005 ? Textures.battery_red : ec.Level <= low_threshold ? Textures.battery_yellow : Textures.battery_white; p.AddRightIcon(image, tooltip); }
// execute a script public void Execute(Vessel v, ScriptType type) { // do nothing if there is no EC left on the vessel ResourceInfo ec = ResourceCache.GetResource(v, "ElectricCharge"); if (ec.Amount <= double.Epsilon) { return; } // get the script Script script; if (scripts.TryGetValue(type, out script)) { // execute the script script.Execute(GetModuleDevices(v)); // show message to the user // - unless the script is empty (can happen when being edited) if (script.states.Count > 0 && v.KerbalismData().cfg_script) { Message.Post(Lib.BuildString(Localizer.Format("#KERBALISM_UI_scriptvessel"), " <b>", v.vesselName, "</b>")); } } }
private static bool FixedUpdatePrefix(KerbalismSentinel __instance) { if (__instance.isTrackingEnabled) { VesselData vd = __instance.vessel.KerbalismData(); if (!vd.Connection.linked || vd.Connection.rate < __instance.comms_rate) { __instance.isTracking = false; __instance.status = "Comms connection too weak"; return(false); } ResourceInfo ec = ResourceCache.GetResource(__instance.vessel, "ElectricCharge"); ec.Consume(__instance.ec_rate * Kerbalism.elapsed_s, ResourceBroker.Scanner); if (ec.Amount <= double.Epsilon) { __instance.isTracking = false; __instance.status = Local.Module_Experiment_issue4; // "no Electricity" return(false); } __instance.isTracking = true; } return(true); }
public static List <double> ResourceLevels(Vessel v, List <string> resource_names) { List <double> result = new List <double>(resource_names.Count); foreach (var name in resource_names) { result.Add(ResourceCache.GetResource(v, name).Level); } return(result); }
public static void Update(Vessel v) { // do nothing if not an eva kerbal if (!v.isEVA) { return; } // get KerbalEVA module KerbalEVA kerbal = Lib.FindModules <KerbalEVA>(v)[0]; // Stock KSP adds 5 units of monoprop to EVAs. We want to limit that amount // to whatever was available in the ship, so we don't magically create EVA prop out of nowhere if (Cache.HasVesselObjectsCache(v, "eva_prop")) { var quantity = Cache.VesselObjectsCache <double>(v, "eva_prop"); Cache.RemoveVesselObjectsCache(v, "eva_prop"); Lib.SetResource(kerbal.part, Lib.EvaPropellantName(), quantity, Lib.EvaPropellantCapacity()); } // get resource handler ResourceInfo ec = ResourceCache.GetResource(v, "ElectricCharge"); // determine if headlamps need ec // - not required if there is no EC capacity in eva kerbal (no ec supply in profile) // - not required if no EC cost for headlamps is specified (set by the user) bool need_ec = ec.Capacity > double.Epsilon && Settings.HeadLampsCost > double.Epsilon; // consume EC for the headlamps if (need_ec && kerbal.lampOn) { ec.Consume(Settings.HeadLampsCost * Kerbalism.elapsed_s, ResourceBroker.Light); } // force the headlamps on/off HeadLamps(kerbal, kerbal.lampOn && (!need_ec || ec.Amount > double.Epsilon)); // if dead if (IsDead(v)) { // enforce freezed state Freeze(kerbal); // disable modules DisableModules(kerbal); // remove plant flag action kerbal.flagItems = 0; } }
// return total radiation emitted in a vessel public static double Total(Vessel v) { // get resource cache ResourceInfo ec = ResourceCache.GetResource(v, "ElectricCharge"); double tot = 0.0; if (v.loaded) { foreach (var emitter in Lib.FindModules <Emitter>(v)) { if (ec.Amount > double.Epsilon || emitter.ec_rate <= double.Epsilon) { if (emitter.running) { if (emitter.radiation > 0) { tot += emitter.radiation * emitter.radiation_impact; } else { tot += emitter.radiation; // always account for full shielding effect } } } } } else { foreach (ProtoPartModuleSnapshot m in Lib.FindModules(v.protoVessel, "Emitter")) { if (ec.Amount > double.Epsilon || Lib.Proto.GetDouble(m, "ec_rate") <= double.Epsilon) { if (Lib.Proto.GetBool(m, "running")) { var rad = Lib.Proto.GetDouble(m, "radiation"); if (rad < 0) { tot += rad; } else { tot += rad * Lib.Proto.GetDouble(m, "radiation_factor"); } } } } } return(tot); }
public override void OnUpdate() { if (!Lib.IsFlight() || module == null) { return; } // get energy from cache resources = ResourceCache.GetResource(vessel, "ElectricCharge"); hasEnergy = resources.Amount > double.Epsilon; // Update UI only if hasEnergy has changed or if is broken state has changed if (isBroken) { if (isBroken != lastBrokenState) { lastBrokenState = isBroken; Update_UI(!isBroken); } } else if (hasEnergyChanged != hasEnergy) { Lib.LogDebugStack("Energy state has changed: {0}", hasEnergy); hasEnergyChanged = hasEnergy; lastBrokenState = false; // Update UI Update_UI(hasEnergy); } // Constantly Update UI for special modules if (isBroken) { Constant_OnGUI(!isBroken); } else { Constant_OnGUI(hasEnergy); } if (!hasEnergy || isBroken) { actualCost = 0; isConsuming = false; } else { isConsuming = GetIsConsuming(); } }
public void FixedUpdate() { // do nothing in the editor if (Lib.IsEditor()) { return; } // if has any animation playing, consume energy. if (Is_consuming_energy()) { // get resource handler ResourceInfo ec = ResourceCache.GetResource(vessel, "ElectricCharge"); // consume ec ec.Consume(ec_rate * Kerbalism.elapsed_s, ResourceBroker.GravityRing); } }
private void EvaluateStatus() { UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.VesselData.EvaluateStatus"); // determine if there is enough EC for a powered state powered = Lib.IsPowered(Vessel); // calculate crew info for the vessel crewCount = Lib.CrewCount(Vessel); crewCapacity = Lib.CrewCapacity(Vessel); // malfunction stuff malfunction = Reliability.HasMalfunction(Vessel); critical = Reliability.HasCriticalFailure(Vessel); // communications info connection = ConnectionInfo.Update(Vessel, powered, EnvBlackout); // habitat data habitatInfo.Update(Vessel); volume = Habitat.Tot_volume(Vessel); surface = Habitat.Tot_surface(Vessel); pressure = Math.Min(Habitat.Pressure(Vessel), habitatInfo.MaxPressure); evas = (uint)(Math.Max(0, ResourceCache.GetResource(Vessel, "Nitrogen").Amount - 330) / Settings.LifeSupportAtmoLoss); poisoning = Habitat.Poisoning(Vessel); shielding = Habitat.Shielding(Vessel); livingSpace = Habitat.Living_space(Vessel); volumePerCrew = Habitat.Volume_per_crew(Vessel); comforts = new Comforts(Vessel, EnvLanded, crewCount > 1, connection.linked && connection.rate > double.Epsilon); // data about greenhouses greenhouses = Greenhouse.Greenhouses(Vessel); Drive.GetCapacity(this, out drivesFreeSpace, out drivesCapacity); // solar panels data if (Vessel.loaded) { solarPanelsAverageExposure = SolarPanelFixer.GetSolarPanelsAverageExposure(solarPanelsExposure); solarPanelsExposure.Clear(); } UnityEngine.Profiling.Profiler.EndSample(); }
public static void Update(Vessel v) { // do nothing if not an eva kerbal if (!v.isEVA) { return; } // get KerbalEVA module KerbalEVA kerbal = Lib.FindModules <KerbalEVA>(v)[0]; // get resource handler ResourceInfo ec = ResourceCache.GetResource(v, "ElectricCharge"); // determine if headlamps need ec // - not required if there is no EC capacity in eva kerbal (no ec supply in profile) // - not required if no EC cost for headlamps is specified (set by the user) bool need_ec = ec.Capacity > double.Epsilon && Settings.HeadLampsCost > double.Epsilon; // consume EC for the headlamps if (need_ec && kerbal.lampOn) { ec.Consume(Settings.HeadLampsCost * Kerbalism.elapsed_s, ResourceBroker.Light); } // force the headlamps on/off HeadLamps(kerbal, kerbal.lampOn && (!need_ec || ec.Amount > double.Epsilon)); // if dead if (IsDead(v)) { // enforce freezed state Freeze(kerbal); // disable modules DisableModules(kerbal); // remove plant flag action kerbal.flagItems = 0; } }
public void SetupRescue(Vessel v) { // do nothing if no resource on rescue if (on_rescue <= double.Epsilon) { return; } // if the vessel has no capacity if (ResourceCache.GetResource(v, resource).Capacity <= double.Epsilon) { // find the first useful part Part p = v.parts.Find(k => k.CrewCapacity > 0 || k.FindModuleImplementing <KerbalEVA>() != null); // add capacity Lib.AddResource(p, resource, 0.0, on_rescue); } // add resource to the vessel ResourceCache.Produce(v, resource, on_rescue, ResourceBroker.Generic); }
void Indicator_supplies(Panel p, Vessel v, VesselData vd) { List <string> tooltips = new List <string>(); uint max_severity = 0; if (vd.CrewCount > 0) { foreach (Supply supply in Profile.supplies.FindAll(k => k.resource != "ElectricCharge")) { ResourceInfo res = ResourceCache.GetResource(v, supply.resource); double depletion = res.DepletionTime(); if (res.Capacity > double.Epsilon) { if (tooltips.Count == 0) { tooltips.Add(String.Format("<align=left /><b>{0,-18}\t" + Local.Monitor_level + "\t" + Local.Monitor_duration + "</b>", Local.Monitor_name)); //level"duration"name" } tooltips.Add(Lib.Color( String.Format("{0,-18}\t{1}\t{2}", supply.resource, Lib.HumanReadablePerc(res.Level), depletion <= double.Epsilon ? Local.Monitor_depleted : Lib.HumanReadableDuration(depletion)), //"depleted" res.Level <= 0.005 ? Lib.Kolor.Red : res.Level <= supply.low_threshold ? Lib.Kolor.Orange : Lib.Kolor.None )); uint severity = res.Level <= 0.005 ? 2u : res.Level <= supply.low_threshold ? 1u : 0; max_severity = Math.Max(max_severity, severity); } } } Texture2D image = max_severity == 2 ? Textures.box_red : max_severity == 1 ? Textures.box_yellow : Textures.box_white; p.AddRightIcon(image, string.Join("\n", tooltips.ToArray())); }
// return total radiation emitted in a vessel public static double Total(Vessel v) { // get resource cache ResourceInfo ec = ResourceCache.GetResource(v, "ElectricCharge"); double total = 0.0; if (v.loaded) { foreach (var shield in Lib.FindModules <PassiveShield>(v)) { if (ec.Amount > 0 || shield.ec_rate <= 0) { if (shield.deployed) { total += shield.radiation; } } } } else { foreach (ProtoPartModuleSnapshot m in Lib.FindModules(v.protoVessel, "PassiveShield")) { if (ec.Amount > 0 || Lib.Proto.GetDouble(m, "ec_rate") <= 0) { if (Lib.Proto.GetBool(m, "deployed")) { var rad = Lib.Proto.GetDouble(m, "radiation"); total += rad; } } } } return(total); }
public void Update() { // update RMB ui Events["Toggle"].guiName = deployed ? Local.Generic_RETRACT : Local.Generic_DEPLOY; Events["Toggle"].active = (deploy.Length > 0) && (part.FindModuleImplementing <Habitat>() == null) && !deploy_anim.Playing() && !waitRotation && ResourceCache.GetResource(vessel, "ElectricCharge").Amount > ec_rate; // in flight if (Lib.IsFlight()) { // if deployed if (deployed) { // if there is no ec if (ResourceCache.GetResource(vessel, "ElectricCharge").Amount < 0.01) { // pause rotate animation // - safe to pause multiple times Set_rotation(false); } // if there is enough ec instead and is not deploying else if (Should_start_rotation()) { // resume rotate animation // - safe to resume multiple times Set_rotation(true); } } // stop loop animation if exist and we are retracting else { // Call transform.stop() if it is rotating and the Stop method wasn't called. Set_rotation(false); } // 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 (rotateIsTransform && rotate_transf != null) { rotate_transf.DoSpin(); } if (counterWeightRotateIsTransform && counterWeightRotate_transf != null) { Lib.Log("KGR: counterweight do spin"); counterWeightRotate_transf.DoSpin(); } } }
/// <summary> /// Return vessel shielding factor. /// </summary> public static double Shielding(Vessel v) { return(Radiation.ShieldingEfficiency(ResourceCache.GetResource(v, "Shielding").Level)); }
// return waste level in a vessel atmosphere public static double Poisoning(Vessel v) { // the proportion of co2 in the atmosphere is simply the level of WasteAtmo return(ResourceCache.GetResource(v, "WasteAtmosphere").Level); }
// return normalized pressure in a vessel public static double Pressure(Vessel v) { // the pressure is simply the atmosphere level return(ResourceCache.GetResource(v, "Atmosphere").Level); }
// return habitat surface in a vessel in m^2 public static double Tot_surface(Vessel v) { // we use capacity: this mean that partially pressurized parts will still count, return(ResourceCache.GetResource(v, "Shielding").Capacity); }
// return habitat volume in a vessel in m^3 public static double Tot_volume(Vessel v) { // we use capacity: this mean that partially pressurized parts will still count, return(ResourceCache.GetResource(v, "Atmosphere").Capacity / 1e3); }
// trigger a random breakdown event public static void Breakdown(Vessel v, ProtoCrewMember c) { // constants const double res_penalty = 0.1; // proportion of food lost on 'depressed' and 'wrong_valve' // get a supply resource at random ResourceInfo res = null; if (Profile.supplies.Count > 0) { Supply supply = Profile.supplies[Lib.RandomInt(Profile.supplies.Count)]; res = ResourceCache.GetResource(v, supply.resource); } // compile list of events with condition satisfied List <KerbalBreakdown> events = new List <KerbalBreakdown> { KerbalBreakdown.mumbling //< do nothing, here so there is always something that can happen }; if (Lib.HasData(v)) { events.Add(KerbalBreakdown.fat_finger); } if (Reliability.CanMalfunction(v)) { events.Add(KerbalBreakdown.rage); } if (res != null && res.Amount > double.Epsilon) { events.Add(KerbalBreakdown.wrong_valve); } // choose a breakdown event KerbalBreakdown breakdown = events[Lib.RandomInt(events.Count)]; // generate message string text = ""; string subtext = ""; switch (breakdown) { case KerbalBreakdown.mumbling: text = "$ON_VESSEL$KERBAL has been in space for too long"; subtext = "Mumbling incoherently"; break; case KerbalBreakdown.fat_finger: text = "$ON_VESSEL$KERBAL is pressing buttons at random on the control panel"; subtext = "Science data has been lost"; break; case KerbalBreakdown.rage: text = "$ON_VESSEL$KERBAL is possessed by a blind rage"; subtext = "A component has been damaged"; break; case KerbalBreakdown.wrong_valve: text = "$ON_VESSEL$KERBAL opened the wrong valve"; subtext = res.ResourceName + " has been lost"; break; } // post message first so this one is shown before malfunction message Message.Post(Severity.breakdown, Lib.ExpandMsg(text, v, c), subtext); // trigger the event switch (breakdown) { case KerbalBreakdown.mumbling: break; // do nothing case KerbalBreakdown.fat_finger: Lib.RemoveData(v); break; case KerbalBreakdown.rage: Reliability.CauseMalfunction(v); break; case KerbalBreakdown.wrong_valve: res.Consume(res.Amount * res_penalty, "breakdown"); break; } // remove reputation if (HighLogic.CurrentGame.Mode == Game.Modes.CAREER) { Reputation.Instance.AddReputation(-Settings.KerbalBreakdownReputationPenalty, TransactionReasons.Any); } }
void FromEVA(GameEvents.FromToAction <Part, Part> data) { // contract configurator calls this event with both parts being the same when it adds a passenger if (data.from == data.to) { return; } // for each resource in the eva kerbal for (int i = 0; i < data.from.Resources.Count; ++i) { // get the resource PartResource res = data.from.Resources[i]; // add leftovers to the vessel data.to.RequestResource(res.resourceName, -res.amount); } #if !KSP15_16 && !KSP17 && !KSP18 && !KSP110 string evaPropName = Lib.EvaPropellantName(); if (evaPropName != "EVA Propellant") { KerbalEVA kerbalEVA = data.from.FindModuleImplementing <KerbalEVA>(); List <ProtoPartResourceSnapshot> propContainers = new List <ProtoPartResourceSnapshot>(); if (kerbalEVA.ModuleInventoryPartReference != null) { foreach (StoredPart storedPart in kerbalEVA.ModuleInventoryPartReference.storedParts.Values) { ProtoPartResourceSnapshot propContainer = storedPart.snapshot.resources.Find(p => p.resourceName == evaPropName); if (propContainer != null && propContainer.amount > 0.0) { propContainers.Add(propContainer); } } } if (propContainers.Count > 0) { // get vessel resources handler ResourceInfo evaPropOnVessel = ResourceCache.GetResource(data.to.vessel, evaPropName); double storageAvailable = evaPropOnVessel.Capacity - evaPropOnVessel.Amount; foreach (ProtoPartResourceSnapshot propContainer in propContainers) { double stored = Math.Min(propContainer.amount, storageAvailable); storageAvailable -= stored; evaPropOnVessel.Produce(stored, ResourceBroker.Generic); propContainer.amount = Math.Max(propContainer.amount - stored, 0.0); if (storageAvailable <= 0.0) { break; } } // Explaination : // - The ProtoCrewMember has already been removed from the EVA part and added to the vessel part // - It's inventory has already been saved // - The stock ModuleInventoryPart.RefillEVAPropellantOnBoarding() method has already been called // So to set the correct amount of EVA prop, we : // - Harmony patch ModuleInventoryPart.RefillEVAPropellantOnBoarding() so it doesn't refill anything // - Grab the ProtoCrewMember on the vessel part // - Call the SaveInventory() method again, with the modified amount on the inventory StoredPart data.to.protoModuleCrew[data.to.protoModuleCrew.Count - 1].SaveInventory(kerbalEVA.ModuleInventoryPartReference); } } #endif // merge drives data Drive.Transfer(data.from.vessel, data.to.vessel, true); // forget EVA vessel data Cache.PurgeVesselCaches(data.from.vessel); //Drive.Purge(data.from.vessel); // update boarded vessel this.OnVesselModified(data.to.vessel); // execute script data.to.vessel.KerbalismData().computer.Execute(data.to.vessel, ScriptType.eva_in); }
public static double ResourceLevel(Vessel v, string resource_name) { return(ResourceCache.GetResource(v, resource_name).Level); }
public static double ResourceCapacity(Vessel v, string resource_name) { return(ResourceCache.GetResource(v, resource_name).Capacity); }
public static double ResourceAmount(Vessel v, string resource_name) { return(ResourceCache.GetResource(v, resource_name).Amount); }
public void FixedUpdate() { // do nothing in the editor if (Lib.IsEditor()) { return; } // if enabled if (running) { // if a researcher is not required, or the researcher is present if (!researcher_cs || researcher_cs.Check(part.protoModuleCrew)) { // get next sample to analyze current_sample = NextSample(vessel); double rate = analysis_rate; if (researcher_cs) { int bonus = researcher_cs.Bonus(part.protoModuleCrew); double crew_gain = 1 + bonus * Settings.LaboratoryCrewLevelBonus; crew_gain = Lib.Clamp(crew_gain, 1, Settings.MaxLaborartoryBonus); rate *= crew_gain; } // if there is a sample to analyze if (current_sample != null) { // consume EC ec = ResourceCache.GetResource(vessel, "ElectricCharge"); ec.Consume(ec_rate * Kerbalism.elapsed_s, "laboratory"); // if there was ec // - comparing against amount in previous simulation step if (ec.Amount > double.Epsilon) { // analyze the sample status = Analyze(vessel, current_sample, rate * Kerbalism.elapsed_s); running = status == Status.RUNNING; } // if there was no ec else { status = Status.NO_EC; } } // if there is no sample to analyze else { status = Status.NO_SAMPLE; } } // if a researcher is required, but missing else { status = Status.NO_RESEARCHER; } } // if disabled else { status = Status.DISABLED; } }
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); } }