// execute the recipe public void Execute(Vessel v, vessel_resources resources) { // determine worst input ratio double worst_input = 1.0; foreach (var pair in inputs) { if (pair.Value > double.Epsilon) //< avoid division by zero { resource_info res = resources.Info(v, pair.Key); worst_input = Math.Min(worst_input, Math.Max(0.0, res.amount + res.deferred) / pair.Value); } } // consume inputs foreach (var pair in inputs) { resource_info res = resources.Info(v, pair.Key); res.Consume(pair.Value * worst_input); } // produce outputs foreach (var pair in outputs) { resource_info res = resources.Info(v, pair.Key); res.Produce(pair.Value * worst_input); } }
static void ProcessFissionGenerator(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule fission_generator, resource_info ec, double elapsed_s) { // note: ignore heat double power = Lib.ReflectionValue <float>(fission_generator, "PowerGeneration"); var reactor = p.modules.Find(k => k.moduleName == "FissionReactor"); double tweakable = reactor == null ? 1.0 : Lib.ConfigValue(reactor.moduleValues, "CurrentPowerPercent", 100.0) * 0.01; ec.Produce(power * tweakable * elapsed_s); }
static void ProcessRadioisotopeGenerator(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule radioisotope_generator, resource_info ec, double elapsed_s) { // note: doesn't support easy mode double power = Lib.ReflectionValue <float>(radioisotope_generator, "BasePower"); double half_life = Lib.ReflectionValue <float>(radioisotope_generator, "HalfLife"); double mission_time = v.missionTime / (3600.0 * Lib.HoursInDay() * Lib.DaysInYear()); double remaining = Math.Pow(2.0, (-mission_time) / half_life); ec.Produce(power * remaining * elapsed_s); }
static void ProcessCurvedPanel(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule curved_panel, Part part_prefab, vessel_info info, resource_info ec, double elapsed_s) { // note: we assume deployed, this is a current limitation // if in sunlight if (info.sunlight > double.Epsilon) { // get values from module string transform_name = Lib.ReflectionValue <string>(curved_panel, "PanelTransformName"); float tot_rate = Lib.ReflectionValue <float>(curved_panel, "TotalEnergyRate"); // get components Transform[] components = part_prefab.FindModelTransforms(transform_name); if (components.Length == 0) { return; } // calculate normalized solar flux // note: this include fractional sunlight if integrated over orbit // note: this include atmospheric absorption if inside an atmosphere double norm_solar_flux = info.solar_flux / Sim.SolarFluxAtHome(); // calculate rate per component double rate = (double)tot_rate / (double)components.Length; // calculate world-space part rotation quaternion // note: a possible optimization here is to cache the transform lookup (unity was coded by monkeys) Quaternion rot = v.transform.rotation * p.rotation; // calculate output of all components double output = 0.0; foreach (Transform t in components) { output += rate // nominal rate per-component at 1 AU * norm_solar_flux // normalized solar flux at panel distance from sun * Math.Max(Vector3d.Dot(info.sun_dir, (rot * t.forward).normalized), 0.0); // cosine factor of component orientation } // produce EC ec.Produce(output * elapsed_s); } }
State venting() { // in flight if (Lib.IsFlight()) { // shortcuts resource_info vessel_atmo = ResourceCache.Info(vessel, "Atmosphere"); PartResource hab_atmo = part.Resources["Atmosphere"]; // get amount of atmosphere in part double hab_amount = hab_atmo.amount; // venting succeeded if the amount reached zero if (hab_amount <= double.Epsilon) { return(State.disabled); } // determine venting speed double amount = volume * equalize_speed * Kerbalism.elapsed_s; // consume from the part, clamp amount to what's available in the part amount = Math.Min(amount, hab_atmo.amount); hab_atmo.amount -= amount; // produce in all enabled habs in the vessel // (attempt recovery, but dump overboard if there is no capacity left) vessel_atmo.Produce(amount); // venting still in progress return(State.venting); } // in the editors else { // set amount to zero part.Resources["Atmosphere"].amount = 0.0; // return new state return(State.disabled); } }
static void ProcessFNGenerator(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule fission_generator, resource_info ec, double elapsed_s) { string maxPowerStr = Lib.Proto.GetString(m, "MaxPowerStr"); double maxPower = 0; if (maxPowerStr.Contains("GW")) { maxPower = double.Parse(maxPowerStr.Replace(" GW", "")) * 1000000; } else if (maxPowerStr.Contains("MW")) { maxPower = double.Parse(maxPowerStr.Replace(" MW", "")) * 1000; } else { maxPower = double.Parse(maxPowerStr.Replace(" KW", "")); } ec.Produce(maxPower * elapsed_s); }
static void ProcessPanel(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleDeployableSolarPanel panel, vessel_info info, resource_info ec, double elapsed_s) { // note: we ignore temperature curve, and make sure it is not relavant in the MM patch // note: we ignore power curve, that is used by no panel as far as I know // note: cylindrical and spherical panels are not supported // note: we assume the tracking target is SUN // if in sunlight and extended if (info.sunlight > double.Epsilon && m.moduleValues.GetValue("deployState") == "EXTENDED") { // get panel normal/pivot dir in world space Transform tr = panel.part.FindModelComponent <Transform>(panel.pivotName); Vector3d dir = panel.isTracking ? tr.up : tr.forward; dir = (v.transform.rotation * p.rotation * dir).normalized; // calculate cosine factor // - fixed panel: clamped cosine // - tracking panel, tracking pivot enabled: around the pivot // - tracking panel, tracking pivot disabled: assume perfect alignment double cosine_factor = !panel.isTracking ? Math.Max(Vector3d.Dot(info.sun_dir, dir), 0.0) : Settings.TrackingPivot ? Math.Cos(1.57079632679 - Math.Acos(Vector3d.Dot(info.sun_dir, dir))) : 1.0; // calculate normalized solar flux // - this include fractional sunlight if integrated over orbit // - this include atmospheric absorption if inside an atmosphere double norm_solar_flux = info.solar_flux / Sim.SolarFluxAtHome(); // calculate output double output = panel.resHandler.outputResources[0].rate // nominal panel charge rate at 1 AU * norm_solar_flux // normalized flux at panel distance from sun * cosine_factor; // cosine factor of panel orientation // produce EC ec.Produce(output * elapsed_s); } }
static void ProcessPanel(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleDeployableSolarPanel panel, vessel_info info, resource_info ec, double elapsed_s) { // note: we ignore temperature curve, and make sure it is not relavant in the MM patch // note: we ignore power curve, that is used by no panel as far as I know // play nice with BackgroundProcessing if (Kerbalism.detected_mods.BackgroundProcessing) { return; } // if in sunlight and extended if (info.sunlight > double.Epsilon && m.moduleValues.GetValue("stateString") == "EXTENDED") { // get panel normal direction Vector3d normal = panel.part.FindModelComponent <Transform>(panel.raycastTransformName).forward; // calculate cosine factor // note: for gameplay reasons, we ignore tracking panel pivots // note: a possible optimization here is to cache the transform lookup (unity was coded by monkeys) double cosine_factor = panel.sunTracking ? 1.0 : Math.Max(Vector3d.Dot(info.sun_dir, (v.transform.rotation * p.rotation * normal).normalized), 0.0); // calculate normalized solar flux // note: this include fractional sunlight if integrated over orbit // note: this include atmospheric absorption if inside an atmosphere double norm_solar_flux = info.solar_flux / Sim.SolarFluxAtHome(); // calculate output double output = panel.chargeRate // nominal panel charge rate at 1 AU * norm_solar_flux // normalized flux at panel distance from sun * cosine_factor // cosine factor of panel orientation * Reliability.Penalty(p, "Panel"); // malfunctioned panel penalty // produce EC ec.Produce(output * elapsed_s); } }
public void FixedUpdate() { // do nothing in editor if (Lib.IsEditor()) { return; } // do nothing if there isn't a solar panel if (panel == null) { return; } // get resource handler resource_info ec = ResourceCache.Info(vessel, "ElectricCharge"); // get vessel data from cache vessel_info info = Cache.VesselInfo(vessel); // do nothing if vessel is invalid if (!info.is_valid) { return; } // detect if sunlight is evaluated analytically bool analytical_sunlight = info.sunlight > 0.0 && info.sunlight < 1.0; // detect occlusion from other vessel parts // - we are only interested when the sunlight evaluation is discrete var collider = panel.hit.collider; bool locally_occluded = !analytical_sunlight && collider != null && info.sunlight > 0.0; // if panel is enabled and extended, and if sun is not occluded, not even locally if (panel.isEnabled && panel.deployState == ModuleDeployablePart.DeployState.EXTENDED && info.sunlight > 0.0 && !locally_occluded) { // calculate cosine factor // - the stock module is already computing the tracking direction double cosine_factor = Math.Max(Vector3d.Dot(info.sun_dir, panel.trackingDotTransform.forward), 0.0); // calculate normalized solar flux // - this include fractional sunlight if integrated over orbit // - this include atmospheric absorption if inside an atmosphere double norm_solar_flux = info.solar_flux / Sim.SolarFluxAtHome(); // calculate output double output = rate // nominal panel charge rate at 1 AU * norm_solar_flux // normalized flux at panel distance from sun * cosine_factor; // cosine factor of panel orientation // produce EC ec.Produce(output * Kerbalism.elapsed_s); // update ui field_visibility = info.sunlight * 100.0; field_atmosphere = info.atmo_factor * 100.0; field_exposure = cosine_factor * 100.0; field_output = output; Fields["field_visibility"].guiActive = analytical_sunlight; Fields["field_atmosphere"].guiActive = info.atmo_factor < 1.0; Fields["field_exposure"].guiActive = true; Fields["field_output"].guiActive = true; } // if panel is disabled, retracted, or in shadow else { // hide ui Fields["field_visibility"].guiActive = false; Fields["field_atmosphere"].guiActive = false; Fields["field_exposure"].guiActive = false; Fields["field_output"].guiActive = false; } // update status ui field_status = analytical_sunlight ? "<color=#ffff22>Integrated over the orbit</color>" : locally_occluded ? "<color=#ff2222>Occluded by vessel</color>" : info.sunlight < 1.0 ? "<color=#ff2222>Occluded by celestial body</color>" : string.Empty; Fields["field_status"].guiActive = field_status.Length > 0; }
State equalize() { // in flight if (Lib.IsFlight()) { // shortcuts resource_info vessel_atmo = ResourceCache.Info(vessel, "Atmosphere"); PartResource hab_atmo = part.Resources["Atmosphere"]; // get level of atmosphere in vessel and part double vessel_level = vessel_atmo.level; double hab_level = Lib.Level(part, "Atmosphere", true); // equalization succeeded if the levels are the same // note: this behave correctly in the case the hab is the only enabled one or not if (Math.Abs(vessel_level - hab_level) < 0.01) { return(State.enabled); } // in case vessel pressure is dropping during equalization, it mean that pressure // control is not enough so we just enable the hab while not fully equalized if (vessel_atmo.rate < 0.0) { return(State.enabled); } // determine equalization speed // we deal with the case where a big hab is sucking all atmosphere from the rest of the vessel double amount = Math.Min(Cache.VesselInfo(vessel).volume, volume) * equalize_speed * Kerbalism.elapsed_s; // vessel pressure is higher if (vessel_level > hab_level) { // clamp amount to what's available in the vessel and what can fit in the part amount = Math.Min(amount, vessel_atmo.amount); amount = Math.Min(amount, hab_atmo.maxAmount - hab_atmo.amount); // consume from all enabled habs in the vessel vessel_atmo.Consume(amount); // produce in the part hab_atmo.amount += amount; } // vessel pressure is lower else { // consume from the part, clamp amount to what's available in the part amount = Math.Min(amount, hab_atmo.amount); hab_atmo.amount -= amount; // produce in all enabled habs in the vessel // (attempt recovery, but dump overboard if there is no capacity left) vessel_atmo.Produce(amount); } // equalization still in progress return(State.equalizing); } // in the editors else { // set amount to max capacity PartResource hab_atmo = part.Resources["Atmosphere"]; hab_atmo.amount = hab_atmo.maxAmount; // return new state return(State.enabled); } }