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, "deploy"); } }
public static void BackgroundUpdate(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Deploy deploy, ResourceInfo ec, double elapsed_s) { if (deploy.isConsuming) { ec.Consume(deploy.extra_Cost * elapsed_s, "deploy"); } }
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); }
static void ProcessLight(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleLight light, ResourceInfo ec, double elapsed_s) { if (light.useResources && Lib.Proto.GetBool(m, "isOn")) { ec.Consume(light.resourceAmount * elapsed_s, ResourceBroker.Light); } }
public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, GravityRing ring, ResourceInfo 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, ResourceBroker.GravityRing); } }
static void ProcessStockLab(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleScienceConverter lab, ResourceInfo 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, ResourceBroker.ScienceLab); } }
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; } }
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); } }
public static void BackgroundUpdate(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Laboratory lab, ResourceInfo 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, "laboratory"); // 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 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 static void BackgroundUpdate(Vessel v, ProtoPartModuleSnapshot m, KerbalismSentinel prefab, VesselData vd, ResourceInfo ec, double elapsed_s) { if (Lib.Proto.GetBool(m, "isTrackingEnabled")) { if (!vd.Connection.linked || vd.Connection.rate < prefab.comms_rate) { Lib.Proto.Set(m, "isTracking", false); return; } ec.Consume(prefab.ec_rate * elapsed_s, ResourceBroker.Scanner); if (ec.Amount <= double.Epsilon) { Lib.Proto.Set(m, "isTracking", false); return; } Lib.Proto.Set(m, "isTracking", true); } }
// consume EC for transmission, and transmit science data public static void Update(Vessel v, VesselData vd, ResourceInfo ec, double elapsed_s) { // do nothing if science system is disabled if (!Features.Science) { return; } // consume ec for transmitters ec.Consume(vd.Connection.ec_idle * elapsed_s, ResourceBroker.CommsIdle); // avoid corner-case when RnD isn't live during scene changes // - this avoid losing science if the buffer reach threshold during a scene change if (HighLogic.CurrentGame.Mode != Game.Modes.SANDBOX && ResearchAndDevelopment.Instance == null) { return; } // clear list of files transmitted vd.filesTransmitted.Clear(); // check connection if (vd.Connection == null || !vd.Connection.linked || vd.Connection.rate <= 0.0 || !vd.deviceTransmit || ec.Amount < vd.Connection.ec_idle * elapsed_s) { // reset all files transmit rate foreach (Drive drive in Drive.GetDrives(vd, true)) { foreach (File f in drive.files.Values) { f.transmitRate = 0.0; } } // do nothing else return; } double totalTransmitCapacity = vd.Connection.rate * elapsed_s; double remainingTransmitCapacity = totalTransmitCapacity; GetFilesToTransmit(v, vd); if (xmitFiles.Count == 0) { return; } UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.Science.Update-Loop"); // traverse the list in reverse because : // - warp cache files are at the end, and they are always transmitted regerdless of transmit capacity // - others files are next, sorted in science value per MB ascending order for (int i = xmitFiles.Count - 1; i >= 0; i--) { XmitFile xmitFile = xmitFiles[i]; if (xmitFile.file.size == 0.0) { continue; } // always transmit everything in the warp cache if (!xmitFile.isInWarpCache && remainingTransmitCapacity <= 0.0) { break; } // determine how much data is transmitted double transmitted = xmitFile.isInWarpCache ? xmitFile.file.size : Math.Min(xmitFile.file.size, remainingTransmitCapacity); if (transmitted == 0.0) { continue; } // consume transmit capacity remainingTransmitCapacity -= transmitted; // get science value double xmitScienceValue = transmitted * xmitFile.sciencePerMB; // consume data in the file xmitFile.file.size -= transmitted; // remove science collected (ignoring final science value clamped to subject completion) xmitFile.file.subjectData.RemoveScienceCollectedInFlight(xmitScienceValue); // save transmit rate for the file, and add it to the VesselData list of files being transmitted if (xmitFile.isInWarpCache && xmitFile.realDriveFile != null) { xmitFile.realDriveFile.transmitRate = transmitted / elapsed_s; vd.filesTransmitted.Add(xmitFile.realDriveFile); } else { xmitFile.file.transmitRate = transmitted / elapsed_s; vd.filesTransmitted.Add(xmitFile.file); } if (xmitScienceValue > 0.0) { // add science to the subject (and eventually included subjects), trigger completion events, credit the science, return how much has been credited. vd.scienceTransmitted += xmitFile.file.subjectData.RetrieveScience(xmitScienceValue, true, v.protoVessel, xmitFile.file); } } UnityEngine.Profiling.Profiler.EndSample(); // consume EC cost for transmission (ec_idle is consumed above) double transmittedCapacity = totalTransmitCapacity - remainingTransmitCapacity; double transmissionCost = (vd.Connection.ec - vd.Connection.ec_idle) * (transmittedCapacity / (vd.Connection.rate * elapsed_s)); ec.Consume(transmissionCost * elapsed_s, ResourceBroker.CommsXmit); }
// 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); } }
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; } }
// consume EC for transmission, and transmit science data public static void Update(Vessel v, VesselData vd, ResourceInfo ec, double elapsed_s) { // do nothing if science system is disabled if (!Features.Science) { return; } // consume ec for transmitters ec.Consume(vd.Connection.ec_idle * elapsed_s, ResourceBroker.CommsIdle); // avoid corner-case when RnD isn't live during scene changes // - this avoid losing science if the buffer reach threshold during a scene change if (HighLogic.CurrentGame.Mode != Game.Modes.SANDBOX && ResearchAndDevelopment.Instance == null) { return; } // clear list of files transmitted vd.filesTransmitted.Clear(); // check connection if (vd.Connection == null || !vd.Connection.linked || vd.Connection.rate <= 0.0 || !vd.deviceTransmit || ec.Amount < vd.Connection.ec_idle * elapsed_s) { // reset all files transmit rate foreach (Drive drive in Drive.GetDrives(vd, true)) { foreach (File f in drive.files.Values) { f.transmitRate = 0.0; } } // do nothing else return; } double totalTransmitCapacity = vd.Connection.rate * elapsed_s; double remainingTransmitCapacity = totalTransmitCapacity; double scienceCredited = 0.0; GetFilesToTransmit(v, vd); if (xmitFiles.Count == 0) { return; } UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.Science.Update-Loop"); // traverse the list in reverse because : // - warp cache files are at the end, and they are always transmitted regerdless of transmit capacity // - others files are next, sorted in science value per MB ascending order for (int i = xmitFiles.Count - 1; i >= 0; i--) { XmitFile xmitFile = xmitFiles[i]; if (xmitFile.file.size == 0.0) { continue; } // always transmit everything in the warp cache if (!xmitFile.isInWarpCache && remainingTransmitCapacity <= 0.0) { break; } // determine how much data is transmitted double transmitted = xmitFile.isInWarpCache ? xmitFile.file.size : Math.Min(xmitFile.file.size, remainingTransmitCapacity); if (transmitted == 0.0) { continue; } // consume transmit capacity remainingTransmitCapacity -= transmitted; // get science value double xmitScienceValue = transmitted * xmitFile.sciencePerMB; // consume data in the file xmitFile.file.size -= transmitted; // remove science collected (ignoring final science value clamped to subject completion) xmitFile.file.subjectData.RemoveScienceCollectedInFlight(xmitScienceValue); // fire subject completed events int timesCompleted = xmitFile.file.subjectData.UpdateSubjectCompletion(xmitScienceValue); if (timesCompleted > 0) { SubjectXmitCompleted(xmitFile.file, timesCompleted, v); } // save transmit rate for the file, and add it to the VesselData list of files being transmitted if (xmitFile.isInWarpCache && xmitFile.realDriveFile != null) { xmitFile.realDriveFile.transmitRate = transmitted / elapsed_s; vd.filesTransmitted.Add(xmitFile.realDriveFile); } else { xmitFile.file.transmitRate = transmitted / elapsed_s; vd.filesTransmitted.Add(xmitFile.file); } // clamp science value to subject max value xmitScienceValue = Math.Min(xmitScienceValue, xmitFile.file.subjectData.ScienceRemainingToRetrieve); if (xmitScienceValue > 0.0) { // add credits scienceCredited += xmitScienceValue; // credit the subject xmitFile.file.subjectData.AddScienceToRnDSubject(xmitScienceValue); } } UnityEngine.Profiling.Profiler.EndSample(); vd.scienceTransmitted += scienceCredited; // consume EC cost for transmission (ec_idle is consumed above) double transmittedCapacity = totalTransmitCapacity - remainingTransmitCapacity; double transmissionCost = (vd.Connection.ec - vd.Connection.ec_idle) * (transmittedCapacity / vd.Connection.rate); ec.Consume(transmissionCost, ResourceBroker.CommsXmit); UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.Science.Update-AddScience"); // Add science points, but wait until we have at least 0.1 points to add because AddScience is VERY slow // We don't use "TransactionReasons.ScienceTransmission" because AddScience fire multiple events not meant to be fired continuously // this avoid many side issues (ex : chatterer transmit sound playing continously, strategia "+0.0 science" popup...) ScienceDB.uncreditedScience += scienceCredited; if (ScienceDB.uncreditedScience > 0.1) { if (GameHasRnD) { ResearchAndDevelopment.Instance.AddScience((float)ScienceDB.uncreditedScience, TransactionReasons.None); } ScienceDB.uncreditedScience = 0.0; } UnityEngine.Profiling.Profiler.EndSample(); }
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); } }
static void ProcessCryoTank(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule cryotank, VesselResources resources, ResourceInfo ec, double elapsed_s) { // Note. Currently background simulation of Cryotanks has an irregularity in that boiloff of a fuel type in a tank removes resources from all tanks // but at least some simulation is better than none ;) // get list of fuels, do nothing if no fuels IList fuels = Lib.ReflectionValue <IList>(cryotank, "fuels"); if (fuels == null) { return; } // is cooling available, note: comparing against amount in previous simulation step bool available = (Lib.Proto.GetBool(m, "CoolingEnabled") && ec.Amount > double.Epsilon); // get cooling cost double cooling_cost = Lib.ReflectionValue <float>(cryotank, "CoolingCost"); string fuel_name = ""; double amount = 0.0; double total_cost = 0.0; double boiloff_rate = 0.0; foreach (var item in fuels) { fuel_name = Lib.ReflectionValue <string>(item, "fuelName"); // if fuel_name is null, don't do anything if (fuel_name == null) { continue; } //get fuel resource ResourceInfo fuel = resources.GetResource(v, fuel_name); // if there is some fuel // note: comparing against amount in previous simulation step if (fuel.Amount > double.Epsilon) { // Try to find resource "fuel_name" in PartResources ProtoPartResourceSnapshot proto_fuel = p.resources.Find(k => k.resourceName == fuel_name); // If part doesn't have the fuel, don't do anything. if (proto_fuel == null) { continue; } // get amount in the part amount = proto_fuel.amount; // if cooling is enabled and there is enough EC if (available) { // calculate ec consumption total_cost += cooling_cost * amount * 0.001; } // if cooling is disabled or there wasn't any EC else { // get boiloff rate per-second boiloff_rate = Lib.ReflectionValue <float>(item, "boiloffRate") / 360000.0f; // let it boil off fuel.Consume(amount * (1.0 - Math.Pow(1.0 - boiloff_rate, elapsed_s)), ResourceBroker.Boiloff); } } } // apply EC consumption ec.Consume(total_cost * elapsed_s, ResourceBroker.Cryotank); }
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) VesselData vd = vessel.KerbalismData(); // get resource cache VesselResources resources = ResourceCache.Get(vessel); ResourceInfo ec = resources.GetResource(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 = vd.EnvSolarFluxTotal; 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, ResourceBroker.Greenhouse); } // 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 ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.Greenhouse); foreach (ModuleResource input in resHandler.inputResources) { // WasteAtmosphere is primary combined input if (WACO2 && input.name == "WasteAtmosphere") { recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * Kerbalism.elapsed_s, "CarbonDioxide"); } // CarbonDioxide is secondary combined input else if (WACO2 && input.name == "CarbonDioxide") { recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * Kerbalism.elapsed_s, ""); } // if atmosphere is breathable disable WasteAtmosphere / CO2 else if (!WACO2 && (input.name == "CarbonDioxide" || input.name == "WasteAtmosphere")) { recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate, ""); } else { recipe.AddInput(input.name, input.rate * Kerbalism.elapsed_s); } } foreach (ModuleResource output in resHandler.outputResources) { // if atmosphere is breathable disable Oxygen if (output.name == "Oxygen") { recipe.AddOutput(output.name, vd.EnvBreathable ? 0.0 : output.rate * Kerbalism.elapsed_s, true); } else { recipe.AddOutput(output.name, output.rate * Kerbalism.elapsed_s, true); } } resources.AddRecipe(recipe); // determine environment conditions bool lighting = natural + artificial >= light_tolerance; bool pressure = pressure_tolerance <= double.Epsilon || vd.Pressure >= pressure_tolerance; bool radiation = radiation_tolerance <= double.Epsilon || (1.0 - vd.Shielding) * vd.EnvHabitatRadiation < radiation_tolerance; // determine input resources conditions // - comparing against amounts in previous simulation step bool inputs = true; string missing_res = string.Empty; bool dis_WACO2 = false; foreach (ModuleResource input in resHandler.inputResources) { // combine WasteAtmosphere and CO2 if both exist if (input.name == "WasteAtmosphere" || input.name == "CarbonDioxide") { if (dis_WACO2 || vd.EnvBreathable) { continue; // skip if already checked or atmosphere is breathable } if (WACO2) { if (resources.GetResource(vessel, "WasteAtmosphere").Amount <= double.Epsilon && resources.GetResource(vessel, "CarbonDioxide").Amount <= double.Epsilon) { inputs = false; missing_res = "CarbonDioxide"; break; } dis_WACO2 = true; continue; } } if (resources.GetResource(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(Local.harvestedready_msg.Format("<b>" + vessel.vesselName + "</b>")); //Lib.BuildString("On <<1>> 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(Local.Greenhouse_resoucesmissing.Format(missing_res)) //"missing <<1>>" : !lighting ? Local.Greenhouse_issue1 //"insufficient lighting" : !pressure ? Local.Greenhouse_issue2 //"insufficient pressure" : !radiation ? Local.Greenhouse_issue3 //"excessive radiation" : string.Empty; } }
public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, KerbalismScansat kerbalismScansat, Part part_prefab, VesselData vd, ResourceInfo ec, double elapsed_s) { List <ProtoPartModuleSnapshot> scanners = Cache.VesselObjectsCache <List <ProtoPartModuleSnapshot> >(vessel, "scansat_" + p.flightID); if (scanners == null) { scanners = Lib.FindModules(p, "SCANsat"); if (scanners.Count == 0) { scanners = Lib.FindModules(p, "ModuleSCANresourceScanner"); } Cache.SetVesselObjectsCache(vessel, "scansat_" + p.flightID, scanners); } if (scanners.Count == 0) { return; } var scanner = scanners[0]; bool is_scanning = Lib.Proto.GetBool(scanner, "scanning"); if (is_scanning && kerbalismScansat.ec_rate > double.Epsilon) { ec.Consume(kerbalismScansat.ec_rate * elapsed_s, ResourceBroker.Scanner); } if (!Features.Science) { if (is_scanning && ec.Amount < double.Epsilon) { SCANsat.StopScanner(vessel, scanner, part_prefab); is_scanning = false; // remember disabled scanner vd.scansat_id.Add(p.flightID); // give the user some feedback if (vd.cfg_ec) { Message.Post(Local.Scansat_sensordisabled.Format("<b>" + vessel.vesselName + "</b>")); //Lib.BuildString("SCANsat sensor was disabled on <<1>>) } } else if (vd.scansat_id.Contains(p.flightID)) { // if there is enough ec // note: comparing against amount in previous simulation step // re-enable at 25% EC if (ec.Level > 0.25) { // re-enable the scanner SCANsat.ResumeScanner(vessel, m, part_prefab); is_scanning = true; // give the user some feedback if (vd.cfg_ec) { Message.Post(Local.Scansat_sensorresumed.Format("<b>" + vessel.vesselName + "</b>")); //Lib.BuildString("SCANsat sensor resumed operations on <<1>>) } } } // forget active scanners if (is_scanning) { vd.scansat_id.Remove(p.flightID); } return; } // if(!Feature.Science) string body_name = Lib.Proto.GetString(m, "body_name"); int sensorType = (int)Lib.Proto.GetUInt(m, "sensorType"); double body_coverage = Lib.Proto.GetDouble(m, "body_coverage"); double warp_buffer = Lib.Proto.GetDouble(m, "warp_buffer"); double new_coverage = SCANsat.Coverage(sensorType, vessel.mainBody); if (body_name == vessel.mainBody.name && new_coverage < body_coverage) { // SCANsat sometimes reports a coverage of 0, which is wrong new_coverage = body_coverage; } if (vessel.mainBody.name != body_name) { body_name = vessel.mainBody.name; body_coverage = new_coverage; } else { double coverage_delta = new_coverage - body_coverage; body_coverage = new_coverage; if (is_scanning) { ExperimentInfo expInfo = ScienceDB.GetExperimentInfo(kerbalismScansat.experimentType); SubjectData subject = ScienceDB.GetSubjectData(expInfo, vd.VesselSituations.GetExperimentSituation(expInfo)); if (subject == null) { return; } double size = expInfo.DataSize * coverage_delta / 100.0; // coverage is 0-100% size += warp_buffer; if (size > double.Epsilon) { // store what we can foreach (var d in Drive.GetDrives(vd)) { var available = d.FileCapacityAvailable(); var chunk = Math.Min(size, available); if (!d.Record_file(subject, chunk, true)) { break; } size -= chunk; if (size < double.Epsilon) { break; } } } if (size > double.Epsilon) { // we filled all drives up to the brim but were unable to store everything if (warp_buffer < double.Epsilon) { // warp buffer is empty, so lets store the rest there warp_buffer = size; size = 0; } else { // warp buffer not empty. that's ok if we didn't get new data if (coverage_delta < double.Epsilon) { size = 0; } // else we're scanning too fast. stop. } } // we filled all drives up to the brim but were unable to store everything // cancel scanning and annoy the user if (size > double.Epsilon || ec.Amount < double.Epsilon) { warp_buffer = 0; SCANsat.StopScanner(vessel, scanner, part_prefab); vd.scansat_id.Add(p.flightID); if (vd.cfg_ec) { Message.Post(Local.Scansat_sensordisabled.Format("<b>" + vessel.vesselName + "</b>")); //Lib.BuildString("SCANsat sensor was disabled on <<1>>) } } } else if (vd.scansat_id.Contains(p.flightID)) { if (ec.Level >= 0.25 && (vd.DrivesFreeSpace / vd.DrivesCapacity > 0.9)) { SCANsat.ResumeScanner(vessel, scanner, part_prefab); vd.scansat_id.Remove(p.flightID); if (vd.cfg_ec) { Message.Post(Local.Scansat_sensorresumed.Format("<b>" + vessel.vesselName + "</b>")); //Lib.BuildString("SCANsat sensor resumed operations on <<1>>) } } } } Lib.Proto.Set(m, "warp_buffer", warp_buffer); Lib.Proto.Set(m, "body_coverage", body_coverage); Lib.Proto.Set(m, "body_name", body_name); }
public static void BackgroundUpdate(Vessel v, ProtoPartModuleSnapshot m, Greenhouse g, VesselData vd, VesselResources 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 ResourceInfo ec = resources.GetResource(v, "ElectricCharge"); // calculate natural and artificial lighting double natural = vd.EnvSolarFluxTotal; 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, ResourceBroker.Greenhouse); } // 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 ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.Greenhouse); foreach (ModuleResource input in g.resHandler.inputResources) //recipe.Input(input.name, input.rate * elapsed_s); { // WasteAtmosphere is primary combined input if (g.WACO2 && input.name == "WasteAtmosphere") { recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * elapsed_s, "CarbonDioxide"); } // CarbonDioxide is secondary combined input else if (g.WACO2 && input.name == "CarbonDioxide") { recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * elapsed_s, ""); } // if atmosphere is breathable disable WasteAtmosphere / CO2 else if (!g.WACO2 && (input.name == "CarbonDioxide" || input.name == "WasteAtmosphere")) { recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate, ""); } else { recipe.AddInput(input.name, input.rate * elapsed_s); } } foreach (ModuleResource output in g.resHandler.outputResources) { // if atmosphere is breathable disable Oxygen if (output.name == "Oxygen") { recipe.AddOutput(output.name, vd.EnvBreathable ? 0.0 : output.rate * elapsed_s, true); } else { recipe.AddOutput(output.name, output.rate * elapsed_s, true); } } resources.AddRecipe(recipe); // determine environment conditions bool lighting = natural + artificial >= g.light_tolerance; bool pressure = g.pressure_tolerance <= 0 || vd.Pressure >= g.pressure_tolerance; bool radiation = g.radiation_tolerance <= 0 || vd.EnvRadiation * (1.0 - vd.Shielding) < g.radiation_tolerance; // determine inputs conditions // note: comparing against amounts in previous simulation step bool inputs = true; string missing_res = string.Empty; bool dis_WACO2 = false; foreach (ModuleResource input in g.resHandler.inputResources) { // combine WasteAtmosphere and CO2 if both exist if (input.name == "WasteAtmosphere" || input.name == "CarbonDioxide") { if (dis_WACO2 || vd.EnvBreathable) { continue; // skip if already checked or atmosphere is breathable } if (g.WACO2) { if (resources.GetResource(v, "WasteAtmosphere").Amount <= double.Epsilon && resources.GetResource(v, "CarbonDioxide").Amount <= double.Epsilon) { inputs = false; missing_res = "CarbonDioxide"; break; } dis_WACO2 = true; continue; } } if (resources.GetResource(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(Local.harvestedready_msg.Format("<b>" + v.vesselName + "</b>")); //Lib.BuildString("On <<1>> 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(Local.Greenhouse_resoucesmissing.Format(missing_res)) //"missing ", missing_res : !lighting ? Local.Greenhouse_issue1 //"insufficient lighting" : !pressure ? Local.Greenhouse_issue2 //"insufficient pressure" : !radiation ? Local.Greenhouse_issue3 //"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); } }
/// <summary> /// Execute the recipe and record deferred consumption/production for inputs/ouputs. /// This need to be called multiple times until left <= 0.0 for complete execution of the recipe. /// return true if recipe execution is completed, false otherwise /// </summary> private bool ExecuteRecipeStep(Vessel v, VesselResources 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]; ResourceInfo res = resources.GetResource(v, e.name); // handle combined inputs if (e.combined != null) { // is combined resource the primary if (e.combined != "") { Entry sec_e = inputs.Find(x => x.name.Contains(e.combined)); ResourceInfo sec = resources.GetResource(v, sec_e.name); double pri_worst = Lib.Clamp((res.Amount + res.Deferred) * e.inv_quantity, 0.0, worst_input); if (pri_worst > 0.0) { worst_input = pri_worst; } else { worst_input = Lib.Clamp((sec.Amount + sec.Deferred) * sec_e.inv_quantity, 0.0, worst_input); } } } else { 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 { ResourceInfo res = resources.GetResource(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]; ResourceInfo res = resources.GetResource(v, e.name); // handle combined inputs if (e.combined != null) { // is combined resource the primary if (e.combined != "") { Entry sec_e = inputs.Find(x => x.name.Contains(e.combined)); ResourceInfo sec = resources.GetResource(v, sec_e.name); double need = (e.quantity * worst_io) + (sec_e.quantity * worst_io); // do we have enough primary to satisfy needs, if so don't consume secondary if (res.Amount + res.Deferred >= need) { resources.Consume(v, e.name, need, name); } // consume primary if any available and secondary else { need -= res.Amount + res.Deferred; res.Consume(res.Amount + res.Deferred, name); sec.Consume(need, name); } } } else { res.Consume(e.quantity * worst_io, name); } } // produce outputs for (int i = 0; i < outputs.Count; ++i) { Entry e = outputs[i]; ResourceInfo res = resources.GetResource(v, e.name); res.Produce(e.quantity * worst_io, name); } // produce cures for (int i = 0; i < cures.Count; ++i) { Entry entry = cures[i]; List <RuleData> curingRules = new List <RuleData>(); foreach (ProtoCrewMember crew in v.GetVesselCrew()) { KerbalData kd = DB.Kerbal(crew.name); if (kd.sickbay.IndexOf(entry.combined + ",", StringComparison.Ordinal) >= 0) { curingRules.Add(kd.Rule(entry.name)); } } foreach (RuleData rd in curingRules) { rd.problem -= entry.quantity * worst_io / curingRules.Count; rd.problem = Math.Max(rd.problem, 0); } } // update amount left to execute left -= worst_io; // the recipe was executed, at least partially return(worst_io > double.Epsilon); }