static void ProcessLight(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleLight light, resource_info ec, double elapsed_s) { if (light.useResources && Lib.Proto.GetBool(m, "isOn")) { ec.Consume(light.resourceAmount * elapsed_s); } }
// execute the recipe public void Execute(Vessel v, vessel_resources resources) { // determine worst input ratio double worst_input = 1.0; foreach (var pair in inputs) { if (pair.Value > double.Epsilon) //< avoid division by zero { resource_info res = resources.Info(v, pair.Key); worst_input = Math.Min(worst_input, Math.Max(0.0, res.amount + res.deferred) / pair.Value); } } // consume inputs foreach (var pair in inputs) { resource_info res = resources.Info(v, pair.Key); res.Consume(pair.Value * worst_input); } // produce outputs foreach (var pair in outputs) { resource_info res = resources.Info(v, pair.Key); res.Produce(pair.Value * worst_input); } }
public static void BackgroundUpdate(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Deploy deploy, resource_info ec, double elapsed_s) { if (deploy.isConsuming) { ec.Consume(deploy.extra_Cost * elapsed_s); } }
// implement gravity ring mechanics for unloaded vessels public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, GravityRing ring, vessel_resources resources, double elapsed_s) { // get protomodule data float speed = Lib.Proto.GetFloat(m, "speed"); // get resource handler resource_info ec = resources.Info(vessel, "ElectricCharge"); // consume ec ec.Consume(ring.ec_rate * speed * elapsed_s * Reliability.Penalty(p, "GravityRing", 2.0)); // reset speed if there isn't enough ec // note: comparing against amount in previous simulation step if (ec.amount <= double.Epsilon) { speed = 0.0f; Lib.Proto.Set(m, "speed", speed); } // set entertainment // note: entertainmnent is only recomputed for loaded vessels, // so changing rate here does nothing until vessel is reloaded double rate = 1.0 + (ring.entertainment_rate - 1.0) * speed; Lib.Proto.Set(m, "rate", rate); }
public virtual void FixedUpdate() { if (!Lib.IsFlight() || module == null) { return; } if (isBroken) { if (isBroken != lastFixedBrokenState) { lastFixedBrokenState = isBroken; FixModule(!isBroken); } } else if (hasFixedEnergyChanged != hasEnergy) { hasFixedEnergyChanged = hasEnergy; lastFixedBrokenState = false; // Update module FixModule(hasEnergy); } // If isConsuming if (isConsuming && resources != null) { resources.Consume(actualCost * Kerbalism.elapsed_s); } }
public static void BackgroundUpdate(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Laboratory lab, resource_info ec, double elapsed_s) { // if enabled if (Lib.Proto.GetBool(m, "running")) { // if a researcher is not required, or the researcher is present CrewSpecs researcher_cs = new CrewSpecs(lab.researcher); if (!researcher_cs || researcher_cs.check(p.protoModuleCrew)) { // get sample to analyze string sample_filename = next_sample(v); // if there is a sample to analyze if (sample_filename.Length > 0) { // consume EC ec.Consume(lab.ec_rate * elapsed_s); // if there was ec // - comparing against amount in previous simulation step if (ec.amount > double.Epsilon) { // analyze the sample analyze(v, sample_filename, lab.analysis_rate * elapsed_s); } } } } }
public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, GravityRing ring, resource_info ec, double elapsed_s) { // if the module is either non-deployable or deployed if (ring.deploy.Length == 0 || Lib.Proto.GetBool(m, "deployed")) { // consume ec ec.Consume(ring.ec_rate * elapsed_s); } }
public static void BackgroundUpdate(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Emitter emitter, resource_info ec, double elapsed_s) { // if enabled, and EC is required if (Lib.Proto.GetBool(m, "running") && emitter.ec_rate > double.Epsilon) { // consume EC ec.Consume(emitter.ec_rate * elapsed_s); } }
static void ProcessLab(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleScienceConverter lab, resource_info ec, double elapsed_s) { // note: we are only simulating the EC consumption // note: there is no easy way to 'stop' the lab when there isn't enough EC // if active if (Lib.Proto.GetBool(m, "IsActivated")) { // consume ec ec.Consume(lab.powerRequirement * elapsed_s); } }
// implement gravity ring mechanics public void FixedUpdate() { // reset speed when not open if (!opened) speed = 0.0f; // hide the tweakable if not open this.Fields["speed"].guiActive = opened; this.Fields["speed"].guiActiveEditor = opened; // manage animation if (rotate != null) { // set rotating animation speed rotate[rotate_animation].speed = speed; // if its open but no animations are playing, start rotating if (opened) { bool playing = false; foreach(var anim in this.part.FindModelAnimators()) { playing |= anim.isPlaying; } if (!playing) rotate.Play(rotate_animation); } } // do nothing else in the editor if (HighLogic.LoadedSceneIsEditor) return; // get vessel info from the cache vessel_info vi = Cache.VesselInfo(vessel); // do nothing if vessel is invalid if (!vi.is_valid) return; // get resource cache vessel_resources resources = ResourceCache.Get(vessel); // get resource handler resource_info ec = resources.Info(vessel, "ElectricCharge"); // consume ec ec.Consume(ec_rate * speed * Kerbalism.elapsed_s * vi.time_dilation); // reset speed if there isn't enough ec // note: comparing against amount in previous simulation step if (ec.amount <= double.Epsilon) speed = 0.0f; // set entertainment rate = 1.0 + (entertainment_rate - 1.0) * speed; }
public static void BackgroundUpdate(Vessel v, ProtoPartModuleSnapshot m, Experiment exp, resource_info ec, double elapsed_s) { // if experiment is active if (Lib.Proto.GetBool(m, "recording")) { // detect conditions // - comparing against amount in previous step bool has_ec = ec.amount > double.Epsilon; bool has_operator = new CrewSpecs(exp.crew).check(v); string sit = Science.situation(v, exp.situations); // deduce issues string issue = string.Empty; if (sit.Length == 0) { issue = "invalid situation"; } else if (!has_operator) { issue = "no operator"; } else if (!has_ec) { issue = "missing <b>EC</b>"; } Lib.Proto.Set(m, "issue", issue); // if there are no issues if (issue.Length == 0) { // generate subject id string subject_id = Science.generate_subject(exp.experiment, v.mainBody, sit, Science.biome(v, sit), Science.multiplier(v, sit)); // record in drive if (exp.transmissible) { DB.Vessel(v).drive.record_file(subject_id, exp.data_rate * elapsed_s); } else { DB.Vessel(v).drive.record_sample(subject_id, exp.data_rate * elapsed_s); } // consume ec ec.Consume(exp.ec_rate * elapsed_s); } } }
static void ProcessCryoTank(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule simple_boiloff, vessel_resources resources, double elapsed_s) { // note: cryotank module already does a post-facto simulation of background boiling, and we could use that for the boiling // however, it also does simulate the ec consumption that way, so we have to disable the post-facto simulation IList fuels = Lib.PrivateField <IList>(simple_boiloff.GetType(), simple_boiloff, "fuels"); foreach (object cryoFuel in fuels) { string fuel_name = Lib.PrivateField <string> (cryoFuel.GetType(), cryoFuel, "fuelName"); float boiloff_rate_percent = Lib.PrivateField <float> (cryoFuel.GetType(), cryoFuel, "boiloffRate"); // get resource handlers resource_info ec = resources.Info(v, "ElectricCharge"); resource_info fuel = resources.Info(v, fuel_name); // if there is some fuel // note: comparing against amount in previous simulation step if (fuel.amount > double.Epsilon) { // get capacity in the part double capacity = p.resources.Find(k => k.resourceName == fuel_name).maxAmount; // if cooling is enabled and there was enough ec // note: comparing against amount in previous simulation step if (Lib.Proto.GetBool(m, "CoolingEnabled") && ec.amount > double.Epsilon) { // get cooling ec cost per 1000 units of fuel, per-second double cooling_cost = Lib.ReflectionValue <float>(simple_boiloff, "CoolingCost"); // consume ec ec.Consume(cooling_cost * capacity * 0.001 * elapsed_s); } // if there wasn't ec, or if cooling is disabled else { // get boiloff rate in proportion to fuel amount, per-second double boiloff_rate = boiloff_rate_percent * 0.00000277777; // let it boil off fuel.Consume(capacity * (1.0 - Math.Pow(1.0 - boiloff_rate, elapsed_s))); } } } // disable post-facto simulation Lib.Proto.Set(m, "LastUpdateTime", v.missionTime); }
public static void update(Vessel v) { // get kerbal data from db kerbal_data kd = KerbalData(v); // get KerbalEVA module KerbalEVA kerbal = v.FindPartModulesImplementing<KerbalEVA>()[0]; // show/hide helmet, play nice with KIS if (!Kerbalism.detected_mods.KIS) { SetHelmet(kerbal, kd.has_helmet); } // synchronize has_helmet state with KIS (for the headlights) else { kd.has_helmet = HasHelmet(kerbal); } // get resource handler resource_info ec = ResourceCache.Info(v, "ElectricCharge"); // consume EC for the headlamp if (kd.has_helmet && kerbal.lampOn) ec.Consume(Settings.HeadlightCost * Kerbalism.elapsed_s); //< ignore time dilation // force the headlamp lights on/off depending on ec amount left and if it has an helmet // synchronize helmet flares with headlamp state // support case when there is no ec rule (or no profile at all) bool b = kd.has_helmet && kerbal.lampOn && (ec.amount > double.Epsilon || ec.capacity <= double.Epsilon); SetHeadlamp(kerbal, b); SetFlares(kerbal, b); // if dead if (kd.eva_dead) { // enforce freezed state SetFreezed(kerbal); // remove plant flag action kerbal.flagItems = 0; // remove experiment actions (game engine keeps readding them) RemoveExperiments(kerbal); } }
public void FixedUpdate() { // do nothing in the editor if (Lib.IsEditor()) { return; } // if enabled, and there is ec consumption if (running && ec_rate > double.Epsilon) { // get resource cache resource_info ec = ResourceCache.Info(vessel, "ElectricCharge"); // consume EC ec.Consume(ec_rate * Kerbalism.elapsed_s); } }
public void FixedUpdate() { // do nothing in the editor if (Lib.IsEditor()) { return; } // if the module is either non-deployable or deployed if (deploy.Length == 0 || deployed) { // get resource handler resource_info ec = ResourceCache.Info(vessel, "ElectricCharge"); // consume ec ec.Consume(ec_rate * Kerbalism.elapsed_s); } }
public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Emitter emitter, resource_info ec, double elapsed_s) { // if there is enough EC // note: comparing against amount in previous simulation step if (ec.amount > double.Epsilon) { // get intensity double intensity = Lib.Proto.GetDouble(m, "intensity"); // consume EC ec.Consume(emitter.ec_rate * intensity * elapsed_s * Reliability.Penalty(p, "Emitter", 2.0)); } // else disable it else { Lib.Proto.Set(m, "intensity", 0.0); } }
public static void update(Vessel v) { // do nothing if not an eva kerbal if (!v.isEVA) return; // get KerbalEVA module KerbalEVA kerbal = Lib.FindModules<KerbalEVA>(v)[0]; // get resource handler resource_info ec = ResourceCache.Info(v, "ElectricCharge"); // determine if headlamps need ec // - not required if there is no EC capacity in eva kerbal (no ec supply in profile) // - not required if no EC cost for headlamps is specified (set by the user) bool need_ec = ec.capacity > double.Epsilon && Settings.HeadLampsCost > double.Epsilon; // consume EC for the headlamps if (need_ec && kerbal.lampOn) { ec.Consume(Settings.HeadLampsCost * Kerbalism.elapsed_s); } // force the headlamps on/off HeadLamps(kerbal, kerbal.lampOn && (!need_ec || ec.amount > double.Epsilon)); // if dead if (IsDead(v)) { // enforce freezed state Freeze(kerbal); // disable modules DisableModules(kerbal); // remove plant flag action kerbal.flagItems = 0; } }
public void FixedUpdate() { // in any scene: update the RMB ui Status = Lib.HumanReadableRadiationRate(Math.Abs(radiation) * intensity); // do nothing else in the editor if (HighLogic.LoadedSceneIsEditor) return; // if there is ec consumption if (ec_rate > double.Epsilon) { // get vessel info from the cache vessel_info vi = Cache.VesselInfo(vessel); // do nothing if vessel is invalid if (!vi.is_valid) return; // get resource cache resource_info ec = ResourceCache.Info(vessel, "ElectricCharge"); // get elapsed time double elapsed_s = Kerbalism.elapsed_s * vi.time_dilation; // if there is enough EC // note: comparing against amount in previous simulation step if (ec.amount > double.Epsilon) { // consume EC ec.Consume(ec_rate * intensity * elapsed_s); } // else disable it else { intensity = 0.0f; } } }
public void FixedUpdate() { // in flight if (Lib.IsFlight()) { // if we are transmitting using the stock system if (stream.transmitting()) { // get ec resource handler resource_info ec = ResourceCache.Info(vessel, "ElectricCharge"); // if we are still linked, and there is ec left if (CanTransmit() && ec.amount > double.Epsilon) { // compression factor // - used to avoid making the user wait too much for transmissions that // don't happen in background, while keeping transmission rates realistic const double compression = 16.0; // transmit using the data stream stream.update(DataRate * Kerbalism.elapsed_s * compression, vessel); // consume ec ec.Consume(DataResourceCost * Kerbalism.elapsed_s); } else { // abort transmission, return data to the vessel stream.abort(vessel); // inform the user ScreenMessages.PostScreenMessage("Transmission aborted", 5.0f, ScreenMessageStyle.UPPER_LEFT); } } } }
public void FixedUpdate() { // do nothing in the editor if (Lib.IsEditor()) { return; } // if enabled and not ready for harvest if (active && growth < 0.99) { // get vessel info from the cache // - if the vessel is not valid (eg: flagged as debris) then solar flux will be 0 and landed false (but that's okay) vessel_info vi = Cache.VesselInfo(vessel); // get resource cache vessel_resources resources = ResourceCache.Get(vessel); resource_info ec = resources.Info(vessel, "ElectricCharge"); // deal with corner cases when greenhouse is assembled using KIS if (double.IsNaN(growth) || double.IsInfinity(growth)) { growth = 0.0; } // calculate natural and artificial lighting natural = vi.solar_flux; artificial = Math.Max(light_tolerance - natural, 0.0); // consume EC for the lamps, scaled by artificial light intensity if (artificial > double.Epsilon) { ec.Consume(ec_rate * (artificial / light_tolerance) * Kerbalism.elapsed_s); } // reset artificial lighting if there is no ec left // - comparing against amount in previous simulation step if (ec.amount <= double.Epsilon) { artificial = 0.0; } // execute recipe resource_recipe recipe = new resource_recipe(); foreach (ModuleResource input in resHandler.inputResources) { recipe.Input(input.name, input.rate * Kerbalism.elapsed_s); } foreach (ModuleResource output in resHandler.outputResources) { recipe.Output(output.name, output.rate * Kerbalism.elapsed_s, true); } resources.Transform(recipe); // determine environment conditions bool lighting = natural + artificial >= light_tolerance; bool pressure = pressure_tolerance <= double.Epsilon || vi.pressure >= pressure_tolerance; bool radiation = radiation_tolerance <= double.Epsilon || vi.radiation * (1.0 - vi.shielding) < radiation_tolerance; // determine input resources conditions // - comparing against amounts in previous simulation step bool inputs = true; string missing_res = string.Empty; foreach (ModuleResource input in resHandler.inputResources) { if (resources.Info(vessel, input.name).amount <= double.Epsilon) { inputs = false; missing_res = input.name; break; } } // if growing if (lighting && pressure && radiation && inputs) { // increase growth growth += crop_rate * Kerbalism.elapsed_s; growth = Math.Min(growth, 1.0); // notify the user when crop can be harvested if (growth >= 0.99) { Message.Post(Lib.BuildString("On <b>", vessel.vesselName, "</b> the crop is ready to be harvested")); growth = 1.0; } } // update time-to-harvest tta = (1.0 - growth) / crop_rate; // update issues issue = !inputs?Lib.BuildString("missing ", missing_res) : !lighting ? "insufficient lighting" : !pressure ? "insufficient pressure" : !radiation ? "excessive radiation" : string.Empty; } }
// trigger a random breakdown event public static void Breakdown(Vessel v, ProtoCrewMember c) { // constants const double res_penalty = 0.1; // proportion of food lost on 'depressed' and 'wrong_valve' // get info Rule supply = supply_rules.Count > 0 ? supply_rules[Lib.RandomInt(supply_rules.Count)] : null; resource_info res = supply != null?ResourceCache.Info(v, supply.resource_name) : null; // compile list of events with condition satisfied List <KerbalBreakdown> events = new List <KerbalBreakdown>(); events.Add(KerbalBreakdown.mumbling); //< do nothing, here so there is always something that can happen if (Lib.CrewCount(v) > 1) { events.Add(KerbalBreakdown.argument); //< do nothing, add some variation to messages } if (Lib.HasData(v)) { events.Add(KerbalBreakdown.fat_finger); } if (Reliability.CanMalfunction(v)) { events.Add(KerbalBreakdown.rage); } if (supply != null && res.amount > double.Epsilon) { events.Add(KerbalBreakdown.depressed); events.Add(KerbalBreakdown.wrong_valve); } // choose a breakdown event KerbalBreakdown breakdown = events[Lib.RandomInt(events.Count)]; // generate message string text = ""; string subtext = ""; switch (breakdown) { case KerbalBreakdown.mumbling: text = "$ON_VESSEL$KERBAL has been in space for too long"; subtext = "Mumbling incoherently"; break; case KerbalBreakdown.argument: text = "$ON_VESSEL$KERBAL had an argument with the rest of the crew"; subtext = "Morale is degenerating at an alarming rate"; break; case KerbalBreakdown.fat_finger: text = "$ON_VESSEL$KERBAL is pressing buttons at random on the control panel"; subtext = "Science data has been lost"; break; case KerbalBreakdown.rage: text = "$ON_VESSEL$KERBAL is possessed by a blind rage"; subtext = "A component has been damaged"; break; case KerbalBreakdown.depressed: text = "$ON_VESSEL$KERBAL is not respecting the rationing guidelines"; subtext = supply.resource_name + " has been lost"; break; case KerbalBreakdown.wrong_valve: text = "$ON_VESSEL$KERBAL opened the wrong valve"; subtext = supply.resource_name + " has been lost"; break; } // post message first so this one is shown before malfunction message Message.Post(Severity.breakdown, Lib.ExpandMsg(text, v, c), subtext); // trigger the event switch (breakdown) { case KerbalBreakdown.mumbling: break; // do nothing case KerbalBreakdown.argument: break; // do nothing case KerbalBreakdown.fat_finger: Lib.RemoveData(v); break; case KerbalBreakdown.rage: Reliability.CauseMalfunction(v); break; case KerbalBreakdown.depressed: case KerbalBreakdown.wrong_valve: res.Consume(res.amount * res_penalty); break; } // remove reputation if (HighLogic.CurrentGame.Mode == Game.Modes.CAREER) { Reputation.Instance.AddReputation(-Settings.BreakdownReputationPenalty, TransactionReasons.Any); } }
public void FixedUpdate() { // do nothing in the editor if (Lib.IsEditor()) { return; } // if enabled if (running) { // if a researcher is not required, or the researcher is present if (!researcher_cs || researcher_cs.check(part.protoModuleCrew)) { // get next sample to analyze string sample_filename = next_sample(vessel); // if there is a sample to analyze if (sample_filename.Length > 0) { // consume EC resource_info ec = ResourceCache.Info(vessel, "ElectricCharge"); ec.Consume(ec_rate * Kerbalism.elapsed_s); // if there was ec // - comparing against amount in previous simulation step if (ec.amount > double.Epsilon) { // analyze the sample analyze(vessel, sample_filename, analysis_rate * Kerbalism.elapsed_s); // update status status = Science.experiment(sample_filename).name; } // if there was no ec else { // update status status = "<color=yellow>no electric charge</color>"; } } // if there is no sample to analyze else { // update status status = "no samples to analyze"; } } // if a researcher is required, but missing else { // update status status = Lib.BuildString("<color=yellow>", researcher_cs.warning(), "</color>"); } } // if disabled else { // update status status = "disabled"; } }
public void Execute(Vessel v, vessel_info vi, vessel_resources resources, double elapsed_s) { // store list of crew to kill List <ProtoCrewMember> deferred_kills = new List <ProtoCrewMember>(); // get input resource handler resource_info res = input.Length > 0 ? resources.Info(v, input) : null; // determine message variant uint variant = vi.temperature < Settings.SurvivalTemperature ? 0 : 1u; // get product of all environment modifiers double k = Modifiers.evaluate(v, vi, resources, modifiers); // for each crew foreach (ProtoCrewMember c in Lib.CrewList(v)) { // get kerbal data KerbalData kd = DB.Kerbal(c.name); // skip rescue kerbals if (kd.rescue) { continue; } // skip disabled kerbals if (kd.disabled) { continue; } // get kerbal property data from db RuleData rd = kd.Rule(name); // if continuous double step; if (interval <= double.Epsilon) { // influence consumption by elapsed time step = elapsed_s; } // if interval-based else { // accumulate time rd.time_since += elapsed_s; // determine number of steps step = Math.Floor(rd.time_since / interval); // consume time rd.time_since -= step * interval; // remember if a meal is consumed/produced in this simulation step res.meal_happened |= step > 0.99; if (output.Length > 0) { ResourceCache.Info(v, output).meal_happened |= step > 0.99; } } // if continuous, or if one or more intervals elapsed if (step > double.Epsilon) { // if there is a resource specified if (res != null && rate > double.Epsilon) { // determine amount of resource to consume double required = rate // consumption rate * k // product of environment modifiers * step; // seconds elapsed or number of steps // if there is no output if (output.Length == 0) { // simply consume (that is faster) res.Consume(required); } // if there is an output and output_only is false else if (!output_only) { // transform input into output resource // - rules always dump excess overboard (because it is waste) resource_recipe recipe = new resource_recipe(); recipe.Input(input, required); recipe.Output(output, required * ratio, true); resources.Transform(recipe); } // if output_only then do not consume input resource else { // simply produce (that is faster) resources.Produce(v, output, required); } } // degenerate: // - if the environment modifier is not telling to reset (by being zero) // - if the input threshold is reached if used // - if this rule is resource-less, or if there was not enough resource in the vessel if (input_threshold >= double.Epsilon) { if (res.amount >= double.Epsilon && res.capacity >= double.Epsilon) { trigger = res.amount / res.capacity >= input_threshold; } else { trigger = false; } } else { trigger = input.Length == 0 || res.amount <= double.Epsilon; } if (k > 0.0 && trigger) { rd.problem += degeneration // degeneration rate per-second or per-interval * k // product of environment modifiers * step // seconds elapsed or by number of steps * Variance(c, variance); // kerbal-specific variance } // else slowly recover else { rd.problem *= 1.0 / (1.0 + Math.Max(interval, 1.0) * step * 0.002); rd.problem = Math.Max(rd.problem, 0.0); } } // kill kerbal if necessary if (rd.problem >= fatal_threshold) { if (fatal_message.Length > 0) { Message.Post(breakdown ? Severity.breakdown : Severity.fatality, Lib.ExpandMsg(fatal_message, v, c, variant)); } if (breakdown) { // trigger breakdown event Misc.Breakdown(v, c); // move back between warning and danger level rd.problem = (warning_threshold + danger_threshold) * 0.5; // make sure next danger messagen is shown rd.message = 1; } else { deferred_kills.Add(c); } } // show messages else if (rd.problem >= danger_threshold && rd.message < 2) { if (danger_message.Length > 0) { Message.Post(Severity.danger, Lib.ExpandMsg(danger_message, v, c, variant)); } rd.message = 2; } else if (rd.problem >= warning_threshold && rd.message < 1) { if (warning_message.Length > 0) { Message.Post(Severity.warning, Lib.ExpandMsg(warning_message, v, c, variant)); } rd.message = 1; } else if (rd.problem < warning_threshold && rd.message > 0) { if (relax_message.Length > 0) { Message.Post(Severity.relax, Lib.ExpandMsg(relax_message, v, c, variant)); } rd.message = 0; } } // execute the deferred kills foreach (ProtoCrewMember c in deferred_kills) { Misc.Kill(v, c); } }
public void FixedUpdate() { // do nothing in the editor if (Lib.IsEditor()) { return; } // do nothing if vessel is invalid if (!Cache.VesselInfo(vessel).is_valid) { return; } // get ec handler resource_info ec = ResourceCache.Info(vessel, "ElectricCharge"); // if experiment is active if (recording) { // detect conditions // - comparing against amount in previous step bool has_ec = ec.amount > double.Epsilon; bool has_operator = operator_cs.check(vessel); string sit = Science.situation(vessel, situations); // deduce issues issue = string.Empty; if (sit.Length == 0) { issue = "invalid situation"; } else if (!has_operator) { issue = "no operator"; } else if (!has_ec) { issue = "missing <b>EC</b>"; } // if there are no issues if (issue.Length == 0) { // generate subject id string subject_id = Science.generate_subject(experiment, vessel.mainBody, sit, Science.biome(vessel, sit), Science.multiplier(vessel, sit)); // record in drive if (transmissible) { DB.Vessel(vessel).drive.record_file(subject_id, data_rate * Kerbalism.elapsed_s); } else { DB.Vessel(vessel).drive.record_sample(subject_id, data_rate * Kerbalism.elapsed_s); } // consume ec ec.Consume(ec_rate * Kerbalism.elapsed_s); } } }
public void FixedUpdate() { // do nothing in the editor if (Lib.IsEditor()) return; // if deployed if (deployed || (animBackwards && !deployed)) { // if there is no ec if (ResourceCache.Info(vessel, "ElectricCharge").amount < 0.01) { // pause rotate animation // - safe to pause multiple times if (rotateIsTransform && rotate_transf.IsRotating() && !rotate_transf.IsStopping()) rotate_transf.Stop(); else rotate_anim.pause(); } // if there is enough ec instead and is not deploying else if (!deploy_anim.playing()) { // resume rotate animation // - safe to resume multiple times if (rotateIsTransform && (!rotate_transf.IsRotating() || rotate_transf.IsStopping())) rotate_transf.Play(); else rotate_anim.resume(false); } } // stop loop animation if exist and we are retracting else { // Call transform.stop() if it is rotating and the Stop method wasn't called. if (rotateIsTransform && rotate_transf.IsRotating() && !rotate_transf.IsStopping()) rotate_transf.Stop(); else rotate_anim.stop(); } // When is not rotating if (waitRotation) { if (rotateIsTransform && !rotate_transf.IsRotating()) { // start retract animation in the correct direction, when is not rotating if (animBackwards) deploy_anim.play(deployed, false); else deploy_anim.play(!deployed, false); waitRotation = false; } else if (!rotateIsTransform && !rotate_anim.playing()) { if (animBackwards) deploy_anim.play(deployed, false); else deploy_anim.play(!deployed, false); waitRotation = false; } } // if has any animation playing, consume energy. if (deploy_anim.playing() || (rotate_transf.IsRotating() && !rotate_transf.IsStopping()) || rotate_anim.playing()) { // get resource handler resource_info ec = ResourceCache.Info(vessel, "ElectricCharge"); // consume ec ec.Consume(ec_rate * Kerbalism.elapsed_s); } if (rotateIsTransform && rotate_transf != null) rotate_transf.DoSpin(); }
// implement greenhouse mechanics public void FixedUpdate() { // set emissive intensity from lamp tweakable if (emissive_object.Length > 0) { foreach(var rdr in part.GetComponentsInChildren<UnityEngine.Renderer>()) { if (rdr.name == emissive_object) { rdr.material.SetColor("_EmissiveColor", new Color(lamps, lamps, lamps, 1.0f)); break; } } } // do nothing else in the editor if (HighLogic.LoadedSceneIsEditor) return; // get vessel info from the cache vessel_info vi = Cache.VesselInfo(vessel); // do nothing if vessel is invalid if (!vi.is_valid) return; // get resource cache vessel_resources resources = ResourceCache.Get(vessel); // get elapsed time double elapsed_s = Kerbalism.elapsed_s * vi.time_dilation; // when the greenhouse is assembled using KIS, the growth field is set to NaN // at that point it remain NaN forever, so we fix it // also, a report indicated -infinity in growth if (double.IsNaN(growth) || double.IsInfinity(growth)) growth = 0.0; // if lamp is on if (lamps > float.Epsilon) { // get resource handler resource_info ec = resources.Info(vessel, "ElectricCharge"); // consume ec ec.Consume(ec_rate * lamps * elapsed_s); // shut down the light if there isn't enough ec // note: comparing against amount at previous simulation step if (ec.amount <= double.Epsilon) lamps = 0.0f; } // determine lighting conditions // note: we ignore sun direction for gameplay reasons: else the user must reorient the greenhouse as the planets dance over time lighting = vi.solar_flux / Sim.SolarFluxAtHome() * (door_opened ? 1.0 : 0.0) + lamps; // if can use waste, and there is some lighting double waste_perc = 0.0; if (waste_name.Length > 0 && waste_rate > double.Epsilon && lighting > double.Epsilon) { // get resource handler resource_info waste = resources.Info(vessel, waste_name); // consume waste waste.Consume(waste_rate * elapsed_s); // determine waste bonus // note: comparing against amount from previous simulation step waste_perc = Math.Min(waste.amount / waste_rate, 1.0); } // determine growth bonus double growth_bonus = soil_bonus * (vi.landed ? 1.0 : 0.0) + waste_bonus * waste_perc; // grow the crop growing = growth_rate * (1.0 + growth_bonus) * lighting; growth += elapsed_s * growing; // if it is harvest time if (growth >= 1.0) { // reset growth growth = 0.0; // produce food resources.Produce(vessel, resource_name, harvest_size); // show a message to the user Message.Post(Lib.BuildString("On <color=FFFFFF>", vessel.vesselName, "</color> the crop harvest produced <color=FFFFFF>", harvest_size.ToString("F0"), " ", resource_name, "</color>")); // record first space harvest if (!vi.landed && DB.Ready()) DB.Landmarks().space_harvest = 1; } // set rmb ui status GrowthStatus = Lib.HumanReadablePerc(growth); LightStatus = Lib.HumanReadablePerc(lighting); WasteStatus = Lib.HumanReadablePerc(waste_perc); SoilStatus = vi.landed ? "yes" : "no"; TTAStatus = Lib.HumanReadableDuration(growing > double.Epsilon ? (1.0 - growth) / growing : 0.0); // enable/disable emergency harvest Events["EmergencyHarvest"].active = (growth >= 0.5); }
// implement greenhouse mechanics for unloaded vessels public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Greenhouse greenhouse, vessel_info info, vessel_resources resources, double elapsed_s) { // get protomodule data bool door_opened = Lib.Proto.GetBool(m, "door_opened"); double growth = Lib.Proto.GetDouble(m, "growth"); float lamps = Lib.Proto.GetFloat(m, "lamps"); double lighting = Lib.Proto.GetDouble(m, "lighting"); // if lamp is on if (lamps > float.Epsilon) { // get resource handler resource_info ec = resources.Info(vessel, "ElectricCharge"); // consume ec ec.Consume(greenhouse.ec_rate * lamps * elapsed_s * Reliability.Penalty(p, "Greenhouse", 2.0)); // shut down the light if there isn't enough ec // note: comparing against amount at previous simulation step if (ec.amount <= double.Epsilon) lamps = 0.0f; } // determine lighting conditions // note: we ignore sun direction for gameplay reasons: else the user must reorient the greenhouse as the planets dance over time // - natural light depend on: distance from sun, direct sunlight, door status // - artificial light depend on: lamps tweakable and ec available, door status lighting = info.solar_flux / Sim.SolarFluxAtHome() * (door_opened ? 1.0 : 0.0) + lamps; // if can use waste, and there is some lighting double waste_perc = 0.0; if (greenhouse.waste_name.Length > 0 && lighting > double.Epsilon) { // get resource handler resource_info waste = resources.Info(vessel, greenhouse.waste_name); // consume waste waste.Consume(greenhouse.waste_rate * elapsed_s); // determine waste bonus // note: comparing against amount from previous simulation step waste_perc = Math.Min(waste.amount / greenhouse.waste_rate, 1.0); } // determine growth bonus double growth_bonus = greenhouse.soil_bonus * (info.landed ? 1.0 : 0.0) + greenhouse.waste_bonus * waste_perc; // grow the crop double growing = greenhouse.growth_rate * (1.0 + growth_bonus) * lighting; growth += elapsed_s * growing; // if it is harvest time if (growth >= 1.0) { // reset growth growth = 0.0; // produce food resources.Produce(vessel, greenhouse.resource_name, greenhouse.harvest_size); // show a message to the user Message.Post(Lib.BuildString("On <color=FFFFFF>", vessel.vesselName, "</color> the crop harvest produced <color=FFFFFF>", greenhouse.harvest_size.ToString("F0"), " ", greenhouse.resource_name, "</color>")); // record first space harvest if (!info.landed && DB.Ready()) DB.Landmarks().space_harvest = 1; } // store data Lib.Proto.Set(m, "growth", growth); Lib.Proto.Set(m, "lamps", lamps); Lib.Proto.Set(m, "lighting", lighting); Lib.Proto.Set(m, "growth_diff", growing); }
public static void applyRules(Vessel v, vessel_info vi, vessel_data vd, vessel_resources resources, double elapsed_s) { // get crew List<ProtoCrewMember> crew = v.loaded ? v.GetVesselCrew() : v.protoVessel.GetVesselCrew(); // get breathable modifier double breathable = vi.breathable ? 0.0 : 1.0; // get temp diff modifier double temp_diff = v.altitude < 2000.0 && v.mainBody == FlightGlobals.GetHomeBody() ? 0.0 : Sim.TempDiff(vi.temperature); // for each rule foreach(Rule r in Kerbalism.rules) { // get resource handler resource_info res = r.resource_name.Length > 0 ? resources.Info(v, r.resource_name) : null; // if a resource is specified if (res != null) { // get data from db vmon_data vmon = DB.VmonData(v.id, r.name); // message obey user config bool show_msg = (r.resource_name == "ElectricCharge" ? vd.cfg_ec > 0 : vd.cfg_supply > 0); // no messages with no capacity if (res.capacity > double.Epsilon) { // manned/probe message variant uint variant = crew.Count > 0 ? 0 : 1u; // manage messages if (res.level <= double.Epsilon && vmon.message < 2) { if (r.empty_message.Length > 0 && show_msg) Message.Post(Severity.danger, Lib.ExpandMsg(r.empty_message, v, null, variant)); vmon.message = 2; } else if (res.level < r.low_threshold && vmon.message < 1) { if (r.low_message.Length > 0 && show_msg) Message.Post(Severity.warning, Lib.ExpandMsg(r.low_message, v, null, variant)); vmon.message = 1; } else if (res.level > r.low_threshold && vmon.message > 0) { if (r.refill_message.Length > 0 && show_msg) Message.Post(Severity.relax, Lib.ExpandMsg(r.refill_message, v, null, variant)); vmon.message = 0; } } } // for each crew foreach(ProtoCrewMember c in crew) { // get kerbal data kerbal_data kd = DB.KerbalData(c.name); // skip resque kerbals if (kd.resque == 1) continue; // skip disabled kerbals if (kd.disabled == 1) continue; // get supply data from db kmon_data kmon = DB.KmonData(c.name, r.name); // get product of all environment modifiers double k = 1.0; foreach(string modifier in r.modifier) { switch(modifier) { case "breathable": k *= breathable; break; case "temperature": k *= temp_diff; break; case "radiation": k *= vi.radiation * (1.0 - kd.shielding); break; case "qol": k /= QualityOfLife.Bonus(kd.living_space, kd.entertainment, vi.landed, vi.link.linked, vi.crew_count == 1); break; } } // if continuous double step; if (r.interval <= double.Epsilon) { // influence consumption by elapsed time step = elapsed_s; } // if interval-based else { // accumulate time kmon.time_since += elapsed_s; // determine number of steps step = Math.Floor(kmon.time_since / r.interval); // consume time kmon.time_since -= step * r.interval; // remember if a meal is consumed in this simulation step res.meal_consumed |= step > 0.99; } // if continuous, or if one or more intervals elapsed if (step > double.Epsilon) { // indicate if we must degenerate bool must_degenerate = true; // if there is a resource specified, and this isn't just a monitoring rule if (res != null && r.rate > double.Epsilon) { // determine amount of resource to consume double required = r.rate // rate per-second or per interval * k // product of environment modifiers * step; // seconds elapsed or number of steps // if there is no waste if (r.waste_name.Length == 0) { // simply consume (that is faster) res.Consume(required); } // if there is waste else { // transform resource into waste resource_recipe recipe = new resource_recipe(resource_recipe.rule_priority); recipe.Input(r.resource_name, required); recipe.Output(r.waste_name, required * r.waste_ratio); resources.Transform(recipe); } // reset degeneration when consumed, or when not required at all // note: evaluating amount from previous simulation step if (required <= double.Epsilon || res.amount > double.Epsilon) { // slowly recover instead of instant reset kmon.problem *= 1.0 / (1.0 + Math.Max(r.interval, 1.0) * step * 0.002); kmon.problem = Math.Max(kmon.problem, 0.0); // do not degenerate must_degenerate = false; } } // degenerate if this rule is resource-less, or if there was not enough resource in the vessel if (must_degenerate) { kmon.problem += r.degeneration // degeneration rate per-second or per-interval * k // product of environment modifiers * step // seconds elapsed or by number of steps * Variance(c, r.variance); // kerbal-specific variance } // determine message variant uint variant = vi.temperature < Settings.SurvivalTemperature ? 0 : 1u; // kill kerbal if necessary if (kmon.problem >= r.fatal_threshold) { if (r.fatal_message.Length > 0) Message.Post(r.breakdown ? Severity.breakdown : Severity.fatality, Lib.ExpandMsg(r.fatal_message, v, c, variant)); if (r.breakdown) { Kerbalism.Breakdown(v, c); kmon.problem = r.danger_threshold * 1.01; //< move back to danger threshold } else { Kerbalism.Kill(v, c); } } // show messages else if (kmon.problem >= r.danger_threshold && kmon.message < 2) { if (r.danger_message.Length > 0) Message.Post(Severity.danger, Lib.ExpandMsg(r.danger_message, v, c, variant)); kmon.message = 2; } else if (kmon.problem >= r.warning_threshold && kmon.message < 1) { if (r.warning_message.Length > 0) Message.Post(Severity.warning, Lib.ExpandMsg(r.warning_message, v, c, variant)); kmon.message = 1; } else if (kmon.problem < r.warning_threshold && kmon.message > 0) { if (r.relax_message.Length > 0) Message.Post(Severity.relax, Lib.ExpandMsg(r.relax_message, v, c, variant)); kmon.message = 0; } } } } }
static void ProcessScanner(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule scanner, Part part_prefab, vessel_data vd, resource_info ec, double elapsed_s) { // get ec consumption rate double power = SCANsat.EcConsumption(scanner); // if the scanner doesn't require power to operate, we aren't interested in simulating it if (power <= double.Epsilon) { return; } // get scanner state bool is_scanning = Lib.Proto.GetBool(m, "scanning"); // if its scanning if (is_scanning) { // consume ec ec.Consume(power * elapsed_s); // if there isn't ec // note: comparing against amount in previous simulation step if (ec.amount <= double.Epsilon) { // unregister scanner SCANsat.stopScanner(v, m, part_prefab, scanner); is_scanning = false; // remember disabled scanner vd.scansat_id.Add(p.flightID); // give the user some feedback if (vd.cfg_ec == 1) { Message.Post(Lib.BuildString("SCANsat sensor was disabled on <b>", v.vesselName, "</b>")); } } } // if it was disabled in background else if (vd.scansat_id.Contains(p.flightID)) { // if there is enough ec // note: comparing against amount in previous simulation step if (ec.level > 0.25) //< re-enable at 25% EC { // re-enable the scanner SCANsat.resumeScanner(v, m, part_prefab, scanner); is_scanning = true; // give the user some feedback if (vd.cfg_ec == 1) { Message.Post(Lib.BuildString("SCANsat sensor resumed operations on <b>", v.vesselName, "</b>")); } } } // forget active scanners if (is_scanning) { vd.scansat_id.Remove(p.flightID); } }
public static void BackgroundUpdate(Vessel v, ProtoPartModuleSnapshot m, Greenhouse g, vessel_info vi, vessel_resources resources, double elapsed_s) { // get protomodule data bool active = Lib.Proto.GetBool(m, "active"); double growth = Lib.Proto.GetDouble(m, "growth"); // if enabled and not ready for harvest if (active && growth < 0.99) { // get resource handler resource_info ec = resources.Info(v, "ElectricCharge"); // calculate natural and artificial lighting double natural = vi.solar_flux; double artificial = Math.Max(g.light_tolerance - natural, 0.0); // consume EC for the lamps, scaled by artificial light intensity if (artificial > double.Epsilon) { ec.Consume(g.ec_rate * (artificial / g.light_tolerance) * elapsed_s); } // reset artificial lighting if there is no ec left // note: comparing against amount in previous simulation step if (ec.amount <= double.Epsilon) { artificial = 0.0; } // execute recipe resource_recipe recipe = new resource_recipe(); foreach (ModuleResource input in g.resHandler.inputResources) { recipe.Input(input.name, input.rate * elapsed_s); } foreach (ModuleResource output in g.resHandler.outputResources) { recipe.Output(output.name, output.rate * elapsed_s, true); } resources.Transform(recipe); // determine environment conditions bool lighting = natural + artificial >= g.light_tolerance; bool pressure = g.pressure_tolerance <= double.Epsilon || vi.pressure >= g.pressure_tolerance; bool radiation = g.radiation_tolerance <= double.Epsilon || vi.radiation * (1.0 - vi.shielding) < g.radiation_tolerance; // determine inputs conditions // note: comparing against amounts in previous simulation step bool inputs = true; string missing_res = string.Empty; foreach (ModuleResource input in g.resHandler.inputResources) { if (resources.Info(v, input.name).amount <= double.Epsilon) { inputs = false; missing_res = input.name; break; } } // if growing if (lighting && pressure && radiation && inputs) { // increase growth growth += g.crop_rate * elapsed_s; growth = Math.Min(growth, 1.0); // notify the user when crop can be harvested if (growth >= 0.99) { Message.Post(Lib.BuildString("On <b>", v.vesselName, "</b> the crop is ready to be harvested")); growth = 1.0; } } // update time-to-harvest double tta = (1.0 - growth) / g.crop_rate; // update issues string issue = !inputs?Lib.BuildString("missing ", missing_res) : !lighting ? "insufficient lighting" : !pressure ? "insufficient pressure" : !radiation ? "excessive radiation" : string.Empty; // update protomodule data Lib.Proto.Set(m, "natural", natural); Lib.Proto.Set(m, "artificial", artificial); Lib.Proto.Set(m, "tta", tta); Lib.Proto.Set(m, "issue", issue); Lib.Proto.Set(m, "growth", growth); } }