void indicator_supplies(Panel p, Vessel v, vessel_info vi) { List<string> tooltips = new List<string>(); uint max_severity = 0; if (vi.crew_count > 0) { var supplies = Profile.supplies.FindAll(k => k.resource != "ElectricCharge"); foreach(Supply supply in supplies) { resource_info res = ResourceCache.Info(v, supply.resource); if (res.capacity > double.Epsilon) { double depletion = res.Depletion(vi.crew_count); string deplete_str = depletion <= double.Epsilon ? ", depleted" : double.IsNaN(depletion) ? "" : Lib.BuildString(", deplete in <b>", Lib.HumanReadableDuration(depletion), "</b>"); tooltips.Add(Lib.BuildString(supply.resource, ": <b>", Lib.HumanReadablePerc(res.level), "</b>", deplete_str)); uint severity = res.level <= 0.005 ? 2u : res.level <= supply.low_threshold ? 1u : 0; max_severity = Math.Max(max_severity, severity); } } } Texture image = Icons.box_white; switch(max_severity) { case 0: image = Icons.box_white; break; case 1: image = Icons.box_yellow; break; case 2: image = Icons.box_red; break; } string tooltip = string.Join("\n", tooltips.ToArray()); p.icon(image, tooltip); }
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())); }
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; } } }
// 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); }
// return name of file being transmitted from vessel specified public static string Transmitting(Vessel v, bool linked) { // never transmitting if science system is disabled if (!Features.Science) { return(string.Empty); } // not transmitting if unlinked if (!linked) { return(string.Empty); } // not transmitting if there is no ec left if (ResourceCache.Info(v, "ElectricCharge").amount <= double.Epsilon) { return(string.Empty); } // get first file flagged for transmission, AND has a ts at least 5 seconds old or is > 0.001Mb in size foreach (var drive in DB.Vessel(v).drives.Values) { double now = Planetarium.GetUniversalTime(); foreach (var p in drive.files) { if (p.Value.send && (p.Value.ts + 3 < now || p.Value.size > 0.003)) { return(p.Key); } } } // no file flagged for transmission return(string.Empty); }
public void Update() { // in editor, merely update ui button label if (Lib.IsEditor()) { Events["Toggle"].guiName = Lib.StatusToggle(title, running ? "running" : "stopped"); } // if in flight, and the stock planet resource system is online if (Lib.IsFlight() && ResourceMap.Instance != null) { // sample abundance double abundance = SampleAbundance(); // determine if resource can be extracted issue = DetectIssue(abundance); // TODO: Change Review double ecLeft = ResourceCache.Info(part.vessel, "ElectricCharge").amount; // update ui Events["Toggle"].guiActive = deployed && ecLeft > double.Epsilon; Fields["Abundance"].guiActive = deployed && ecLeft > double.Epsilon; if (deployed) { string status = !running ? "stopped" : issue.Length == 0 ? "running" : Lib.BuildString("<color=yellow>", issue, "</color>"); Events["Toggle"].guiName = Lib.StatusToggle(title, status); Abundance = abundance > double.Epsilon ? Lib.HumanReadablePerc(abundance, "F2") : "none"; } } }
public void Execute(Vessel v, ScriptType type) { // do nothing if there is no EC left on the vessel Resource_Info ec = ResourceCache.Info(v, "ElectricCharge"); if (ec.amount <= double.Epsilon) { return; } // get the script if (scripts.TryGetValue(type, out Script script)) { // execute the script script.Execute(Boot(v)); // show message to the user // - unless the script is empty (can happen when being edited) if (script.states.Count > 0 && DB.Vessel(v).cfg_script) { Message.Post(Lib.BuildString("Script called on vessel <b>", v.vesselName, "</b>")); } } }
void Indicator_supplies(Panel p, Vessel v, Vessel_info vi) { List<string> tooltips = new List<string>(); uint max_severity = 0; if (vi.crew_count > 0) { foreach (Supply supply in Profile.supplies.FindAll(k => k.resource != "ElectricCharge")) { Resource_info res = ResourceCache.Info(v, supply.resource); double depletion = res.Depletion(vi.crew_count); if (res.capacity > double.Epsilon) { if (tooltips.Count == 0) tooltips.Add(String.Format("<align=left /><b>{0,-18}\tlevel\tduration</b>", "name")); tooltips.Add(Lib.BuildString ( res.level <= 0.005 ? "<color=#ff0000>" : res.level <= supply.low_threshold ? "<color=#ffff00>" : "<color=#cccccc>", String.Format("{0,-18}\t{1}\t{2}", supply.resource, Lib.HumanReadablePerc(res.level), depletion <= double.Epsilon ? "depleted" : Lib.HumanReadableDuration(depletion)), "</color>" )); 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 ? Icons.box_red : max_severity == 1 ? Icons.box_yellow : Icons.box_white; p.AddIcon(image, string.Join("\n", tooltips.ToArray())); }
void VesselDestroyed(Vessel v) { DB.vessels.Remove(Lib.VesselID(v)); // rescan the damn kerbals // - vessel crew is empty at destruction time // - we can't even use the flightglobal roster, because sometimes it isn't updated yet at this point HashSet <string> kerbals_alive = new HashSet <string>(); HashSet <string> kerbals_dead = new HashSet <string>(); foreach (Vessel ov in FlightGlobals.Vessels) { foreach (ProtoCrewMember c in Lib.CrewList(ov)) { kerbals_alive.Add(c.name); } } foreach (KeyValuePair <string, KerbalData> p in DB.Kerbals()) { if (!kerbals_alive.Contains(p.Key)) { kerbals_dead.Add(p.Key); } } foreach (string n in kerbals_dead) { // we don't know if the kerbal really is dead, or if it is just not currently assigned to a mission DB.KillKerbal(n, false); } // purge the caches ResourceCache.Purge(v); Drive.Purge(v); Cache.PurgeObjects(v); }
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 static void telemetry(this Panel p, Vessel v) { // if vessel doesn't exist anymore if (FlightGlobals.FindVessel(v.id) == null) return; // get info from the cache vessel_info vi = Cache.VesselInfo(v); // if not a valid vessel if (!vi.is_valid) return; // set metadata p.title(Lib.BuildString(Lib.Ellipsis(v.vesselName, 20), " <color=#cccccc>TELEMETRY</color>")); // time-out simulation if (p.timeout(vi)) return; // get vessel data VesselData vd = DB.Vessel(v); // get resources vessel_resources resources = ResourceCache.Get(v); // get crew var crew = Lib.CrewList(v); // draw the content render_crew(p, crew); render_greenhouse(p, vi); render_supplies(p, v, vi, resources); render_habitat(p, v, vi); render_environment(p, v, vi); // collapse eva kerbal sections into one if (v.isEVA) p.collapse("EVA SUIT"); }
void manageResqueMission(Vessel v) { // true if we detected this was a resque mission vessel bool detected = false; // deal with resque missions foreach (ProtoCrewMember c in v.GetVesselCrew()) { // get kerbal data kerbal_data kd = DB.KerbalData(c.name); // flag the kerbal as not resque at prelaunch if (v.situation == Vessel.Situations.PRELAUNCH) { kd.resque = 0; } // if the kerbal belong to a resque mission if (kd.resque == 1) { // remember it detected = true; // flag the kerbal as non-resque // note: enable life support mechanics for the kerbal kd.resque = 0; // show a message Message.Post(Lib.BuildString("We found <b>", c.name, "</b>"), Lib.BuildString((c.gender == ProtoCrewMember.Gender.Male ? "He" : "She"), "'s still alive!")); } } // gift resources if (detected) { var reslib = PartResourceLibrary.Instance.resourceDefinitions; var parts = Lib.GetPartsRecursively(v.rootPart); // give the vessel some monoprop string monoprop_name = v.isEVA ? "EVA Propellant" : detected_mods.RealFuels ? "Hydrazine" : "MonoPropellant"; foreach (var part in parts) { if (part.CrewCapacity > 0 || part.FindModuleImplementing <KerbalEVA>() != null) { if (part.Resources.list.Find(k => k.resourceName == monoprop_name) == null) { Lib.SetupResource(part, monoprop_name, 0.0, Settings.MonoPropellantOnResque); } break; } } ResourceCache.Produce(v, monoprop_name, Settings.MonoPropellantOnResque); // give the vessel some supplies foreach (Rule r in rules) { if (r.resource_name.Length == 0 || r.on_resque <= double.Epsilon || !reslib.Contains(r.resource_name)) { continue; } foreach (var part in parts) { if (part.CrewCapacity > 0 || part.FindModuleImplementing <KerbalEVA>() != null) { if (part.Resources.list.Find(k => k.resourceName == r.resource_name) == null) { Lib.SetupResource(part, r.resource_name, 0.0, r.on_resque); } break; } } ResourceCache.Produce(v, r.resource_name, r.on_resque); } } }
void toEVA(GameEvents.FromToAction <Part, Part> data) { // use Hydrazine instead of MonoPropellant if RealFuel is installed string monoprop_name = detected_mods.RealFuels ? "Hydrazine" : "MonoPropellant"; // determine if inside breathable atmosphere // note: the user can force the helmet + oxygen by pressing shift when going on eva bool breathable = Sim.Breathable(data.from.vessel) && !(Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)); // get total crew in the origin vessel double tot_crew = (double)data.from.vessel.GetVesselCrew().Count + 1.0; // EVA vessels start with 5 units of eva fuel, remove them data.to.RequestResource("EVA Propellant", 5.0); // determine how much MonoPropellant to get // note: never more that the 'share' of this kerbal double monoprop = Math.Min(ResourceCache.Info(data.from.vessel, monoprop_name).amount / tot_crew, Settings.MonoPropellantOnEVA); // get monoprop from the vessel monoprop = data.from.RequestResource(monoprop_name, monoprop); // transfer monoprop to the EVA kerbal data.to.RequestResource("EVA Propellant", -monoprop); // show warning if there isn't monoprop in the eva suit if (monoprop <= double.Epsilon && !Lib.Landed(data.from.vessel)) { Message.Post(Severity.danger, Lib.BuildString("There isn't any <b>", monoprop_name, "</b> in the EVA suit", "Don't let the ladder go!")); } // manage resources from rules foreach (Rule r in rules) { if (r.resource_name.Length == 0 || r.on_eva <= double.Epsilon) { continue; } // determine amount to take, never more that his own share double amount = Math.Min(ResourceCache.Info(data.from.vessel, r.resource_name).amount / tot_crew, r.on_eva); // deal with breathable modifier if (breathable && r.modifier.Contains("breathable")) { continue; } // remove resource from the vessel amount = data.from.RequestResource(r.resource_name, amount); // create new resource in the eva kerbal Lib.SetupResource(data.to, r.resource_name, amount, r.on_eva); } // get KerbalEVA KerbalEVA kerbal = data.to.FindModuleImplementing <KerbalEVA>(); // turn off headlamp light, to avoid stock bug that show the light for a split second when going on eva EVA.SetHeadlamp(kerbal, false); EVA.SetFlares(kerbal, false); // remove the helmet if inside breathable atmosphere // note: done in EVA::FixedUpdate(), but also done here avoid 'popping' of the helmet when going on eva EVA.SetHelmet(kerbal, !breathable); // remember if the kerbal has an helmet EVA.KerbalData(data.to.vessel).has_helmet = !breathable; // execute script on vessel computer if (DB.Ready()) { DB.VesselData(data.from.vessel.id).computer.execute("run", "auto/eva_out", string.Empty, data.from.vessel); } // mute messages for a couple seconds to avoid warning messages from the vessel resource amounts Message.MuteInternal(); base.StartCoroutine(CallbackUtil.DelayedCallback(2.0f, Message.UnmuteInternal)); // if vessel info is open, switch to the eva kerbal // note: for a single tick, the EVA vessel is not valid (sun_dist is zero) // this make IsVessel() return false, that in turn close the vessel info instantly // for this reason, we wait a small amount of time before switching the info window if (Info.IsOpen()) { Info.Open(data.to.vessel); } }
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, "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 Resource_recipe recipe = new Resource_recipe(part, "greenhouse"); foreach (ModuleResource input in resHandler.inputResources) { // WasteAtmosphere is primary combined input if (WACO2 && input.name == "WasteAtmosphere") { recipe.Input(input.name, vi.breathable ? 0.0 : input.rate * Kerbalism.elapsed_s, "CarbonDioxide"); } // CarbonDioxide is secondary combined input else if (WACO2 && input.name == "CarbonDioxide") { recipe.Input(input.name, vi.breathable ? 0.0 : input.rate * Kerbalism.elapsed_s, ""); } // if atmosphere is breathable disable WasteAtmosphere / CO2 else if (!WACO2 && (input.name == "CarbonDioxide" || input.name == "WasteAtmosphere")) { recipe.Input(input.name, vi.breathable ? 0.0 : input.rate, ""); } else { recipe.Input(input.name, input.rate * Kerbalism.elapsed_s); } } foreach (ModuleResource output in resHandler.outputResources) { // if atmosphere is breathable disable Oxygen if (output.name == "Oxygen") { recipe.Output(output.name, vi.breathable ? 0.0 : output.rate * Kerbalism.elapsed_s, true); } else { 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; 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 || Cache.VesselInfo(vessel).breathable) { continue; // skip if already checked or atmosphere is breathable } if (WACO2) { if (resources.Info(vessel, "WasteAtmosphere").amount <= double.Epsilon && resources.Info(vessel, "CarbonDioxide").amount <= double.Epsilon) { inputs = false; missing_res = "CarbonDioxide"; break; } dis_WACO2 = true; continue; } } 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; } }
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 FixedUpdate() { // basic sanity checks if (Lib.IsEditor()) { return; } if (!Cache.VesselInfo(vessel).is_valid) { return; } if (next_check > Planetarium.GetUniversalTime()) { return; } // get ec handler Resource_info ec = ResourceCache.Info(vessel, "ElectricCharge"); shrouded = part.ShieldedFromAirstream; issue = TestForIssues(vessel, ec, this, privateHdId, broken, remainingSampleMass, didPrepare, shrouded, last_subject_id); if (string.IsNullOrEmpty(issue)) { issue = TestForResources(vessel, resourceDefs, Kerbalism.elapsed_s, ResourceCache.Get(vessel)); } scienceValue = Science.Value(last_subject_id, 0); state = GetState(scienceValue, issue, recording, forcedRun); if (!string.IsNullOrEmpty(issue)) { next_check = Planetarium.GetUniversalTime() + Math.Max(3, Kerbalism.elapsed_s * 3); return; } var subject_id = Science.Generate_subject_id(experiment_id, vessel); if (last_subject_id != subject_id) { dataSampled = 0; forcedRun = false; } last_subject_id = subject_id; if (state != State.RUNNING) { return; } var exp = Science.Experiment(experiment_id); if (dataSampled >= exp.max_amount) { return; } // if experiment is active and there are no issues DoRecord(ec, subject_id); }
void FixedUpdate() { // remove control locks in any case Misc.clearLocks(); // do nothing if paused if (Lib.IsPaused()) { return; } // maintain elapsed_s, converting to double only once // and detect warp blending double fixedDeltaTime = TimeWarp.fixedDeltaTime; if (Math.Abs(fixedDeltaTime - elapsed_s) > double.Epsilon) { warp_blending = 0; } else { ++warp_blending; } elapsed_s = fixedDeltaTime; // evict oldest entry from vessel cache Cache.update(); // store info for oldest unloaded vessel double last_time = 0.0; Vessel last_v = null; vessel_info last_vi = null; VesselData last_vd = null; vessel_resources last_resources = null; // for each vessel foreach (Vessel v in FlightGlobals.Vessels) { // get vessel info from the cache vessel_info vi = Cache.VesselInfo(v); // set locks for active vessel if (v.isActiveVessel) { Misc.setLocks(v, vi); } // maintain eva dead animation and helmet state if (v.loaded && v.isEVA) { EVA.update(v); } // keep track of rescue mission kerbals, and gift resources to their vessels on discovery if (v.loaded && vi.is_vessel) { // manage rescue mission mechanics Misc.manageRescueMission(v); } // do nothing else for invalid vessels if (!vi.is_valid) { continue; } // get vessel data from db VesselData vd = DB.Vessel(v); // get resource cache vessel_resources resources = ResourceCache.Get(v); // if loaded if (v.loaded) { // show belt warnings Radiation.beltWarnings(v, vi, vd); // update storm data Storm.update(v, vi, vd, elapsed_s); // show signal warnings Signal.update(v, vi, vd, elapsed_s); // consume ec for transmission, and transmit science data Science.update(v, vi, vd, resources, elapsed_s); // apply rules Profile.Execute(v, vi, vd, resources, elapsed_s); // apply deferred requests resources.Sync(v, elapsed_s); // call automation scripts vd.computer.automate(v, vi, resources); // remove from unloaded data container unloaded.Remove(vi.id); } // if unloaded else { // get unloaded data, or create an empty one unloaded_data ud; if (!unloaded.TryGetValue(vi.id, out ud)) { ud = new unloaded_data(); unloaded.Add(vi.id, ud); } // accumulate time ud.time += elapsed_s; // maintain oldest entry if (ud.time > last_time) { last_time = ud.time; last_v = v; last_vi = vi; last_vd = vd; last_resources = resources; } } } // if the oldest unloaded vessel was selected if (last_v != null) { // show belt warnings Radiation.beltWarnings(last_v, last_vi, last_vd); // update storm data Storm.update(last_v, last_vi, last_vd, last_time); // show signal warnings Signal.update(last_v, last_vi, last_vd, last_time); // consume ec for transmission, and transmit science Science.update(last_v, last_vi, last_vd, last_resources, last_time); // apply rules Profile.Execute(last_v, last_vi, last_vd, last_resources, last_time); // simulate modules in background Background.update(last_v, last_vi, last_vd, last_resources, last_time); // apply deferred requests last_resources.Sync(last_v, last_time); // call automation scripts last_vd.computer.automate(last_v, last_vi, last_resources); // remove from unloaded data container unloaded.Remove(last_vi.id); } // update storm data for one body per-step if (storm_bodies.Count > 0) { storm_bodies.ForEach(k => k.time += elapsed_s); storm_data sd = storm_bodies[storm_index]; Storm.update(sd.body, sd.time); sd.time = 0.0; storm_index = (storm_index + 1) % storm_bodies.Count; } }
// 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.Info(v, "Shielding").capacity; }
// 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.Info(v, "WasteAtmosphere").level; }
// return shielding factor in a vessel public static double Shielding(Vessel v) { // the shielding factor is simply the level of shielding, scaled by the 'shielding efficiency' setting return(ResourceCache.Info(v, "Shielding").level *PreferencesStorm.Instance.shieldingEfficiency); }
public Comforts(Vessel v, bool env_firm_ground, bool env_not_alone, bool env_call_home) { // environment factors firm_ground = env_firm_ground; not_alone = env_not_alone; call_home = env_call_home; // if loaded if (v.loaded) { // scan parts for comfort foreach (Comfort c in Lib.FindModules <Comfort>(v)) { switch (c.bonus) { case "firm-ground": firm_ground = true; break; case "not-alone": not_alone = true; break; case "call-home": call_home = true; break; case "exercise": exercise = true; break; case "panorama": panorama = true; break; case "plants": plants = true; break; } } // scan parts for gravity ring if (ResourceCache.Info(v, "ElectricCharge").amount >= 0.01) { firm_ground |= Lib.HasModule <GravityRing>(v, k => k.deployed); } } // if not loaded else { // scan parts for comfort foreach (ProtoPartModuleSnapshot m in Lib.FindModules(v.protoVessel, "Comfort")) { switch (Lib.Proto.GetString(m, "bonus")) { case "firm-ground": firm_ground = true; break; case "not-alone": not_alone = true; break; case "call-home": call_home = true; break; case "exercise": exercise = true; break; case "panorama": panorama = true; break; case "plants": plants = true; break; } } // scan parts for gravity ring if (ResourceCache.Info(v, "ElectricCharge").amount >= 0.01) { firm_ground |= Lib.HasModule(v.protoVessel, "GravityRing", k => Lib.Proto.GetBool(k, "deployed")); } } // calculate factor factor = 0.1; if (firm_ground) { factor += PreferencesComfort.Instance.firmGround; } if (not_alone) { factor += PreferencesComfort.Instance.notAlone; } if (call_home) { factor += PreferencesComfort.Instance.callHome; } if (exercise) { factor += PreferencesComfort.Instance.exercise; } if (panorama) { factor += PreferencesComfort.Instance.panorama; } if (plants) { factor += PreferencesComfort.Instance.plants; } factor = Lib.Clamp(factor, 0.1, 1.0); }
public Vessel_info(Vessel v, UInt64 vessel_id, UInt64 inc) { // NOTE: anything used here can't in turn use cache, unless you know what you are doing // NOTE: you can't cache vessel position // at any point in time all vessel/body positions are relative to a different frame of reference // so comparing the current position of a vessel, with the cached one of another make no sense // associate with an unique incremental id this.inc = inc; // determine if this is a valid vessel is_vessel = Lib.IsVessel(v); if (!is_vessel) { return; } // determine if this is a rescue mission vessel is_rescue = Misc.IsRescueMission(v); if (is_rescue) { return; } // dead EVA are not valid vessels if (EVA.IsDead(v)) { return; } // shortcut for common tests is_valid = true; // generate id once id = vessel_id; // calculate crew info for the vessel crew_count = Lib.CrewCount(v); crew_capacity = Lib.CrewCapacity(v); // get vessel position Vector3d position = Lib.VesselPosition(v); // this should never happen again if (Vector3d.Distance(position, v.mainBody.position) < 1.0) { throw new Exception("Shit hit the fan for vessel " + v.vesselName); } // determine if there is enough EC for a powered state powered = ResourceCache.Info(v, "ElectricCharge").amount > double.Epsilon; // determine if in sunlight, calculate sun direction and distance sunlight = Sim.RaytraceBody(v, position, FlightGlobals.Bodies[0], out sun_dir, out sun_dist) ? 1.0 : 0.0; // environment stuff atmo_factor = Sim.AtmosphereFactor(v.mainBody, position, sun_dir); gamma_transparency = Sim.GammaTransparency(v.mainBody, v.altitude); underwater = Sim.Underwater(v); breathable = Sim.Breathable(v, underwater); landed = Lib.Landed(v); zerog = !landed && (!v.mainBody.atmosphere || v.mainBody.atmosphereDepth < v.altitude); if (v.mainBody.flightGlobalsIndex != 0 && TimeWarp.CurrentRate > 1000.0f) { highspeedWarp(v); } // temperature at vessel position temperature = Sim.Temperature(v, position, sunlight, atmo_factor, out solar_flux, out albedo_flux, out body_flux, out total_flux); temp_diff = Sim.TempDiff(temperature, v.mainBody, landed); // radiation radiation = Radiation.Compute(v, position, gamma_transparency, sunlight, out blackout, out magnetosphere, out inner_belt, out outer_belt, out interstellar); // extended atmosphere thermosphere = Sim.InsideThermosphere(v); exosphere = Sim.InsideExosphere(v); // malfunction stuff malfunction = Reliability.HasMalfunction(v); critical = Reliability.HasCriticalFailure(v); // communications info connection = new ConnectionInfo(v, powered, blackout); transmitting = Science.Transmitting(v, connection.linked && connection.rate > double.Epsilon); // habitat data volume = Habitat.Tot_volume(v); surface = Habitat.Tot_surface(v); pressure = Habitat.Pressure(v); evas = (uint)(Math.Max(0, ResourceCache.Info(v, "Nitrogen").amount - 330) / PreferencesLifeSupport.Instance.evaAtmoLoss); poisoning = Habitat.Poisoning(v); humidity = Habitat.Humidity(v); shielding = Habitat.Shielding(v); living_space = Habitat.Living_space(v); volume_per_crew = Habitat.Volume_per_crew(v); comforts = new Comforts(v, landed, crew_count > 1, connection.linked && connection.rate > double.Epsilon); // data about greenhouses greenhouses = Greenhouse.Greenhouses(v); // other stuff gravioli = Sim.Graviolis(v); }
// 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); } }
void ToEVA(GameEvents.FromToAction <Part, Part> data) { OnVesselModified(data.from.vessel); OnVesselModified(data.to.vessel); // get total crew in the origin vessel double tot_crew = Lib.CrewCount(data.from.vessel) + 1.0; // get vessel resources handler VesselResources resources = ResourceCache.Get(data.from.vessel); // setup supply resources capacity in the eva kerbal Profile.SetupEva(data.to); String prop_name = Lib.EvaPropellantName(); // for each resource in the kerbal for (int i = 0; i < data.to.Resources.Count; ++i) { // get the resource PartResource res = data.to.Resources[i]; // eva prop is handled differently if (res.resourceName == prop_name) { continue; } double quantity = Math.Min(resources.GetResource(data.from.vessel, res.resourceName).Amount / tot_crew, res.maxAmount); // remove resource from vessel quantity = data.from.RequestResource(res.resourceName, quantity); // add resource to eva kerbal data.to.RequestResource(res.resourceName, -quantity); } // take as much of the propellant as possible. just imagine: there are 1.3 units left, and 12 occupants // in the ship. you want to send out an engineer to fix the chemical plant that produces monoprop, // and have to get from one end of the station to the other with just 0.1 units in the tank... // nope. double evaPropQuantity = data.from.RequestResource(prop_name, Lib.EvaPropellantCapacity()); // We can't just add the monoprop here, because that doesn't always work. It might be related // to the fact that stock KSP wants to add 5 units of monoprop to new EVAs. Instead of fighting KSP here, // we just let it do it's thing and set our amount later in EVA.cs - which seems to work just fine. // don't put that into Cache.VesselInfo because that can be deleted before we get there Cache.SetVesselObjectsCache(data.to.vessel, "eva_prop", evaPropQuantity); // Airlock loss resources.Consume(data.from.vessel, "Nitrogen", Settings.LifeSupportAtmoLoss, ResourceBroker.Generic); // show warning if there is little or no EVA propellant in the suit if (evaPropQuantity <= 0.05 && !Lib.Landed(data.from.vessel)) { Message.Post(Severity.danger, Local.CallBackMsg_EvaNoMP.Format("<b>" + prop_name + "</b>"), Local.CallBackMsg_EvaNoMP2); //Lib.BuildString("There isn't any <<1>> in the EVA suit")"Don't let the ladder go!" } // turn off headlamp light, to avoid stock bug that show them for a split second when going on eva KerbalEVA kerbal = data.to.FindModuleImplementing <KerbalEVA>(); EVA.HeadLamps(kerbal, false); // execute script data.from.vessel.KerbalismData().computer.Execute(data.from.vessel, ScriptType.eva_out); }
// 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.Info(v, "Atmosphere").capacity; }
public void Update() { // update RMB ui Events["Toggle"].guiName = deployed ? Localizer.Format("#KERBALISM_Generic_RETRACT") : Localizer.Format("#KERBALISM_Generic_DEPLOY"); Events["Toggle"].active = (deploy.Length > 0) && (part.FindModuleImplementing<Habitat>() == null) && !deploy_anim.Playing() && !waitRotation && ResourceCache.Info(vessel, "ElectricCharge").amount > ec_rate; // in flight if (Lib.IsFlight()) { // if deployed if (deployed) { // if there is no ec if (ResourceCache.Info(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(); } }
// return normalized pressure in a vessel public static double pressure(Vessel v) { // the pressure is simply the atmosphere level return ResourceCache.Info(v, "Atmosphere").level; }
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); } } }
// return shielding factor in a vessel public static double shielding(Vessel v) { // the shielding factor is simply the level of shielding, scaled by the 'shielding efficiency' setting return ResourceCache.Info(v, "Shielding").level * Settings.ShieldingEfficiency; }
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); } }