public static environment_data analyze_environment(CelestialBody body, double altitude_mult) { // shortcuts CelestialBody sun = Sim.Sun(); // calculate data environment_data env = new environment_data(); env.body = body; env.altitude = body.Radius * altitude_mult; env.landed = env.altitude <= double.Epsilon; env.breathable = env.landed && body.atmosphereContainsOxygen; env.sun_dist = Sim.Apoapsis(Lib.PlanetarySystem(body)) - sun.Radius - body.Radius; Vector3d sun_dir = (sun.position - body.position).normalized; env.sun_flux = Sim.SolarFlux(env.sun_dist); env.body_flux = Sim.BodyFlux(body, body.position + sun_dir * (body.Radius + env.altitude)); env.body_back_flux = Sim.BodyFlux(body, body.position - sun_dir * (body.Radius + env.altitude)); env.background_temp = Sim.BackgroundTemperature(); env.sun_temp = Sim.BlackBody(env.sun_flux); env.body_temp = Sim.BlackBody(env.body_flux); env.body_back_temp = Sim.BlackBody(env.body_back_flux); env.light_temp = env.background_temp + env.sun_temp + env.body_temp; env.shadow_temp = env.background_temp + env.body_back_temp; env.atmo_temp = body.GetTemperature(0.0); env.orbital_period = Sim.OrbitalPeriod(body, env.altitude); env.shadow_period = Sim.ShadowPeriod(body, env.altitude); env.shadow_time = env.shadow_period / env.orbital_period; env.temp_diff = env.landed && body.atmosphere ? Math.Abs(Settings.SurvivalTemperature - env.atmo_temp) : Lib.Mix(Math.Abs(Settings.SurvivalTemperature - env.light_temp), Math.Abs(Settings.SurvivalTemperature - env.shadow_temp), env.shadow_time); env.atmo_factor = env.landed ? Sim.AtmosphereFactor(body, 0.7071) : 1.0; // return data return env; }
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.MapViewSelectedBody(); if (body == null || (Lib.IsSun(body) && !Features.Radiation)) { return; } // calculate radiation at body surface double surfaceRadiation = Radiation.ComputeSurface(body, Sim.GammaTransparency(body, 0.0)); // for all bodies except sun(s) if (!Lib.IsSun(body)) { CelestialBody mainSun; Vector3d sun_dir; double sun_dist; double solar_flux = Sim.SolarFluxAtBody(body, false, out mainSun, out sun_dir, out sun_dist); solar_flux *= Sim.AtmosphereFactor(body, 0.7071); // calculate simulation values 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); // surface panel string temperature_str = body.atmosphere ? Lib.HumanReadableTemp(temperature) : Lib.BuildString(Lib.HumanReadableTemp(temperature_min), " / ", Lib.HumanReadableTemp(temperature)); p.AddSection(Local.BodyInfo_SURFACE); //"SURFACE" p.AddContent(Local.BodyInfo_temperature, temperature_str); //"temperature" p.AddContent(Local.BodyInfo_solarflux, Lib.HumanReadableFlux(solar_flux)); //"solar flux" if (Features.Radiation) { p.AddContent(Local.BodyInfo_radiation, Lib.HumanReadableRadiation(surfaceRadiation)); //"radiation" } // atmosphere panel if (body.atmosphere) { p.AddSection(Local.BodyInfo_ATMOSPHERE); //"ATMOSPHERE" p.AddContent(Local.BodyInfo_breathable, Sim.Breathable(body) ? Local.BodyInfo_breathable_yes : Local.BodyInfo_breathable_no); //"breathable""yes""no" p.AddContent(Local.BodyInfo_lightabsorption, Lib.HumanReadablePerc(1.0 - Sim.AtmosphereFactor(body, 0.7071))); //"light absorption" if (Features.Radiation) { p.AddContent(Local.BodyInfo_gammaabsorption, Lib.HumanReadablePerc(1.0 - Sim.GammaTransparency(body, 0.0))); //"gamma absorption" } } } // radiation panel if (Features.Radiation) { p.AddSection(Local.BodyInfo_RADIATION); //"RADIATION" string inner, outer, pause; double activity, cycle; RadiationLevels(body, out inner, out outer, out pause, out activity, out cycle); if (Storm.sun_observation_quality > 0.5 && activity > -1) { string title = Local.BodyInfo_solaractivity; //"solar activity" if (Storm.sun_observation_quality > 0.7) { title = Lib.BuildString(title, ": ", Lib.Color(Local.BodyInfo_stormcycle.Format(Lib.HumanReadableDuration(cycle)), Lib.Kolor.LightGrey)); // <<1>> cycle } p.AddContent(title, Lib.HumanReadablePerc(activity)); } if (Storm.sun_observation_quality > 0.8) { p.AddContent(Local.BodyInfo_radiationonsurface, Lib.HumanReadableRadiation(surfaceRadiation)); //"radiation on surface:" } p.AddContent(Lib.BuildString(Local.BodyInfo_innerbelt, " ", Lib.Color(inner, Lib.Kolor.LightGrey)), //"inner belt: " Radiation.show_inner ? Lib.Color(Local.BodyInfo_show, Lib.Kolor.Green) : Lib.Color(Local.BodyInfo_hide, Lib.Kolor.Red), string.Empty, () => p.Toggle(ref Radiation.show_inner)); //"show""hide" p.AddContent(Lib.BuildString(Local.BodyInfo_outerbelt, " ", Lib.Color(outer, Lib.Kolor.LightGrey)), //"outer belt: " Radiation.show_outer ? Lib.Color(Local.BodyInfo_show, Lib.Kolor.Green) : Lib.Color(Local.BodyInfo_hide, Lib.Kolor.Red), string.Empty, () => p.Toggle(ref Radiation.show_outer)); //"show""hide" p.AddContent(Lib.BuildString(Local.BodyInfo_magnetopause, " ", Lib.Color(pause, Lib.Kolor.LightGrey)), //"magnetopause: " Radiation.show_pause ? Lib.Color(Local.BodyInfo_show, Lib.Kolor.Green) : Lib.Color(Local.BodyInfo_hide, Lib.Kolor.Red), string.Empty, () => p.Toggle(ref Radiation.show_pause)); //"show""hide" } // explain the user how to toggle the BodyInfo window p.AddContent(string.Empty); p.AddContent("<i>" + Local.BodyInfo_BodyInfoToggleHelp.Format("<b>B</b>") + "</i>"); //"Press <<1>> to open this window again" // set metadata p.Title(Lib.BuildString(Lib.Ellipsis(body.bodyName, Styles.ScaleStringLength(24)), " ", Lib.Color(Local.BodyInfo_title, Lib.Kolor.LightGrey))); //"BODY INFO" }
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); }
/// <summary> /// Update the 'sunsInfo' list and the 'mainSun', 'solarFluxTotal' variables. /// Uses discrete or analytic (for high timewarp speeds) evaluation methods based on the isAnalytic bool. /// Require the 'visibleBodies' variable to be set. /// </summary> // at the two highest timewarp speed, the number of sun visibility samples drop to the point that // the quantization error first became noticeable, and then exceed 100%, to solve this : // - we switch to an analytical estimation of the sunlight/shadow period // - atmo_factor become an average atmospheric absorption factor over the daylight period (not the whole day) public static void UpdateSunsInfo(VesselData vd, Vector3d vesselPosition) { Vessel v = vd.Vessel; double lastSolarFlux = 0.0; vd.sunsInfo = new List <SunInfo>(Sim.suns.Count); vd.solarFluxTotal = 0.0; vd.rawSolarFluxTotal = 0.0; foreach (Sim.SunData sunData in Sim.suns) { SunInfo sunInfo = new SunInfo(sunData); if (vd.isAnalytic) { // get sun direction and distance Lib.DirectionAndDistance(vesselPosition, sunInfo.sunData.body, out sunInfo.direction, out sunInfo.distance); // analytical estimation of the portion of orbit that was in sunlight, current limitations : // - the result is dependant on the vessel altitude at the time of evaluation, // consequently it gives inconsistent behavior with highly eccentric orbits // - this totally ignore the orbit inclinaison, polar orbits will be treated as equatorial orbits sunInfo.sunlightFactor = 1.0 - Sim.ShadowPeriod(v) / Sim.OrbitalPeriod(v); // get atmospheric absorbtion // for atmospheric bodies whose rotation period is less than 120 hours, // determine analytic atmospheric absorption over a single body revolution instead // of using a discrete value that would be unreliable at large timesteps : if (vd.inAtmosphere) { sunInfo.atmoFactor = Sim.AtmosphereFactorAnalytic(v.mainBody, vesselPosition, sunInfo.direction); } else { sunInfo.atmoFactor = 1.0; } } else { // determine if in sunlight, calculate sun direction and distance sunInfo.sunlightFactor = Sim.IsBodyVisible(v, vesselPosition, sunData.body, vd.visibleBodies, out sunInfo.direction, out sunInfo.distance) ? 1.0 : 0.0; // get atmospheric absorbtion sunInfo.atmoFactor = Sim.AtmosphereFactor(v.mainBody, vesselPosition, sunInfo.direction); } // get resulting solar flux in W/m² sunInfo.rawSolarFlux = sunInfo.sunData.SolarFlux(sunInfo.distance); sunInfo.solarFlux = sunInfo.rawSolarFlux * sunInfo.sunlightFactor * sunInfo.atmoFactor; // increment total flux from all stars vd.rawSolarFluxTotal += sunInfo.rawSolarFlux; vd.solarFluxTotal += sunInfo.solarFlux; // add the star to the list vd.sunsInfo.Add(sunInfo); // the most powerful star will be our "default" sun. Uses raw flux before atmo / sunlight factor if (sunInfo.rawSolarFlux > lastSolarFlux) { lastSolarFlux = sunInfo.rawSolarFlux; vd.mainSun = sunInfo; } } vd.sunlightFactor = 0.0; foreach (SunInfo sunInfo in vd.sunsInfo) { sunInfo.fluxProportion = sunInfo.rawSolarFlux / vd.rawSolarFluxTotal; vd.sunlightFactor += sunInfo.SunlightFactor * sunInfo.fluxProportion; } // avoid rounding errors if (vd.sunlightFactor > 0.99) { vd.sunlightFactor = 1.0; } }
public vessel_info(Vessel v, uint 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 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; // at the two highest timewarp speed, the number of sun visibility samples drop to the point that // the quantization error first became noticeable, and then exceed 100% // to solve this, we switch to an analytical estimation of the portion of orbit that was in sunlight // - we check against timewarp rate, instead of index, to avoid issues during timewarp blending if (v.mainBody.flightGlobalsIndex != 0 && TimeWarp.CurrentRate > 1000.0f) { sunlight = 1.0 - Sim.ShadowPeriod(v) / Sim.OrbitalPeriod(v); } // 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); // 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); // signal info antenna = new AntennaInfo(v); avoid_inf_recursion.Add(v.id); connection = Signal.connection(v, position, antenna, blackout, avoid_inf_recursion); transmitting = Science.transmitting(v, connection.linked); relaying = Signal.relaying(v, avoid_inf_recursion); avoid_inf_recursion.Remove(v.id); // habitat data volume = Habitat.tot_volume(v); surface = Habitat.tot_surface(v); pressure = Habitat.pressure(v); poisoning = Habitat.poisoning(v); shielding = Habitat.shielding(v); living_space = Habitat.living_space(v); comforts = new Comforts(v, landed, crew_count > 1, connection.linked); // data about greenhouses greenhouses = Greenhouse.Greenhouses(v); // other stuff gravioli = Sim.Graviolis(v); }
// draw the window void render(int _) { // shortcut CelestialBody sun = FlightGlobals.Bodies[0]; // get selected body CelestialBody body = Lib.SelectedBody(); // 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); // draw pseudo-title GUILayout.BeginHorizontal(); GUILayout.Label(body.bodyName.ToUpper(), top_style); GUILayout.EndHorizontal(); // surface panel string temperature_str = body.atmosphere ? Lib.HumanReadableTemp(temperature) : Lib.BuildString(Lib.HumanReadableTemp(temperature_min), " / ", Lib.HumanReadableTemp(temperature)); render_title("SURFACE"); render_content("temperature", temperature_str); render_content("radiation", Lib.HumanReadableRadiationRate(radiation)); render_content("solar flux", Lib.HumanReadableFlux(solar_flux)); render_space(); // atmosphere panel if (body.atmosphere) { render_title("ATMOSPHERE"); render_content("breathable", body.atmosphereContainsOxygen ? "yes" : "no"); render_content("light absorption", Lib.HumanReadablePerc(1.0 - Sim.AtmosphereFactor(body, 0.7071))); render_content("gamma absorption", Lib.HumanReadablePerc(1.0 - Sim.GammaTransparency(body, 0.0))); render_space(); } // rendering panel render_title("RENDERING"); render_content("inner belt", ref Radiation.show_inner); render_content("outer belt", ref Radiation.show_outer); render_content("magnetopause", ref Radiation.show_pause); render_space(); // draw footer GUILayout.BeginHorizontal(); GUILayout.Label("(ALT+N to open and close)", bot_style); if (Lib.IsClicked()) Close(); GUILayout.EndHorizontal(); // enable dragging GUI.DragWindow(drag_rect); }
public vessel_info(Vessel v, uint vessel_id, UInt64 inc) { // NOTE: anything used here can't in turn use cache, unless you know what you are doing // 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 resque mission vessel is_resque = Lib.IsResqueMission(v); if (is_resque) return; // dead EVA are not valid vessels if (v.isEVA && EVA.KerbalData(v).eva_dead) 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 once position = Lib.VesselPosition(v); // 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; // if the orbit length vs simulation step is lower than an acceptable threshold, use discrete sun visibility if (v.mainBody.flightGlobalsIndex != 0) { double orbit_period = Sim.OrbitalPeriod(v); if (orbit_period / Kerbalism.elapsed_s < 16.0) sunlight = 1.0 - Sim.ShadowPeriod(v) / orbit_period; } // calculate environment stuff atmo_factor = Sim.AtmosphereFactor(v.mainBody, position, sun_dir); gamma_transparency = Sim.GammaTransparency(v.mainBody, v.altitude); breathable = Sim.Breathable(v); landed = Lib.Landed(v); // calculate temperature at vessel position temperature = Sim.Temperature(v, position, sunlight, atmo_factor, out solar_flux, out albedo_flux, out body_flux, out total_flux); // calculate radiation radiation = Radiation.Compute(v, position, gamma_transparency, sunlight, out blackout, out inside_pause, out inside_belt); // calculate malfunction stuff max_malfunction = Reliability.MaxMalfunction(v); avg_quality = Reliability.AverageQuality(v); // calculate signal info antenna = new antenna_data(v); avoid_inf_recursion.Add(v.id); link = Signal.Link(v, position, antenna, blackout, avoid_inf_recursion); avoid_inf_recursion.Remove(v.id); // partial data about modules, used by vessel info/monitor scrubbers = Scrubber.PartialData(v); recyclers = Recycler.PartialData(v); greenhouses = Greenhouse.PartialData(v); // woot relativity time_dilation = Sim.TimeDilation(v); }
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, 24), " <color=#cccccc>BODY INFO</color>")); }
/// <summary> /// Update the 'sunsInfo' list and the 'mainSun', 'solarFluxTotal' variables. /// Uses discrete or analytic (for high timewarp speeds) evaluation methods based on the isAnalytic bool. /// Require the 'visibleBodies' variable to be set. /// </summary> // at the two highest timewarp speed, the number of sun visibility samples drop to the point that // the quantization error first became noticeable, and then exceed 100%, to solve this: // - we switch to an analytical estimation of the sunlight/shadow period // - atmo_factor become an average atmospheric absorption factor over the daylight period (not the whole day) public static void UpdateSunsInfo(VesselData vd, Vector3d vesselPosition, double elapsedSeconds) { Vessel v = vd.Vessel; double lastSolarFlux = 0.0; vd.sunsInfo = new List <SunInfo>(Sim.suns.Count); vd.solarFluxTotal = 0.0; vd.rawSolarFluxTotal = 0.0; foreach (Sim.SunData sunData in Sim.suns) { SunInfo sunInfo = new SunInfo(sunData); if (vd.isAnalytic) { // get sun direction and distance Lib.DirectionAndDistance(vesselPosition, sunInfo.sunData.body, out sunInfo.direction, out sunInfo.distance); // analytical estimation of the portion of orbit that was in sunlight. // it has some limitations, see the comments on Sim.ShadowPeriod if (Settings.UseSamplingSunFactor) { // sampling estimation of the portion of orbit that is in sunlight // until we will calculate again sunInfo.sunlightFactor = Sim.SampleSunFactor(v, elapsedSeconds); } else { // analytical estimation of the portion of orbit that was in sunlight. // it has some limitations, see the comments on Sim.ShadowPeriod sunInfo.sunlightFactor = 1.0 - Sim.ShadowPeriod(v) / Sim.OrbitalPeriod(v); } // get atmospheric absorbtion // for atmospheric bodies whose rotation period is less than 120 hours, // determine analytic atmospheric absorption over a single body revolution instead // of using a discrete value that would be unreliable at large timesteps : if (vd.inAtmosphere) { sunInfo.atmoFactor = Sim.AtmosphereFactorAnalytic(v.mainBody, vesselPosition, sunInfo.direction); } else { sunInfo.atmoFactor = 1.0; } } else { // determine if in sunlight, calculate sun direction and distance sunInfo.sunlightFactor = Sim.IsBodyVisible(v, vesselPosition, sunData.body, vd.visibleBodies, out sunInfo.direction, out sunInfo.distance) ? 1.0 : 0.0; // get atmospheric absorbtion sunInfo.atmoFactor = Sim.AtmosphereFactor(v.mainBody, vesselPosition, sunInfo.direction); } // get resulting solar flux in W/m² sunInfo.rawSolarFlux = sunInfo.sunData.SolarFlux(sunInfo.distance); sunInfo.solarFlux = sunInfo.rawSolarFlux * sunInfo.sunlightFactor * sunInfo.atmoFactor; // increment total flux from all stars vd.rawSolarFluxTotal += sunInfo.rawSolarFlux; vd.solarFluxTotal += sunInfo.solarFlux; // add the star to the list vd.sunsInfo.Add(sunInfo); // the most powerful star will be our "default" sun. Uses raw flux before atmo / sunlight factor if (sunInfo.rawSolarFlux > lastSolarFlux) { lastSolarFlux = sunInfo.rawSolarFlux; vd.mainSun = sunInfo; } } vd.sunlightFactor = 0.0; foreach (SunInfo sunInfo in vd.sunsInfo) { sunInfo.fluxProportion = sunInfo.rawSolarFlux / vd.rawSolarFluxTotal; vd.sunlightFactor += sunInfo.SunlightFactor * sunInfo.fluxProportion; } // avoid rounding errors if (vd.sunlightFactor > 0.99) { vd.sunlightFactor = 1.0; } }
// called at every simulation step public void FixedUpdate() { // do nothing if paused if (Lib.IsPaused()) return; // do nothing if DB isn't ready if (!DB.Ready()) return; // for each vessel foreach(Vessel vessel in FlightGlobals.Vessels) { // skip invalid vessels if (!Lib.IsVessel(vessel)) continue; // skip loaded vessels if (vessel.loaded) continue; // get vessel data from the db vessel_data vd = DB.VesselData(vessel.id); // get vessel info from the cache vessel_info info = Cache.VesselInfo(vessel); // calculate atmospheric factor (proportion of flux not blocked by atmosphere) double atmo_factor = Sim.AtmosphereFactor(vessel.mainBody, info.position, info.sun_dir); // for each part foreach(ProtoPartSnapshot part in vessel.protoVessel.protoPartSnapshots) { // get part prefab (required for module properties) Part part_prefab = PartLoader.getPartInfoByName(part.partName).partPrefab; // store index of ModuleResourceConverter to process // rationale: a part can contain multiple resource converters int converter_index = 0; // for each module foreach(ProtoPartModuleSnapshot module in part.modules) { // something weird is going on, skip this if (!part_prefab.Modules.Contains(module.moduleName)) continue; // command module if (module.moduleName == "ModuleCommand") { // get module from prefab ModuleCommand command = part_prefab.Modules.GetModules<ModuleCommand>()[0]; // do not consume if this is a MCM with no crew // rationale: for consistency, the game doesn't consume resources for MCM without crew in loaded vessels // this make some sense: you left a vessel with some battery and nobody on board, you expect it to not consume EC if (command.minimumCrew == 0 || part.protoModuleCrew.Count > 0) { // for each input resource foreach(ModuleResource ir in command.inputResources) { // consume the resource Lib.RequestResource(vessel, ir.name, ir.rate * TimeWarp.fixedDeltaTime); } } } // solar panel else if (module.moduleName == "ModuleDeployableSolarPanel") { // determine if extended bool extended = module.moduleValues.GetValue("stateString") == ModuleDeployableSolarPanel.panelStates.EXTENDED.ToString(); // if in sunlight and extended if (info.sunlight && extended) { // get module from prefab ModuleDeployableSolarPanel panel = part_prefab.Modules.GetModules<ModuleDeployableSolarPanel>()[0]; // produce electric charge Lib.RequestResource(vessel, "ElectricCharge", -PanelOutput(vessel, part, panel, info.sun_dir, info.sun_dist, atmo_factor) * TimeWarp.fixedDeltaTime * Malfunction.Penalty(part)); } } // generator // note: assume generators require all input else if (module.moduleName == "ModuleGenerator") { // determine if active bool activated = Convert.ToBoolean(module.moduleValues.GetValue("generatorIsActive")); // if active if (activated) { // get module from prefab ModuleGenerator generator = part_prefab.Modules.GetModules<ModuleGenerator>()[0]; // determine if vessel is full of all output resources bool full = true; foreach(var or in generator.outputList) { double amount = Lib.GetResourceAmount(vessel, or.name); double capacity = Lib.GetResourceCapacity(vessel, or.name); double perc = capacity > 0.0 ? amount / capacity : 0.0; full &= (perc >= 1.0 - double.Epsilon); } // if not full if (!full) { // calculate worst required resource percentual double worst_input = 1.0; foreach(var ir in generator.inputList) { double required = ir.rate * TimeWarp.fixedDeltaTime; double amount = Lib.GetResourceAmount(vessel, ir.name); worst_input = Math.Min(worst_input, amount / required); } // for each input resource foreach(var ir in generator.inputList) { // consume the resource Lib.RequestResource(vessel, ir.name, ir.rate * worst_input * TimeWarp.fixedDeltaTime); } // for each output resource foreach(var or in generator.outputList) { // produce the resource Lib.RequestResource(vessel, or.name, -or.rate * worst_input * TimeWarp.fixedDeltaTime * Malfunction.Penalty(part)); } } } } // converter // note: support multiple resource converters // note: ignore stock temperature mechanic of converters // note: ignore autoshutdown // note: ignore crew experience bonus (seem that stock ignore it too) // note: 'undo' stock behaviour by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation) // note: support PlanetaryBaseSystem converters // note: support NearFuture reactors else if (module.moduleName == "ModuleResourceConverter" || module.moduleName == "ModuleKPBSConverter" || module.moduleName == "FissionReactor") { // get module from prefab ModuleResourceConverter converter = part_prefab.Modules.GetModules<ModuleResourceConverter>()[converter_index++]; // determine if active bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated")); // if active if (activated) { // determine if vessel is full of all output resources bool full = true; foreach(var or in converter.outputList) { double amount = Lib.GetResourceAmount(vessel, or.ResourceName); double capacity = Lib.GetResourceCapacity(vessel, or.ResourceName); double perc = capacity > 0.0 ? amount / capacity : 0.0; full &= (perc >= converter.FillAmount - double.Epsilon); } // if not full if (!full) { // calculate worst required resource percentual double worst_input = 1.0; foreach(var ir in converter.inputList) { double required = ir.Ratio * TimeWarp.fixedDeltaTime; double amount = Lib.GetResourceAmount(vessel, ir.ResourceName); worst_input = Math.Min(worst_input, amount / required); } // for each input resource foreach(var ir in converter.inputList) { // consume the resource Lib.RequestResource(vessel, ir.ResourceName, ir.Ratio * worst_input * TimeWarp.fixedDeltaTime); } // for each output resource foreach(var or in converter.outputList) { // produce the resource Lib.RequestResource(vessel, or.ResourceName, -or.Ratio * worst_input * TimeWarp.fixedDeltaTime * Malfunction.Penalty(part)); } } // undo stock behaviour by forcing last_update_time to now module.moduleValues.SetValue("lastUpdateTime", Planetarium.GetUniversalTime().ToString()); } } // drill // note: ignore stock temperature mechanic of harvesters // note: ignore autoshutdown // note: ignore depletion (stock seem to do the same) // note: 'undo' stock behaviour by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation) else if (module.moduleName == "ModuleResourceHarvester") { // determine if active bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated")); // if active if (activated) { // get module from prefab ModuleResourceHarvester harvester = part_prefab.Modules.GetModules<ModuleResourceHarvester>()[0]; // [disabled] reason: not working // deduce crew bonus /*double experience_bonus = 0.0; if (harvester.UseSpecialistBonus) { foreach(ProtoCrewMember c in vessel.protoVessel.GetVesselCrew()) { experience_bonus = Math.Max(experience_bonus, (c.trait == harvester.Specialty) ? (double)c.experienceLevel : 0.0); } }*/ const double crew_bonus = 1.0; //harvester.SpecialistBonusBase + (experience_bonus + 1.0) * harvester.SpecialistEfficiencyFactor; // detect amount of ore in the ground AbundanceRequest request = new AbundanceRequest { Altitude = vessel.altitude, BodyId = vessel.mainBody.flightGlobalsIndex, CheckForLock = false, Latitude = vessel.latitude, Longitude = vessel.longitude, ResourceType = (HarvestTypes)harvester.HarvesterType, ResourceName = harvester.ResourceName }; double abundance = ResourceMap.Instance.GetAbundance(request); // if there is actually something (should be if active when unloaded) if (abundance > harvester.HarvestThreshold) { // calculate worst required resource percentual double worst_input = 1.0; foreach(var ir in harvester.inputList) { double required = ir.Ratio * TimeWarp.fixedDeltaTime; double amount = Lib.GetResourceAmount(vessel, ir.ResourceName); worst_input = Math.Min(worst_input, amount / required); } // for each input resource foreach(var ir in harvester.inputList) { // consume the resource Lib.RequestResource(vessel, ir.ResourceName, ir.Ratio * worst_input * TimeWarp.fixedDeltaTime); } // determine resource produced double res = abundance * harvester.Efficiency * crew_bonus * worst_input * Malfunction.Penalty(part); // accumulate ore Lib.RequestResource(vessel, harvester.ResourceName, -res * TimeWarp.fixedDeltaTime); } // undo stock behaviour by forcing last_update_time to now module.moduleValues.SetValue("lastUpdateTime", Planetarium.GetUniversalTime().ToString()); } } // asteroid drill // note: untested // note: ignore stock temperature mechanic of asteroid drills // note: ignore autoshutdown // note: 'undo' stock behaviour by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation) else if (module.moduleName == "ModuleAsteroidDrill") { // determine if active bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated")); // if active if (activated) { // get module from prefab ModuleAsteroidDrill asteroid_drill = part_prefab.Modules.GetModules<ModuleAsteroidDrill>()[0]; // [disabled] reason: not working // deduce crew bonus /*double experience_bonus = 0.0; if (asteroid_drill.UseSpecialistBonus) { foreach(ProtoCrewMember c in vessel.protoVessel.GetVesselCrew()) { experience_bonus = Math.Max(experience_bonus, (c.trait == asteroid_drill.Specialty) ? (double)c.experienceLevel : 0.0); } }*/ const double crew_bonus = 1.0; //asteroid_drill.SpecialistBonusBase + (experience_bonus + 1.0) * asteroid_drill.SpecialistEfficiencyFactor; // get asteroid data ProtoPartModuleSnapshot asteroid_info = null; ProtoPartModuleSnapshot asteroid_resource = null; foreach(ProtoPartSnapshot p in vessel.protoVessel.protoPartSnapshots) { if (asteroid_info == null) asteroid_info = p.modules.Find(k => k.moduleName == "ModuleAsteroidInfo"); if (asteroid_resource == null) asteroid_resource = p.modules.Find(k => k.moduleName == "ModuleAsteroidResource"); } // if there is actually an asteroid attached to this active asteroid drill (it should) if (asteroid_info != null && asteroid_resource != null) { // get some data double mass_threshold = Convert.ToDouble(asteroid_info.moduleValues.GetValue("massThresholdVal")); double mass = Convert.ToDouble(asteroid_info.moduleValues.GetValue("currentMassVal")); double abundance = Convert.ToDouble(asteroid_resource.moduleValues.GetValue("abundance")); string res_name = asteroid_resource.moduleValues.GetValue("resourceName"); double res_density = PartResourceLibrary.Instance.GetDefinition(res_name).density; // if asteroid isn't depleted if (mass > mass_threshold && abundance > double.Epsilon) { // consume EC double ec_required = asteroid_drill.PowerConsumption * TimeWarp.fixedDeltaTime; double ec_consumed = Lib.RequestResource(vessel, "ElectricCharge", ec_required); double ec_ratio = ec_consumed / ec_required; // determine resource extracted double res_amount = abundance * asteroid_drill.Efficiency * crew_bonus * ec_ratio * TimeWarp.fixedDeltaTime; // produce mined resource Lib.RequestResource(vessel, res_name, -res_amount); // consume asteroid mass asteroid_info.moduleValues.SetValue("currentMassVal", (mass - res_density * res_amount).ToString()); } } // undo stock behaviour by forcing last_update_time to now module.moduleValues.SetValue("lastUpdateTime", Planetarium.GetUniversalTime().ToString()); } } // science lab // note: we are only simulating the EC consumption // note: there is no easy way to 'stop' the lab when there isn't enough EC else if (module.moduleName == "ModuleScienceConverter") { // get module from prefab ModuleScienceConverter lab = part_prefab.Modules.GetModules<ModuleScienceConverter>()[0]; // determine if active bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated")); // if active if (activated) { Lib.RequestResource(vessel, "ElectricCharge", lab.powerRequirement * TimeWarp.fixedDeltaTime); } } // SCANSAT support else if (module.moduleName == "SCANsat" || module.moduleName == "ModuleSCANresourceScanner") { // get ec consumption rate PartModule scansat = part_prefab.Modules[module.moduleName]; double power = Lib.ReflectionValue<float>(scansat, "power"); double ec_required = power * TimeWarp.fixedDeltaTime; bool is_scanning = Lib.GetProtoValue<bool>(module, "scanning"); bool was_disabled = vd.scansat_id.Contains(part.flightID); // if its scanning if (Lib.GetProtoValue<bool>(module, "scanning")) { // consume ec double ec_consumed = Lib.RequestResource(vessel, "ElectricCharge", ec_required); // if there isn't enough ec if (ec_consumed < ec_required * 0.99 && ec_required > double.Epsilon) { // unregister scanner SCANsat.stopScanner(vessel, module, part_prefab); // remember disabled scanner vd.scansat_id.Add(part.flightID); // give the user some feedback if (DB.VesselData(vessel.id).cfg_ec == 1) Message.Post("SCANsat sensor was disabled on <b>" + vessel.vesselName + "</b>"); } } // if it was disabled else if (vd.scansat_id.Contains(part.flightID)) { // if there is enough ec double ec_amount = Lib.GetResourceAmount(vessel, "ElectricCharge"); double ec_capacity = Lib.GetResourceCapacity(vessel, "ElectricCharge"); if (ec_capacity > double.Epsilon && ec_amount / ec_capacity > 0.25) //< re-enable at 25% EC { // re-enable the scanner SCANsat.resumeScanner(vessel, module, part_prefab); // give the user some feedback if (DB.VesselData(vessel.id).cfg_ec == 1) Message.Post("SCANsat sensor resumed operations on <b>" + vessel.vesselName + "</b>"); } } // forget active scanners if (Lib.GetProtoValue<bool>(module, "scanning")) vd.scansat_id.Remove(part.flightID); } // NearFutureSolar support // note: we assume deployed, this is a current limitation else if (module.moduleName == "ModuleCurvedSolarPanel") { // if in sunlight if (info.sunlight) { PartModule curved_panel = part_prefab.Modules[module.moduleName]; double output = CurvedPanelOutput(vessel, part, part_prefab, curved_panel, info.sun_dir, info.sun_dist, atmo_factor) * Malfunction.Penalty(part); Lib.RequestResource(vessel, "ElectricCharge", -output * TimeWarp.fixedDeltaTime); } } // NearFutureElectrical support // note: fission generator ignore heat // note: radioisotope generator doesn't support easy mode else if (module.moduleName == "FissionGenerator") { PartModule generator = part_prefab.Modules[module.moduleName]; double power = Lib.ReflectionValue<float>(generator, "PowerGeneration"); // get fission reactor tweakable, will default to 1.0 for other modules var reactor = part.modules.Find(k => k.moduleName == "FissionReactor"); double tweakable = reactor == null ? 1.0 : Lib.ConfigValue(reactor.moduleValues, "CurrentPowerPercent", 100.0) * 0.01; Lib.RequestResource(vessel, "ElectricCharge", -power * tweakable * TimeWarp.fixedDeltaTime); } else if (module.moduleName == "ModuleRadioisotopeGenerator") { double mission_time = vessel.missionTime / (3600.0 * Lib.HoursInDay() * Lib.DaysInYear()); PartModule generator = part_prefab.Modules[module.moduleName]; double half_life = Lib.ReflectionValue<float>(generator, "HalfLife"); double remaining = Math.Pow(2.0, (-mission_time) / half_life); double power = Lib.ReflectionValue<float>(generator, "BasePower"); Lib.RequestResource(vessel, "ElectricCharge", -power * remaining * TimeWarp.fixedDeltaTime); } // KERBALISM modules else if (module.moduleName == "Scrubber") { Scrubber.BackgroundUpdate(vessel, part.flightID); } else if (module.moduleName == "Greenhouse") { Greenhouse.BackgroundUpdate(vessel, part.flightID); } else if (module.moduleName == "GravityRing") { GravityRing.BackgroundUpdate(vessel, part.flightID); } else if (module.moduleName == "Malfunction") { Malfunction.BackgroundUpdate(vessel, part.flightID); } } } } }
// called at every simulation step public void FixedUpdate() { // do nothing if paused if (Lib.IsPaused()) return; // do nothing if DB isn't ready if (!DB.Ready()) return; // for each vessel foreach(Vessel vessel in FlightGlobals.Vessels) { // skip invalid vessels if (!Lib.IsVessel(vessel)) continue; // skip loaded vessels if (vessel.loaded) continue; // get vessel info from the cache vessel_info info = Cache.VesselInfo(vessel); // calculate atmospheric factor (proportion of flux not blocked by atmosphere) double atmo_factor = Sim.AtmosphereFactor(vessel.mainBody, info.position, info.sun_dir); // for each part foreach(ProtoPartSnapshot part in vessel.protoVessel.protoPartSnapshots) { // get part prefab (required for module properties) Part part_prefab = PartLoader.getPartInfoByName(part.partName).partPrefab; // store index of ModuleResourceConverter to process // rationale: a part can contain multiple resource converters int converter_index = 0; // for each module foreach(ProtoPartModuleSnapshot module in part.modules) { // command module if (module.moduleName == "ModuleCommand") { // get module from prefab ModuleCommand command = part_prefab.Modules.GetModules<ModuleCommand>()[0]; // do not consume if this is a MCM with no crew // rationale: for consistency, the game doesn't consume resources for MCM without crew in loaded vessels // this make some sense: you left a vessel with some battery and nobody on board, you expect it to not consume EC if (command.minimumCrew == 0 || part.protoModuleCrew.Count > 0) { // for each input resource foreach(ModuleResource ir in command.inputResources) { // consume the resource Lib.RequestResource(vessel, ir.name, ir.rate * TimeWarp.fixedDeltaTime); } } } // solar panel else if (module.moduleName == "ModuleDeployableSolarPanel") { // determine if extended bool extended = module.moduleValues.GetValue("stateString") == ModuleDeployableSolarPanel.panelStates.EXTENDED.ToString(); // if in sunlight and extended if (info.sunlight && extended) { // get module from prefab ModuleDeployableSolarPanel panel = part_prefab.Modules.GetModules<ModuleDeployableSolarPanel>()[0]; // produce electric charge Lib.RequestResource(vessel, "ElectricCharge", -PanelOutput(vessel, part, panel, info.sun_dir, info.sun_dist, atmo_factor) * TimeWarp.fixedDeltaTime * Malfunction.Penalty(part)); } } // generator // note: assume generators require all input else if (module.moduleName == "ModuleGenerator") { // determine if active bool activated = Convert.ToBoolean(module.moduleValues.GetValue("generatorIsActive")); // if active if (activated) { // get module from prefab ModuleGenerator generator = part_prefab.Modules.GetModules<ModuleGenerator>()[0]; // determine if vessel is full of all output resources bool full = true; foreach(var or in generator.outputList) { double amount = Lib.GetResourceAmount(vessel, or.name); double capacity = Lib.GetResourceCapacity(vessel, or.name); double perc = capacity > 0.0 ? amount / capacity : 0.0; full &= (perc >= 1.0 - double.Epsilon); } // if not full if (!full) { // calculate worst required resource percentual double worst_input = 1.0; foreach(var ir in generator.inputList) { double required = ir.rate * TimeWarp.fixedDeltaTime; double amount = Lib.GetResourceAmount(vessel, ir.name); worst_input = Math.Min(worst_input, amount / required); } // for each input resource foreach(var ir in generator.inputList) { // consume the resource Lib.RequestResource(vessel, ir.name, ir.rate * worst_input * TimeWarp.fixedDeltaTime); } // for each output resource foreach(var or in generator.outputList) { // produce the resource Lib.RequestResource(vessel, or.name, -or.rate * worst_input * TimeWarp.fixedDeltaTime * Malfunction.Penalty(part)); } } } } // converter // note: support multiple resource converters // note: ignore stock temperature mechanic of converters // note: ignore autoshutdown // note: ignore crew experience bonus (seem that stock ignore it too) // note: 'undo' stock behaviour by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation) else if (module.moduleName == "ModuleResourceConverter") { // determine if active bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated")); // if active if (activated) { // get module from prefab ModuleResourceConverter converter = part_prefab.Modules.GetModules<ModuleResourceConverter>()[converter_index++]; // determine if vessel is full of all output resources bool full = true; foreach(var or in converter.outputList) { double amount = Lib.GetResourceAmount(vessel, or.ResourceName); double capacity = Lib.GetResourceCapacity(vessel, or.ResourceName); double perc = capacity > 0.0 ? amount / capacity : 0.0; full &= (perc >= converter.FillAmount - double.Epsilon); } // if not full if (!full) { // calculate worst required resource percentual double worst_input = 1.0; foreach(var ir in converter.inputList) { double required = ir.Ratio * TimeWarp.fixedDeltaTime; double amount = Lib.GetResourceAmount(vessel, ir.ResourceName); worst_input = Math.Min(worst_input, amount / required); } // for each input resource foreach(var ir in converter.inputList) { // consume the resource Lib.RequestResource(vessel, ir.ResourceName, ir.Ratio * worst_input * TimeWarp.fixedDeltaTime); } // for each output resource foreach(var or in converter.outputList) { // produce the resource Lib.RequestResource(vessel, or.ResourceName, -or.Ratio * worst_input * TimeWarp.fixedDeltaTime * Malfunction.Penalty(part)); } } // undo stock behaviour by forcing last_update_time to now module.moduleValues.SetValue("lastUpdateTime", Planetarium.GetUniversalTime().ToString()); } } // drill // note: ignore stock temperature mechanic of harvesters // note: ignore autoshutdown // note: ignore depletion (stock seem to do the same) // note: 'undo' stock behaviour by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation) else if (module.moduleName == "ModuleResourceHarvester") { // determine if active bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated")); // if active if (activated) { // get module from prefab ModuleResourceHarvester harvester = part_prefab.Modules.GetModules<ModuleResourceHarvester>()[0]; // deduce crew bonus double experience_bonus = 0.0; if (harvester.UseSpecialistBonus) { foreach(ProtoCrewMember c in vessel.protoVessel.GetVesselCrew()) { experience_bonus = Math.Max(experience_bonus, (c.trait == harvester.Specialty) ? (double)c.experienceLevel : 0.0); } } double crew_bonus = harvester.SpecialistBonusBase + (experience_bonus + 1.0) * harvester.SpecialistEfficiencyFactor; // detect amount of ore in the ground AbundanceRequest request = new AbundanceRequest { Altitude = vessel.altitude, BodyId = vessel.mainBody.flightGlobalsIndex, CheckForLock = false, Latitude = vessel.latitude, Longitude = vessel.longitude, ResourceType = (HarvestTypes)harvester.HarvesterType, ResourceName = harvester.ResourceName }; double abundance = ResourceMap.Instance.GetAbundance(request); // if there is actually something (should be if active when unloaded) if (abundance > harvester.HarvestThreshold) { // calculate worst required resource percentual double worst_input = 1.0; foreach(var ir in harvester.inputList) { double required = ir.Ratio * TimeWarp.fixedDeltaTime; double amount = Lib.GetResourceAmount(vessel, ir.ResourceName); worst_input = Math.Min(worst_input, amount / required); } // for each input resource foreach(var ir in harvester.inputList) { // consume the resource Lib.RequestResource(vessel, ir.ResourceName, ir.Ratio * worst_input * TimeWarp.fixedDeltaTime); } // determine resource produced double res = abundance * harvester.Efficiency * crew_bonus * worst_input * Malfunction.Penalty(part); // accumulate ore Lib.RequestResource(vessel, harvester.ResourceName, -res * TimeWarp.fixedDeltaTime); } // undo stock behaviour by forcing last_update_time to now module.moduleValues.SetValue("lastUpdateTime", Planetarium.GetUniversalTime().ToString()); } } // asteroid drill // note: untested // note: ignore stock temperature mechanic of asteroid drills // note: ignore autoshutdown // note: 'undo' stock behaviour by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation) else if (module.moduleName == "ModuleAsteroidDrill") { // determine if active bool activated = Convert.ToBoolean(module.moduleValues.GetValue("IsActivated")); // if active if (activated) { // get module from prefab ModuleAsteroidDrill asteroid_drill = part_prefab.Modules.GetModules<ModuleAsteroidDrill>()[0]; // deduce crew bonus double experience_bonus = 0.0; if (asteroid_drill.UseSpecialistBonus) { foreach(ProtoCrewMember c in vessel.protoVessel.GetVesselCrew()) { experience_bonus = Math.Max(experience_bonus, (c.trait == asteroid_drill.Specialty) ? (double)c.experienceLevel : 0.0); } } double crew_bonus = asteroid_drill.SpecialistBonusBase + (experience_bonus + 1.0) * asteroid_drill.SpecialistEfficiencyFactor; // get asteroid data ProtoPartModuleSnapshot asteroid_info = null; ProtoPartModuleSnapshot asteroid_resource = null; foreach(ProtoPartSnapshot p in vessel.protoVessel.protoPartSnapshots) { if (asteroid_info == null) asteroid_info = p.modules.Find(k => k.moduleName == "ModuleAsteroidInfo"); if (asteroid_resource == null) asteroid_resource = p.modules.Find(k => k.moduleName == "ModuleAsteroidResource"); } // if there is actually an asteroid attached to this active asteroid drill (it should) if (asteroid_info != null && asteroid_resource != null) { // get some data double mass_threshold = Convert.ToDouble(asteroid_info.moduleValues.GetValue("massThresholdVal")); double mass = Convert.ToDouble(asteroid_info.moduleValues.GetValue("currentMassVal")); double abundance = Convert.ToDouble(asteroid_resource.moduleValues.GetValue("abundance")); string res_name = asteroid_resource.moduleValues.GetValue("resourceName"); double res_density = PartResourceLibrary.Instance.GetDefinition(res_name).density; // if asteroid isn't depleted if (mass > mass_threshold && abundance > double.Epsilon) { // consume EC double ec_required = asteroid_drill.PowerConsumption * TimeWarp.fixedDeltaTime; double ec_consumed = Lib.RequestResource(vessel, "ElectricCharge", ec_required); double ec_ratio = ec_consumed / ec_required; // determine resource extracted double res_amount = abundance * asteroid_drill.Efficiency * crew_bonus * ec_ratio * TimeWarp.fixedDeltaTime; // produce mined resource Lib.RequestResource(vessel, res_name, -res_amount); // consume asteroid mass asteroid_info.moduleValues.SetValue("currentMassVal", (mass - res_density * res_amount).ToString()); } } // undo stock behaviour by forcing last_update_time to now module.moduleValues.SetValue("lastUpdateTime", Planetarium.GetUniversalTime().ToString()); } } // SCANSAT support (new version) // TODO: enable better SCANsat support /*else if (module.moduleName == "SCANsat" || module.moduleName == "ModuleSCANresourceScanner") { // get ec consumption rate PartModule scansat = part_prefab.Modules[module.moduleName]; double power = Lib.ReflectionValue<float>(scansat, "power"); double ec_required = power * TimeWarp.fixedDeltaTime; // if it was scanning if (SCANsat.wasScanning(module)) { // if there is enough ec double ec_amount = Lib.GetResourceAmount(vessel, "ElectricCharge"); double ec_capacity = Lib.GetResourceCapacity(vessel, "ElectricCharge"); if (ec_capacity > double.Epsilon && ec_amount / ec_capacity > 0.15) //< re-enable at 15% EC { // re-enable the scanner SCANsat.resumeScanner(vessel, module, part_prefab); // give the user some feedback if (DB.VesselData(vessel.id).cfg_ec == 1) Message.Post(Severity.relax, "SCANsat> sensor on <b>" + vessel.vesselName + "</b> resumed operations", "we got enough ElectricCharge"); } } // if it is scanning if (SCANsat.isScanning(module)) { // consume ec double ec_consumed = Lib.RequestResource(vessel, "ElectricCharge", ec_required); // if there isn't enough ec if (ec_consumed < ec_required * 0.99 && ec_required > double.Epsilon) { // unregister scanner, and remember it SCANsat.stopScanner(vessel, module, part_prefab); // give the user some feedback if (DB.VesselData(vessel.id).cfg_ec == 1) Message.Post(Severity.warning, "SCANsat sensor was disabled on <b>" + vessel.vesselName + "</b>", "for lack of ElectricCharge"); } } }*/ // SCANSAT support (old version) // note: this one doesn't support re-activation, is a bit slower and less clean // waiting for DMagic to fix a little bug else if (module.moduleName == "SCANsat" || module.moduleName == "ModuleSCANresourceScanner") { // determine if scanning bool scanning = Convert.ToBoolean(module.moduleValues.GetValue("scanning")); // consume ec if (scanning) { // get ec consumption PartModule scansat = part_prefab.Modules[module.moduleName]; double power = Lib.ReflectionValue<float>(scansat, "power"); // consume ec double ec_required = power * TimeWarp.fixedDeltaTime; double ec_consumed = Lib.RequestResource(vessel, "ElectricCharge", ec_required); // if there isn't enough ec if (ec_consumed < ec_required * 0.99 && ec_required > double.Epsilon) { // unregister scanner using reflection foreach(var a in AssemblyLoader.loadedAssemblies) { if (a.name == "SCANsat") { Type controller_type = a.assembly.GetType("SCANsat.SCANcontroller"); System.Object controller = controller_type.GetProperty("controller", BindingFlags.Public | BindingFlags.Static).GetValue(null, null); controller_type.InvokeMember("removeVessel", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, controller, new System.Object[]{vessel}); } } // disable scanning module.moduleValues.SetValue("scanning", false.ToString()); // give the user some feedback if (DB.VesselData(vessel.id).cfg_ec == 1) Message.Post(Severity.warning, "SCANsat sensor was disabled on <b>" + vessel.vesselName + "</b>", "for lack of ElectricCharge"); } } } // NearFutureSolar support // note: we assume deployed, this is a current limitation else if (module.moduleName == "ModuleCurvedSolarPanel") { // [unused] determine if extended //string state = module.moduleValues.GetValue("SavedState"); //bool extended = state == ModuleDeployableSolarPanel.panelStates.EXTENDED.ToString(); // if in sunlight if (info.sunlight) { // produce electric charge double output = CurvedPanelOutput(vessel, part, part_prefab, info.sun_dir, info.sun_dist, atmo_factor) * Malfunction.Penalty(part); Lib.RequestResource(vessel, "ElectricCharge", -output * TimeWarp.fixedDeltaTime); } } // KERBALISM modules else if (module.moduleName == "Scrubber") { Scrubber.BackgroundUpdate(vessel, part.flightID); } else if (module.moduleName == "Greenhouse") { Greenhouse.BackgroundUpdate(vessel, part.flightID); } else if (module.moduleName == "Malfunction") { Malfunction.BackgroundUpdate(vessel, part.flightID); } } } } }