public override void OnStart(StartState state) { // don't break tutorial scenarios if (Lib.DisableScenario(this)) { return; } // parse all setups from string data var archive = new ReadArchive(data); int count; archive.Load(out count); setups = new List <ConfigureSetup>(count); while (count-- > 0) { setups.Add(new ConfigureSetup(archive)); } // parse configuration from string data // - we avoid corner case when cfg was never set up (because craft was never in VAB) selected = new List <string>(); if (!string.IsNullOrEmpty(cfg)) { archive = new ReadArchive(cfg); archive.Load(out count); while (count-- > 0) { string s; archive.Load(out s); selected.Add(s); } } // parse previous configuration from string data // - we avoid corner case when prev_cfg was never set up (because craft was never in VAB) prev_selected = new List <string>(); if (!string.IsNullOrEmpty(prev_cfg)) { archive = new ReadArchive(prev_cfg); archive.Load(out count); while (count-- > 0) { string s; archive.Load(out s); prev_selected.Add(s); } } // default title to part name if (title.Length == 0) { title = Lib.PartName(part); } // parse crew specs reconfigure_cs = new CrewSpecs(reconfigure); // set toggle window button label Events["ToggleWindow"].guiName = Lib.BuildString("Configure <b>", title, "</b>"); // only show toggle in flight if this is reconfigurable Events["ToggleWindow"].active = Lib.IsEditor() || reconfigure_cs; // store configuration changes changes = new Dictionary <int, int>(); }
void Problem_Kerbals(List <ProtoCrewMember> crew, ref List <Texture> icons, ref List <string> tooltips) { UInt32 health_severity = 0; UInt32 stress_severity = 0; foreach (ProtoCrewMember c in crew) { // get kerbal data KerbalData kd = DB.Kerbal(c.name); // skip disabled kerbals if (kd.disabled) { continue; } foreach (Rule r in Profile.rules) { RuleData rd = kd.Rule(r.name); if (rd.problem > r.danger_threshold) { if (!r.breakdown) { health_severity = Math.Max(health_severity, 2); } else { stress_severity = Math.Max(stress_severity, 2); } tooltips.Add(Lib.BuildString(c.name, ": <b>", r.name, "</b>")); } else if (rd.problem > r.warning_threshold) { if (!r.breakdown) { health_severity = Math.Max(health_severity, 1); } else { stress_severity = Math.Max(stress_severity, 1); } tooltips.Add(Lib.BuildString(c.name, ": <b>", r.name, "</b>")); } } } if (health_severity == 1) { icons.Add(Icons.health_yellow); } else if (health_severity == 2) { icons.Add(Icons.health_red); } if (stress_severity == 1) { icons.Add(Icons.brain_yellow); } else if (stress_severity == 2) { icons.Add(Icons.brain_red); } }
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; } }
public static void PartPrefabsTweaks() { foreach (AvailablePart ap in PartLoader.LoadedPartsList) { // scale part icons of the radial container variants switch (ap.name) { case "kerbalism-container-radial-small": ap.iconPrefab.transform.GetChild(0).localScale *= 0.60f; ap.iconScale *= 0.60f; break; case "kerbalism-container-radial-medium": ap.iconPrefab.transform.GetChild(0).localScale *= 0.85f; ap.iconScale *= 0.85f; break; case "kerbalism-container-radial-big": ap.iconPrefab.transform.GetChild(0).localScale *= 1.10f; ap.iconScale *= 1.10f; break; case "kerbalism-container-radial-huge": ap.iconPrefab.transform.GetChild(0).localScale *= 1.33f; ap.iconScale *= 1.33f; break; case "kerbalism-container-inline-375": ap.iconPrefab.transform.GetChild(0).localScale *= 1.33f; ap.iconScale *= 1.33f; break; } // force a non-lexical order in the editor switch (ap.name) { case "kerbalism-container-inline-0625": ap.title = Lib.BuildString("<size=1><color=#00000000>00</color></size>", ap.title); break; case "kerbalism-container-inline-125": ap.title = Lib.BuildString("<size=1><color=#00000000>01</color></size>", ap.title); break; case "kerbalism-container-inline-250": ap.title = Lib.BuildString("<size=1><color=#00000000>02</color></size>", ap.title); break; case "kerbalism-container-inline-375": ap.title = Lib.BuildString("<size=1><color=#00000000>03</color></size>", ap.title); break; case "kerbalism-container-radial-small": ap.title = Lib.BuildString("<size=1><color=#00000000>04</color></size>", ap.title); break; case "kerbalism-container-radial-medium": ap.title = Lib.BuildString("<size=1><color=#00000000>05</color></size>", ap.title); break; case "kerbalism-container-radial-big": ap.title = Lib.BuildString("<size=1><color=#00000000>06</color></size>", ap.title); break; case "kerbalism-container-radial-huge": ap.title = Lib.BuildString("<size=1><color=#00000000>07</color></size>", ap.title); break; case "kerbalism-greenhouse": ap.title = Lib.BuildString("<size=1><color=#00000000>08</color></size>", ap.title); break; case "kerbalism-gravityring": ap.title = Lib.BuildString("<size=1><color=#00000000>09</color></size>", ap.title); break; case "kerbalism-activeshield": ap.title = Lib.BuildString("<size=1><color=#00000000>10</color></size>", ap.title); break; case "kerbalism-chemicalplant": ap.title = Lib.BuildString("<size=1><color=#00000000>11</color></size>", ap.title); break; } // recompile some part infos (this is normally done by KSP on loading, after each part prefab is compiled) // This is needed because : // - We can't check interdependent modules when OnLoad() is called, since the other modules may not be loaded yet // - The science DB needs the system/bodies to be instantiated, which is done after the part compilation bool partNeedsInfoRecompile = false; foreach (PartModule module in ap.partPrefab.Modules) { // we want to remove the editor part tooltip module infos widgets that are switchable trough the configure module // because the clutter the UI quite a bit. To do so, every module that implements IConfigurable is made to return // an empty string in their GetInfo() if the IConfigurable.ModuleIsConfigured() is ever called on them. if (module is Configure configure) { List <IConfigurable> configurables = configure.GetIConfigurableModules(); if (configurables.Count > 0) { partNeedsInfoRecompile = true; } foreach (IConfigurable configurable in configurables) { configurable.ModuleIsConfigured(); } } // note that the experiment modules on the prefab gets initialized from the scienceDB init, which also do // a LoadedPartsList loop to get the scienceDB module infos. So this has to be called after the scienceDB init. else if (module is Experiment) { partNeedsInfoRecompile = true; } } // for some reason this crashes on the EVA kerbals parts if (partNeedsInfoRecompile && !ap.name.StartsWith("kerbalEVA")) { ap.moduleInfos.Clear(); ap.resourceInfos.Clear(); try { Lib.ReflectionCall(PartLoader.Instance, "CompilePartInfo", new Type[] { typeof(AvailablePart), typeof(Part) }, new object[] { ap, ap.partPrefab }); } catch (Exception ex) { Lib.Log("Could not patch the moduleInfo for part " + ap.name + " - " + ex.Message + "\n" + ex.StackTrace); } } } }
static void ProcessScanner(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule scanner, Part part_prefab, VesselData 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 // - comparing against amount in previous simulation step if (ec.amount <= double.Epsilon) { // unregister scanner SCANsat.stopScanner(v, m, 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(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); is_scanning = true; // give the user some feedback if (vd.cfg_ec) { 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); } }
// call scripts automatically when conditions are met public void Automate(Vessel v, Vessel_info vi, Vessel_resources resources) { // do nothing if automation is disabled if (!Features.Automation) { return; } // get current states Resource_info ec = resources.Info(v, "ElectricCharge"); bool sunlight = vi.sunlight > double.Epsilon; bool power_low = ec.level < 0.2; bool power_high = ec.level > 0.8; bool radiation_low = vi.radiation < 0.000005552; //< 0.02 rad/h bool radiation_high = vi.radiation > 0.00001388; //< 0.05 rad/h bool signal = vi.connection.linked; bool drive_full = vi.free_capacity < double.MaxValue && (vi.free_capacity / vi.total_capacity < 0.15); bool drive_empty = vi.free_capacity >= double.MaxValue || (vi.free_capacity / vi.total_capacity > 0.9); // get current situation bool landed = false; bool atmo = false; bool space = false; switch (v.situation) { case Vessel.Situations.LANDED: case Vessel.Situations.SPLASHED: landed = true; break; case Vessel.Situations.FLYING: atmo = true; break; case Vessel.Situations.SUB_ORBITAL: case Vessel.Situations.ORBITING: case Vessel.Situations.ESCAPING: space = true; break; } // compile list of scripts that need to be called var to_exec = new List <Script>(); foreach (var p in scripts) { ScriptType type = p.Key; Script script = p.Value; if (script.states.Count == 0) { continue; //< skip empty scripts (may happen during editing) } switch (type) { case ScriptType.landed: if (landed && script.prev == "0") { to_exec.Add(script); } script.prev = landed ? "1" : "0"; break; case ScriptType.atmo: if (atmo && script.prev == "0") { to_exec.Add(script); } script.prev = atmo ? "1" : "0"; break; case ScriptType.space: if (space && script.prev == "0") { to_exec.Add(script); } script.prev = space ? "1" : "0"; break; case ScriptType.sunlight: if (sunlight && script.prev == "0") { to_exec.Add(script); } script.prev = sunlight ? "1" : "0"; break; case ScriptType.shadow: if (!sunlight && script.prev == "0") { to_exec.Add(script); } script.prev = !sunlight ? "1" : "0"; break; case ScriptType.power_high: if (power_high && script.prev == "0") { to_exec.Add(script); } script.prev = power_high ? "1" : "0"; break; case ScriptType.power_low: if (power_low && script.prev == "0") { to_exec.Add(script); } script.prev = power_low ? "1" : "0"; break; case ScriptType.rad_low: if (radiation_low && script.prev == "0") { to_exec.Add(script); } script.prev = radiation_low ? "1" : "0"; break; case ScriptType.rad_high: if (radiation_high && script.prev == "0") { to_exec.Add(script); } script.prev = radiation_high ? "1" : "0"; break; case ScriptType.linked: if (signal && script.prev == "0") { to_exec.Add(script); } script.prev = signal ? "1" : "0"; break; case ScriptType.unlinked: if (!signal && script.prev == "0") { to_exec.Add(script); } script.prev = !signal ? "1" : "0"; break; case ScriptType.drive_full: if (drive_full && script.prev == "0") { to_exec.Add(script); } script.prev = drive_full ? "1" : "0"; break; case ScriptType.drive_empty: if (drive_empty && script.prev == "0") { to_exec.Add(script); } script.prev = drive_empty ? "1" : "0"; break; } } // if there are scripts to call if (to_exec.Count > 0) { // get list of devices // - we avoid creating it when there are no scripts to be executed, making its overall cost trivial var devices = Boot(v); // execute all scripts foreach (Script script in to_exec) { script.Execute(devices); } // show message to the user if (DB.Vessel(v).cfg_script) { Message.Post(Lib.BuildString("Script called on vessel <b>", v.vesselName, "</b>")); } } }
public static void Update(Vessel v, VesselData vd, ResourceInfo ec, double elapsed_s) { if (!Lib.IsVessel(v)) { return; } // EC consumption is handled in Science update Cache.WarpCache(v).dataCapacity = vd.deviceTransmit ? vd.Connection.rate * elapsed_s : 0.0; // do nothing if network is not ready if (!NetworkInitialized) { return; } // maintain and send messages // - do not send messages during/after solar storms // - do not send messages for EVA kerbals if (!v.isEVA && v.situation != Vessel.Situations.PRELAUNCH) { if (!vd.msg_signal && !vd.Connection.linked) { vd.msg_signal = true; if (vd.cfg_signal) { string subtext = Localizer.Format("#KERBALISM_UI_transmissiondisabled"); switch (vd.Connection.status) { case LinkStatus.plasma: subtext = Localizer.Format("#KERBALISM_UI_Plasmablackout"); break; case LinkStatus.storm: subtext = Localizer.Format("#KERBALISM_UI_Stormblackout"); break; default: if (vd.CrewCount == 0) { switch (Settings.UnlinkedControl) { case UnlinkedCtrl.none: subtext = Localizer.Format("#KERBALISM_UI_noctrl"); break; case UnlinkedCtrl.limited: subtext = Localizer.Format("#KERBALISM_UI_limitedcontrol"); break; } } break; } Message.Post(Severity.warning, Lib.BuildString(Localizer.Format("#KERBALISM_UI_signallost"), " <b>", v.vesselName, "</b>"), subtext); } } else if (vd.msg_signal && vd.Connection.linked) { vd.msg_signal = false; if (vd.cfg_signal) { Message.Post(Severity.relax, Lib.BuildString("<b>", v.vesselName, "</b> ", Localizer.Format("#KERBALISM_UI_signalback")), vd.Connection.status == LinkStatus.direct_link ? Localizer.Format("#KERBALISM_UI_directlink") : Lib.BuildString(Localizer.Format("#KERBALISM_UI_relayby"), " <b>", vd.Connection.target_name, "</b>")); } } } }
} //< make sure it is at the top public string GetPrimaryField() { return(Lib.BuildString("Configurable ", title)); }
public void generate_details(Configure cfg) { // If a setup component is defined after the Configure module in the ConfigNode, // then it is not present in the part during Configure::OnLoad (at precompilation time) // so, find_module() will fail in that situation, resulting in no component details // being added to the Configure window. Therefore we are forced to generate the details // at first use every time the module is loaded, instead of generating them only once. // already generated if (details != null) { return; } // generate module details details = new List <Detail>(); foreach (ConfigureModule cm in modules) { // find module, skip if it doesn't exist PartModule m = cfg.find_module(cm); if (m == null) { continue; } // get title IModuleInfo module_info = m as IModuleInfo; string title = module_info != null?module_info.GetModuleTitle() : cm.type; if (title.Length == 0) { continue; } // get specs, skip if not implemented by module ISpecifics specifics = m as ISpecifics; if (specifics == null) { continue; } Specifics specs = specifics.Specs(); if (specs.entries.Count == 0) { continue; } // add title to details details.Add(new Detail(Lib.BuildString("<b><color=#00ffff>", title, "</color></b>"))); // add specs to details foreach (Specifics.Entry e in specs.entries) { details.Add(new Detail(e.label, e.value)); } } // get visible resources subset List <ConfigureResource> visible_resources = resources.FindAll(k => Lib.GetDefinition(k.name).isVisible); // generate resource details if (visible_resources.Count > 0) { // add resources title details.Add(new Detail("<b><color=#00ffff>Resources</color></b>")); // for each visible resource foreach (ConfigureResource cr in visible_resources) { // add capacity info details.Add(new Detail(cr.name, Lib.Parse.ToDouble(cr.maxAmount).ToString("F2"))); } } // generate extra details if (mass > double.Epsilon || cost > double.Epsilon) { details.Add(new Detail("<b><color=#00ffff>Extra</color></b>")); if (mass > double.Epsilon) { details.Add(new Detail("mass", Lib.HumanReadableMass(mass))); } if (cost > double.Epsilon) { details.Add(new Detail("cost", Lib.HumanReadableCost(cost))); } } }
public static void Update(Vessel v, Vessel_Info vi, VesselData vd, double elapsed_s) { // do nothing if signal mechanic is disabled if (!Features.Signal && !Features.KCommNet && !RemoteTech.Enabled()) { return; } // get connection info ConnectionInfo conn = vi.connection; // maintain and send messages // - do not send messages for vessels without an antenna // - do not send messages during/after solar storms // - do not send messages for EVA kerbals if (conn.status != LinkStatus.no_antenna && !v.isEVA && v.situation != Vessel.Situations.PRELAUNCH) { if (!vd.msg_signal && !conn.linked) { vd.msg_signal = true; if (vd.cfg_signal && conn.status != LinkStatus.blackout) { string subtext = "Data transmission disabled"; if (vi.crew_count == 0) { switch (Settings.UnlinkedControl) { case UnlinkedCtrl.none: subtext = "Remote control disabled"; break; case UnlinkedCtrl.limited: subtext = "Limited control available"; break; } } Message.Post(Severity.warning, Lib.BuildString("Signal lost with <b>", v.vesselName, "</b>"), subtext); } } else if (vd.msg_signal && conn.linked) { vd.msg_signal = false; if (vd.cfg_signal && !Storm.JustEnded(v, elapsed_s)) { var path = conn.path; Message.Post(Severity.relax, Lib.BuildString("<b>", v.vesselName, "</b> signal is back"), path.Count == 0 ? "We got a direct link with the space center" : Lib.BuildString("Relayed by <b>", path[path.Count - 1].vesselName, "</b>")); } } } }
// module info support public string GetModuleTitle() { return(Lib.BuildString("# Configurable ", title)); } //< make sure it is at the top
public void update(CelestialBody body, double elapsed_s) { // do nothing if storms are disabled if (Settings.StormDuration <= double.Epsilon) return; // skip the sun if (body.flightGlobalsIndex == 0) return; // skip moons // note: referenceBody is never null here if (body.referenceBody.flightGlobalsIndex != 0) return; // get body data body_data bd = DB.BodyData(body.name); // generate storm time if necessary if (bd.storm_time <= double.Epsilon) { bd.storm_time = Settings.StormMinTime + (Settings.StormMaxTime - Settings.StormMinTime) * Lib.RandomDouble(); } // accumulate age bd.storm_age += elapsed_s * storm_frequency(body.orbit.semiMajorAxis); // if storm is over if (bd.storm_age > bd.storm_time) { bd.storm_age = 0.0; bd.storm_time = 0.0; bd.storm_state = 0; } // if storm is in progress else if (bd.storm_age > bd.storm_time - Settings.StormDuration) { bd.storm_state = 2; } // if storm is incoming else if (bd.storm_age > bd.storm_time - Settings.StormDuration - time_to_impact(body.orbit.semiMajorAxis)) { bd.storm_state = 1; } // send messages // note: separed from state management to support the case when the user enter the SOI of a body under storm or about to be hit if (bd.msg_storm < 2 && bd.storm_state == 2) { if (body_is_relevant(body)) { Message.Post(Severity.danger, Lib.BuildString("The coronal mass ejection hit <b>", body.name, "</b> system"), Lib.BuildString("Storm duration: ", Lib.HumanReadableDuration(TimeLeftCME(bd.storm_time, bd.storm_age)))); } bd.msg_storm = 2; } else if (bd.msg_storm < 1 && bd.storm_state == 1) { if (body_is_relevant(body)) { Message.Post(Severity.warning, Lib.BuildString("Our observatories report a coronal mass ejection directed toward <b>", body.name, "</b> system"), Lib.BuildString("Time to impact: ", Lib.HumanReadableDuration(TimeBeforeCME(bd.storm_time, bd.storm_age)))); } bd.msg_storm = 1; } else if (bd.msg_storm > 1 && bd.storm_state == 0) { if (body_is_relevant(body)) { Message.Post(Severity.relax, Lib.BuildString("The solar storm at <b>", body.name, "</b> system is over")); } bd.msg_storm = 0; } }
} // attempt to display at the top public string GetPrimaryField() { return(Lib.BuildString("<size=1><color=#00000000>00</color></size>Configurable ", title)); } // attempt to display at the top
} // attempt to display at the top public override string GetModuleDisplayName() { return(Lib.BuildString("<size=1><color=#00000000>00</color></size>Configurable ", title)); } // attempt to display at the top
public override string ToString() { return(Lib.BuildString(BodyName, ScienceSituationName, BiomeName)); }
public void Update() { // in flight if (Lib.IsFlight()) { // get info from cache vessel_info vi = Cache.VesselInfo(vessel); // do nothing if vessel is invalid if (!vi.is_valid) { return; } // update ui bool has_operator = operator_cs.check(vessel); Events["Toggle"].guiName = Lib.StatusToggle(exp_name, !recording ? "stopped" : issue.Length == 0 ? "recording" : Lib.BuildString("<color=#ffff00>", issue, "</color>")); } // in the editor else if (Lib.IsEditor()) { // update ui Events["Toggle"].guiName = Lib.StatusToggle(exp_name, recording ? "recording" : "stopped"); } }
public static void body_info(this Panel p) { // only show in mapview if (!MapView.MapIsEnabled) return; // only show if there is a selected body and that body is not the sun CelestialBody body = Lib.SelectedBody(); if (body == null || (body.flightGlobalsIndex == 0 && !Features.Radiation)) return; // shortcut CelestialBody sun = FlightGlobals.Bodies[0]; // for all bodies except the sun if (body != sun) { // calculate simulation values double atmo_factor = Sim.AtmosphereFactor(body, 0.7071); double gamma_factor = Sim.GammaTransparency(body, 0.0); double sun_dist = Sim.Apoapsis(Lib.PlanetarySystem(body)) - sun.Radius - body.Radius; Vector3d sun_dir = (sun.position - body.position).normalized; double solar_flux = Sim.SolarFlux(sun_dist) * atmo_factor; double albedo_flux = Sim.AlbedoFlux(body, body.position + sun_dir * body.Radius); double body_flux = Sim.BodyFlux(body, 0.0); double total_flux = solar_flux + albedo_flux + body_flux + Sim.BackgroundFlux(); double temperature = body.atmosphere ? body.GetTemperature(0.0) : Sim.BlackBodyTemperature(total_flux); // calculate night-side temperature double total_flux_min = Sim.AlbedoFlux(body, body.position - sun_dir * body.Radius) + body_flux + Sim.BackgroundFlux(); double temperature_min = Sim.BlackBodyTemperature(total_flux_min); // calculate radiation at body surface double radiation = Radiation.ComputeSurface(body, gamma_factor); // surface panel string temperature_str = body.atmosphere ? Lib.HumanReadableTemp(temperature) : Lib.BuildString(Lib.HumanReadableTemp(temperature_min), " / ", Lib.HumanReadableTemp(temperature)); p.section("SURFACE"); p.content("temperature", temperature_str); p.content("solar flux", Lib.HumanReadableFlux(solar_flux)); if (Features.Radiation) p.content("radiation", Lib.HumanReadableRadiation(radiation)); // atmosphere panel if (body.atmosphere) { p.section("ATMOSPHERE"); p.content("breathable", Sim.Breathable(body) ? "yes" : "no"); p.content("light absorption", Lib.HumanReadablePerc(1.0 - Sim.AtmosphereFactor(body, 0.7071))); if (Features.Radiation) p.content("gamma absorption", Lib.HumanReadablePerc(1.0 - Sim.GammaTransparency(body, 0.0))); } } // rendering panel if (Features.Radiation) { p.section("RENDERING"); p.content("inner belt", Radiation.show_inner ? "<color=green>show</color>" : "<color=red>hide</color>", string.Empty, () => p.toggle(ref Radiation.show_inner)); p.content("outer belt", Radiation.show_outer ? "<color=green>show</color>" : "<color=red>hide</color>", string.Empty, () => p.toggle(ref Radiation.show_outer)); p.content("magnetopause", Radiation.show_pause ? "<color=green>show</color>" : "<color=red>hide</color>", string.Empty, () => p.toggle(ref Radiation.show_pause)); } // explain the user how to toggle the BodyInfo window p.content(string.Empty); p.content("<i>Press <b>B</b> to open this window again</i>"); // set metadata p.title(Lib.BuildString(Lib.Ellipsis(body.bodyName, Styles.ScaleStringLength(24)), " <color=#cccccc>BODY INFO</color>")); }
/// <summary>synchronize resources from cache to vessel</summary> /// <remarks> /// this function will also sync from vessel to cache so you can always use the /// ResourceInfo interface to get information about resources /// </remarks> public void Sync(Vessel v, VesselData vd, double elapsed_s, List <PartResource> loadedResList, List <ProtoPartResourceSnapshot> unloadedResList) { UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.Resource.Sync"); // # OVERVIEW // - consumption/production is accumulated in "Deferred", then this function called // - save previous step amount/capacity // - part loop 1 : detect new amount/capacity // - if amount has changed, this mean there is non-Kerbalism producers/consumers on the vessel // - if non-Kerbalism producers are detected on a loaded vessel, prevent high timewarp rates // - clamp "Deferred" to amount/capacity // - part loop 2 : apply "Deferred" to all parts // - apply "Deferred" to amount // - calculate change rate per-second // - calculate resource level // - reset deferred // # NOTE // It is impossible to guarantee coherency in resource simulation of loaded vessels, // if consumers/producers external to the resource cache exist in the vessel (#96). // Such is the case for example on loaded vessels with stock solar panels. // The effect is that the whole resource simulation become dependent on timestep again. // From the user point-of-view, there are two cases: // - (A) the timestep-dependent error is smaller than capacity // - (B) the timestep-dependent error is bigger than capacity // In case [A], there are no consequences except a slightly wrong computed level and rate. // In case [B], the simulation became incoherent and from that point anything can happen, // like for example insta-death by co2 poisoning or climatization. // To avoid the consequences of [B]: // - we hacked the solar panels to use the resource cache (SolarPanelFixer) // - we detect incoherency on loaded vessels, and forbid the two highest warp speeds // remember vessel-wide amount currently known, to calculate rate and detect non-Kerbalism brokers double oldAmount = Amount; // remember vessel-wide capacity currently known, to detect flow state changes double oldCapacity = Capacity; // iterate over all enabled resource containers and detect amount/capacity again // - this detect production/consumption from stock and third-party mods // that by-pass the resource cache, and flow state changes in general Amount = 0.0; Capacity = 0.0; if (v.loaded) { foreach (PartResource r in loadedResList) { Amount += r.amount; Capacity += r.maxAmount; } } else { foreach (ProtoPartResourceSnapshot r in unloadedResList) { Amount += r.amount; Capacity += r.maxAmount; } } // As we haven't yet synchronized anything, changes to amount can only come from non-Kerbalism producers or consumers double unsupportedBrokersRate = Amount - oldAmount; // Avoid false detection due to precision errors if (Math.Abs(unsupportedBrokersRate) < 1e-05) { unsupportedBrokersRate = 0.0; } // Calculate the resulting rate unsupportedBrokersRate /= elapsed_s; // Detect flow state changes bool flowStateChanged = Capacity - oldCapacity > 1e-05; // clamp consumption/production to vessel amount/capacity // - if deferred is negative, then amount is guaranteed to be greater than zero // - if deferred is positive, then capacity - amount is guaranteed to be greater than zero Deferred = Lib.Clamp(Deferred, -Amount, Capacity - Amount); // apply deferred consumption/production to all parts, simulating ALL_VESSEL_BALANCED // - iterating again is faster than using a temporary list of valid PartResources // - avoid very small values in deferred consumption/production if (Math.Abs(Deferred) > 1e-10) { if (v.loaded) { foreach (PartResource r in loadedResList) { // calculate consumption/production coefficient for the part double k = Deferred < 0.0 ? r.amount / Amount : (r.maxAmount - r.amount) / (Capacity - Amount); // apply deferred consumption/production r.amount += Deferred * k; } } else { foreach (ProtoPartResourceSnapshot r in unloadedResList) { // calculate consumption/production coefficient for the part double k = Deferred < 0.0 ? r.amount / Amount : (r.maxAmount - r.amount) / (Capacity - Amount); // apply deferred consumption/production r.amount += Deferred * k; } } } // update amount, to get correct rate and levels at all times Amount += Deferred; // reset deferred production/consumption Deferred = 0.0; // recalculate level Level = Capacity > 0.0 ? Amount / Capacity : 0.0; // calculate rate of change per-second // - don't update rate during warp blending (stock modules have instabilities during warp blending) // - ignore interval-based rules consumption/production if (!v.loaded || !Kerbalism.WarpBlending) { Rate = (Amount - oldAmount - intervalRuleAmount) / elapsed_s; } // calculate average rate of change per-second from interval-based rules intervalRulesRate = 0.0; foreach (var rb in intervalRuleBrokersRates) { intervalRulesRate += rb.Value; } // AverageRate is the exposed property that include simulated rate from interval-based rules. // For consistency with how "Rate" is calculated, we only add the simulated rate if there is some capacity or amount for it to have an effect AverageRate = Rate; if ((intervalRulesRate > 0.0 && Level < 1.0) || (intervalRulesRate < 0.0 && Level > 0.0)) { AverageRate += intervalRulesRate; } // For visualization purpose, update the VesselData.supplies brokers list, merging all detected sources : // - normal brokers that use Consume() or Produce() // - "virtual" brokers from interval-based rules // - non-Kerbalism brokers (aggregated rate) vd.Supply(ResourceName).UpdateResourceBrokers(brokersResourceAmounts, intervalRuleBrokersRates, unsupportedBrokersRate, elapsed_s); //Lib.Log("RESOURCE UPDATE : " + v); //foreach (var rb in vd.Supply(ResourceName).ResourceBrokers) // Lib.Log(Lib.BuildString(ResourceName, " : ", rb.rate.ToString("+0.000000;-0.000000;+0.000000"), "/s (", rb.name, ")")); //Lib.Log("RESOURCE UPDATE END"); // reset amount added/removed from interval-based rules IntervalRuleHappened = intervalRuleAmount > 0.0; intervalRuleAmount = 0.0; // if incoherent producers are detected, do not allow high timewarp speed // - can be disabled in settings // - unloaded vessels can't be incoherent, we are in full control there // - ignore incoherent consumers (no negative consequences for player) // - ignore flow state changes (avoid issue with process controllers and other things) if (Settings.EnforceCoherency && v.loaded && TimeWarp.CurrentRate > 1000.0 && unsupportedBrokersRate > 0.0 && !flowStateChanged) { Message.Post ( Severity.warning, Lib.BuildString ( !v.isActiveVessel ? Lib.BuildString("On <b>", v.vesselName, "</b>\na ") : "A ", "producer of <b>", ResourceName, "</b> has\n", "incoherent behavior at high warp speed.\n", "<i>Unload the vessel before warping</i>" ) ); Lib.StopWarp(1000.0); } // reset brokers brokersResourceAmounts.Clear(); intervalRuleBrokersRates.Clear(); // reset amount added/removed from interval-based rules intervalRuleAmount = 0.0; UnityEngine.Profiling.Profiler.EndSample(); }
public void Update() { // update ui Status = deployed ? Lib.BuildString(Local.PassiveShield_absorbing, " ", Lib.HumanReadableRadiation(Math.Abs(radiation))) : disabledTitle; //"absorbing Events["Toggle"].guiName = Lib.StatusToggle(title, deployed ? disengageActionTitle : engageActionTitle); }
// synchronize amount from cache to vessel public void Sync(Vessel v, double elapsed_s) { // # OVERVIEW // - deferred consumption/production is accumulated, then this function called // - detect amount/capacity in vessel // - clamp deferred to amount/capacity // - apply deferred // - update cached amount [disabled, see comments] // - calculate change rate per-second // - calculate resource level // - reset deferred // # NOTE // It is impossible to guarantee coherency in resource simulation of loaded vessels, // if consumers/producers external to the resource cache exist in the vessel (#96). // Such is the case for example on loaded vessels with stock solar panels. // The effect is that the whole resource simulation become dependent on timestep again. // From the user point-of-view, there are two cases: // - (A) the timestep-dependent error is smaller than capacity // - (B) the timestep-dependent error is bigger than capacity // In case [A], there are no consequences except a slightly wrong computed level and rate. // In case [B], the simulation became incoherent and from that point anything can happen, // like for example insta-death by co2 poisoning or climatization. // To avoid the consequences of [B]: // - we hacked the stock solar panel to use the resource cache // - we detect incoherency on loaded vessels, and forbid the two highest warp speeds // remember amount currently known, to calculate rate later on double old_amount = amount; // remember capacity currently known, to detect flow state changes double old_capacity = capacity; // iterate over all enabled resource containers and detect amount/capacity again // - this detect production/consumption from stock and third-party mods // that by-pass the resource cache, and flow state changes in general amount = 0.0; capacity = 0.0; if (v.loaded) { foreach (Part p in v.Parts) { foreach (PartResource r in p.Resources) { if (r.flowState && r.resourceName == resource_name) { amount += r.amount; capacity += r.maxAmount; } } } } else { foreach (ProtoPartSnapshot p in v.protoVessel.protoPartSnapshots) { foreach (ProtoPartResourceSnapshot r in p.resources) { if (r.flowState && r.resourceName == resource_name) { amount += r.amount; capacity += r.maxAmount; } } } } // if incoherent producers are detected, do not allow high timewarp speed // - ignore incoherent consumers (no negative consequences for player) // - ignore flow state changes (avoid issue with process controllers) // - unloaded vessels can't be incoherent, we are in full control there // - can be disabled in settings // - avoid false detection due to precision errors in stock amounts if (Settings.EnforceCoherency && v.loaded && TimeWarp.CurrentRateIndex >= 6 && amount - old_amount > 1e-05 && capacity - old_capacity < 1e-05) { Message.Post ( Severity.warning, Lib.BuildString ( !v.isActiveVessel ? Lib.BuildString("On <b>", v.vesselName, "</b>\na ") : "A ", "producer of <b>", resource_name, "</b> has\n", "incoherent behaviour at high warp speed.\n", "<i>Unload the vessel before warping</i>" ) ); Lib.StopWarp(5); } // clamp consumption/production to vessel amount/capacity // - if deferred is negative, then amount is guaranteed to be greater than zero // - if deferred is positive, then capacity - amount is guaranteed to be greater than zero deferred = Lib.Clamp(deferred, -amount, capacity - amount); // apply deferred consumption/production, simulating ALL_VESSEL_BALANCED // - iterating again is faster than using a temporary list of valid PartResources // - avoid very small values in deferred consumption/production if (Math.Abs(deferred) > 1e-10) { if (v.loaded) { foreach (Part p in v.parts) { foreach (PartResource r in p.Resources) { if (r.flowState && r.resourceName == resource_name) { // calculate consumption/production coefficient for the part double k = deferred < 0.0 ? r.amount / amount : (r.maxAmount - r.amount) / (capacity - amount); // apply deferred consumption/production r.amount += deferred * k; } } } } else { foreach (ProtoPartSnapshot p in v.protoVessel.protoPartSnapshots) { foreach (ProtoPartResourceSnapshot r in p.resources) { if (r.flowState && r.resourceName == resource_name) { // calculate consumption/production coefficient for the part double k = deferred < 0.0 ? r.amount / amount : (r.maxAmount - r.amount) / (capacity - amount); // apply deferred consumption/production r.amount += deferred * k; } } } } } // update amount, to get correct rate and levels at all times amount += deferred; // calculate rate of change per-second // - don't update rate during and immediately after warp blending (stock modules have instabilities during warp blending) // - don't update rate during the simulation steps where meal is consumed, to avoid counting it twice if ((!v.loaded || Kerbalism.warp_blending > 50) && !meal_happened) { rate = (amount - old_amount) / elapsed_s; } // recalculate level level = capacity > double.Epsilon ? amount / capacity : 0.0; // reset deferred production/consumption deferred = 0.0; // reset meal flag meal_happened = false; }
public static void ManageRescueMission(Vessel v) { // true if we detected this was a rescue mission vessel bool detected = false; // deal with rescue missions foreach (ProtoCrewMember c in Lib.CrewList(v)) { // get kerbal data KerbalData kd = DB.Kerbal(c.name); // flag the kerbal as not rescue at prelaunch if (v.situation == Vessel.Situations.PRELAUNCH) { kd.rescue = false; } // if the kerbal belong to a rescue mission if (kd.rescue) { // remember it detected = true; // flag the kerbal as non-rescue // note: enable life support mechanics for the kerbal kd.rescue = false; // show a message Message.Post(Lib.BuildString(Local.Rescuemission_msg1, " <b>", c.name, "</b>"), Lib.BuildString((c.gender == ProtoCrewMember.Gender.Male ? Local.Kerbal_Male : Local.Kerbal_Female), Local.Rescuemission_msg2)); //We found xx "He"/"She"'s still alive!" } } // gift resources if (detected) { var reslib = PartResourceLibrary.Instance.resourceDefinitions; var parts = Lib.GetPartsRecursively(v.rootPart); // give the vessel some propellant usable on eva string monoprop_name = Lib.EvaPropellantName(); double monoprop_amount = Lib.EvaPropellantCapacity(); foreach (var part in parts) { if (part.CrewCapacity > 0 || part.FindModuleImplementing <KerbalEVA>() != null) { if (Lib.Capacity(part, monoprop_name) <= double.Epsilon) { Lib.AddResource(part, monoprop_name, 0.0, monoprop_amount); } break; } } ResourceCache.Produce(v, monoprop_name, monoprop_amount, ResourceBroker.Generic); // give the vessel some supplies Profile.SetupRescue(v); } }
// draw a vessel in the monitor // - return: 1 if vessel wasn't skipped uint render_vessel(Vessel v) { // get vessel info from cache vessel_info vi = Cache.VesselInfo(v); // skip invalid vessels if (!vi.is_valid) return 0; // get vessel data from the db vessel_data vd = DB.VesselData(v.id); // skip filtered vessels if (filtered() && vd.group != filter) return 0; // get vessel crew List<ProtoCrewMember> crew = v.loaded ? v.GetVesselCrew() : v.protoVessel.GetVesselCrew(); // get vessel name string vessel_name = v.isEVA ? crew[0].name : v.vesselName; // get body name string body_name = v.mainBody.name.ToUpper(); // store problems icons & tooltips List<Texture> problem_icons = new List<Texture>(); List<string> problem_tooltips = new List<string>(); // detect problems problem_sunlight(vi, ref problem_icons, ref problem_tooltips); problem_storm(v, ref problem_icons, ref problem_tooltips); if (crew.Count > 0) { problem_kerbals(crew, ref problem_icons, ref problem_tooltips); problem_radiation(vi, ref problem_icons, ref problem_tooltips); problem_scrubbers(v, vi.scrubbers, ref problem_icons, ref problem_tooltips); problem_recyclers(v, vi.recyclers, ref problem_icons, ref problem_tooltips); } problem_greenhouses(v, vi.greenhouses, ref problem_icons, ref problem_tooltips); // choose problem icon const UInt64 problem_icon_time = 3; Texture problem_icon = icon_empty; if (problem_icons.Count > 0) { UInt64 problem_index = ((UInt64)Time.realtimeSinceStartup / problem_icon_time) % (UInt64)(problem_icons.Count); problem_icon = problem_icons[(int)problem_index]; } // generate problem tooltips string problem_tooltip = String.Join("\n", problem_tooltips.ToArray()); // render vessel name & icons GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent(Lib.BuildString("<b>", Lib.Epsilon(vessel_name, 18), "</b>"), vessel_name.Length > 18 ? vessel_name : ""), name_style); GUILayout.Label(new GUIContent(Lib.Epsilon(body_name, 8), body_name.Length > 8 ? body_name : ""), body_style); GUILayout.Label(new GUIContent(problem_icon, problem_tooltip), icon_style); GUILayout.Label(indicator_ec(v), icon_style); if (Kerbalism.supply_rules.Count > 0) GUILayout.Label(indicator_supplies(v, vi), icon_style); if (Kerbalism.features.reliability) GUILayout.Label(indicator_reliability(v, vi), icon_style); if (Kerbalism.features.signal) GUILayout.Label(indicator_signal(v, vi), icon_style); GUILayout.EndHorizontal(); if (Lib.IsClicked(1)) Info.Toggle(v); else if (Lib.IsClicked(2) && !v.isEVA) Console.Toggle(v); // remember last vessel clicked if (Lib.IsClicked()) last_clicked_id = v.id; // render vessel config if (configured_id == v.id) render_config(v); // spacing between vessels GUILayout.Space(10.0f); // signal that the vessel wasn't skipped for whatever reason return 1; }
// return a verbose description of shielding capability public static string Shielding_to_string(double v) { return(v <= double.Epsilon ? Local.Habitat_none : Lib.BuildString((20.0 * v / PreferencesRadiation.Instance.shieldingEfficiency).ToString("F2"), " mm"));//"none" }
public void Repair() { // disable for dead eva kerbals Vessel v = FlightGlobals.ActiveVessel; if (v == null || EVA.IsDead(v)) { return; } // check trait if (!repair_cs.Check(v)) { Message.Post ( Lib.TextVariant ( "I'm not qualified for this", "I will not even know where to start", "I'm afraid I can't do that" ), repair_cs.Warning() ); return; } // flag as not broken broken = false; // reset times last = 0.0; next = 0.0; // re-enable module foreach (PartModule m in modules) { m.isEnabled = true; m.enabled = true; } // we need to reconfigure the module here, because if all modules of a type // share the broken state, and these modules are part of a configure setup, // then repairing will enable all of them, messing up with the configuration part.FindModulesImplementing <Configure>().ForEach(k => k.DoConfigure()); // type-specific hacks Apply(false); // notify user Message.Post ( Lib.BuildString("<b>", title, "</b> repaired"), Lib.TextVariant ( "A powerkick did the trick", "Duct tape, is there something it can't fix?", "Fully operational again", "We are back in business" ) ); }
bool Render_Vessel(Panel p, Vessel v) { // get vessel info Vessel_Info vi = Cache.VesselInfo(v); // skip invalid vessels if (!vi.is_valid) { return(false); } if (!Lib.IsVessel(v)) { return(false); } // get data from db VesselData vd = DB.Vessel(v); // determine if filter must be shown show_filter |= vd.group.Length > 0 && vd.group != "NONE"; // skip filtered vessels if (Filtered() && vd.group != filter) { return(false); } // get resource handler Vessel_Resources resources = ResourceCache.Get(v); // get vessel crew List <ProtoCrewMember> crew = Lib.CrewList(v); // get vessel name string vessel_name = v.isEVA ? crew[0].name : v.vesselName; // get body name string body_name = v.mainBody.name.ToUpper(); // render entry p.SetHeader ( Lib.BuildString("<b>", Lib.Ellipsis(vessel_name, 20), "</b> <size=9><color=#cccccc>", Lib.Ellipsis(body_name, 8), "</color></size>"), string.Empty, () => { selected_id = selected_id != v.id ? v.id : Guid.Empty; } ); // problem indicator Indicator_Problems(p, v, vi, crew); // battery indicator Indicator_EC(p, v, vi); // supply indicator if (Features.Supplies) { Indicator_Supplies(p, v, vi); } // reliability indicator if (Features.Reliability) { Indicator_Reliability(p, v, vi); } // signal indicator if (Features.Signal || Features.KCommNet) { Indicator_Signal(p, v, vi); } // done return(true); }
// module info support public string GetModuleTitle() { return(Lib.BuildString(title, " Reliability")); }
// TODO: Implement support to CommNet frequency void Indicator_Signal(Panel p, Vessel v, Vessel_Info vi) { ConnectionInfo conn = vi.connection; // target name string target_str = string.Empty; switch (vi.connection.status) { case LinkStatus.direct_link: target_str = "DSN"; break; case LinkStatus.indirect_link: target_str = vi.connection.path[vi.connection.path.Count - 1].vesselName; break; default: target_str = "none"; break; } // transmitted label, content and tooltip string comms_label = vi.relaying.Length == 0 ? "transmitting" : "relaying"; string comms_str = vi.connection.linked ? "telemetry" : "nothing"; string comms_tooltip = string.Empty; if (vi.relaying.Length > 0) { ExperimentInfo exp = Science.Experiment(vi.relaying); comms_str = exp.name; comms_tooltip = exp.fullname; } else if (vi.transmitting.Length > 0) { ExperimentInfo exp = Science.Experiment(vi.transmitting); comms_str = exp.name; comms_tooltip = exp.fullname; } string tooltip = Lib.BuildString ( "<align=left />", "connected\t<b>", vi.connection.linked ? "yes" : "no", "</b>\n", "rate\t\t<b>", Lib.HumanReadableDataRate(vi.connection.rate), "</b>\n", "target\t\t<b>", target_str, "</b>\n", comms_label, "\t<b>", comms_str, "</b>" ); Texture image = Icons.signal_red; switch (conn.status) { case LinkStatus.direct_link: image = vi.connection.rate > 0.005 ? Icons.signal_white : Icons.signal_yellow; break; case LinkStatus.indirect_link: image = vi.connection.rate > 0.005 ? Icons.signal_white : Icons.signal_yellow; tooltip += "\n\n<color=yellow>Signal relayed</color>"; break; case LinkStatus.no_link: image = Icons.signal_red; break; case LinkStatus.no_antenna: image = Icons.signal_red; tooltip += "\n\n<color=red>No antenna</color>"; break; case LinkStatus.blackout: image = Icons.signal_red; tooltip += "\n\n<color=red>Blackout</color>"; break; } p.SetIcon(image, tooltip); }
public override string GetModuleDisplayName() { return(Lib.BuildString(title, " Reliability")); }
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); } }
} // Display after config widget public override string GetModuleDisplayName() { return(Lib.BuildString("<size=1><color=#00000000>01</color></size>", title)); } // Display after config widget