/// <summary>process the process and add/remove the resources from the simulator for the entire vessel at once</summary> private void Process_process_vessel_wide(Process pr, EnvironmentAnalyzer env, VesselAnalyzer va) { // evaluate modifiers double k = Modifiers.Evaluate(env, va, this, pr.modifiers); Process_process_inner_body(k, null, pr, env, va); }
/// <summary>determine if the resources involved are restricted to a part, and then process a rule</summary> public void Process_rule(List <Part> parts, Rule r, EnvironmentAnalyzer env, VesselAnalyzer va) { // evaluate modifiers double k = Modifiers.Evaluate(env, va, this, r.modifiers); Process_rule_inner_body(k, null, r, env, va); }
/// <summary>process a rule and add/remove the resources from the simulator</simulator> private void Process_rule_inner_body(double k, Part p, Rule r, EnvironmentAnalyzer env, VesselAnalyzer va) { // deduce rate per-second double rate = va.crew_count * (r.interval > 0.0 ? r.rate / r.interval : r.rate); // prepare recipe if (r.output.Length == 0) { Resource(r.input).Consume(rate * k, r.name); } else if (rate > double.Epsilon) { // simulate recipe if output_only is false if (!r.monitor) { // - rules always dump excess overboard (because it is waste) SimulatedRecipe recipe = new SimulatedRecipe(p, r.name); recipe.Input(r.input, rate * k); recipe.Output(r.output, rate * k * r.ratio, true); recipes.Add(recipe); } // only simulate output else { Resource(r.output).Produce(rate * k, r.name); } } }
void Process_solarPanel(SolarPanelFixer spf, EnvironmentAnalyzer env) { if (spf.part.editorStarted && spf.isInitialized && spf.isEnabled && spf.editorEnabled) { double editorOutput = 0.0; switch (Planner.Sunlight) { case Planner.SunlightState.SunlightNominal: editorOutput = spf.nominalRate * (env.solar_flux / Sim.SolarFluxAtHome); if (editorOutput > 0.0) { Resource("ElectricCharge").Produce(editorOutput, "solar panel (nominal)"); } break; case Planner.SunlightState.SunlightSimulated: // create a sun direction according to the shadows direction in the VAB / SPH Vector3d sunDir = EditorDriver.editorFacility == EditorFacility.VAB ? new Vector3d(1.0, 1.0, 0.0).normalized : new Vector3d(0.0, 1.0, -1.0).normalized; string occludingPart = null; double effiencyFactor = spf.SolarPanel.GetCosineFactor(sunDir, true) * spf.SolarPanel.GetOccludedFactor(sunDir, out occludingPart, true); double distanceFactor = env.solar_flux / Sim.SolarFluxAtHome; editorOutput = spf.nominalRate * effiencyFactor * distanceFactor; if (editorOutput > 0.0) { Resource("ElectricCharge").Produce(editorOutput, "solar panel (estimated)"); } break; } } }
private void Process_apiModule(PartModule m, EnvironmentAnalyzer env, VesselAnalyzer va) { List<KeyValuePair<string, double>> resourcesList = new List<KeyValuePair<string, double>>(); Dictionary<string, double> environment = new Dictionary<string, double>(); environment["altitude"] = env.altitude; environment["orbital_period"] = env.orbital_period; environment["shadow_period"] = env.shadow_period; environment["shadow_time"] = env.shadow_time; environment["albedo_flux"] = env.albedo_flux; environment["solar_flux"] = env.solar_flux; environment["sun_dist"] = env.sun_dist; environment["temperature"] = env.temperature; environment["total_flux"] = env.total_flux; environment["temperature"] = env.temperature; environment["sunlight"] = Planner.Sunlight == Planner.SunlightState.Shadow ? 0 : 1; Lib.Log("resource count before call " + resourcesList.Count); string title = apiDelegates[m.moduleName].Invoke(m, resourcesList, env.body, environment); Lib.Log("resource count after call " + resourcesList.Count); foreach (var p in resourcesList) { var res = Resource(p.Key); if (p.Value >= 0) res.Produce(p.Value, title); else res.Consume(-p.Value, title); } }
void Analyze_qol(List <Part> parts, ResourceSimulator sim, EnvironmentAnalyzer env) { // calculate living space factor living_space = Lib.Clamp((volume / Math.Max(crew_count, 1u)) / PreferencesComfort.Instance.livingSpace, 0.1, 1.0); // calculate comfort factor comforts = new Comforts(parts, env.landed, crew_count > 1, has_comms); }
void Process_curved_panel(Part p, PartModule m, EnvironmentAnalyzer env) { // note: assume half the components are in sunlight, and average inclination is half // get total rate double tot_rate = Lib.ReflectionValue <float>(m, "TotalEnergyRate"); // get number of components int components = p.FindModelTransforms(Lib.ReflectionValue <string>(m, "PanelTransformName")).Length; // approximate output // 0.7071: average clamped cosine Resource("ElectricCharge").Produce(tot_rate * 0.7071 * env.solar_flux / Sim.SolarFluxAtHome(), "curved panel"); }
/// <summary>process the process and add/remove the resources from the simulator</summary> private void Process_process_inner_body(double k, Part p, Process pr, EnvironmentAnalyzer env, VesselAnalyzer va) { // prepare recipe SimulatedRecipe recipe = new SimulatedRecipe(p, pr.name); foreach (KeyValuePair<string, double> input in pr.inputs) { recipe.Input(input.Key, input.Value * k); } foreach (KeyValuePair<string, double> output in pr.outputs) { recipe.Output(output.Key, output.Value * k, pr.dump.Check(output.Key)); } recipes.Add(recipe); }
public void Analyze(List <Part> parts, ResourceSimulator sim, EnvironmentAnalyzer env) { // note: vessel analysis require resource analysis, but at the same time resource analysis // require vessel analysis, so we are using resource analysis from previous frame (that's okay) // in the past, it was the other way around - however that triggered a corner case when va.comforts // was null (because the vessel analysis was still never done) and some specific rule/process // in resource analysis triggered an exception, leading to the vessel analysis never happening // inverting their order avoided this corner-case Analyze_crew(parts); Analyze_habitat(parts, sim, env); Analyze_radiation(parts, sim); Analyze_reliability(parts); Analyze_qol(parts, sim, env); Analyze_comms(parts); }
void Analyze_habitat(List <Part> parts, ResourceSimulator sim, EnvironmentAnalyzer env) { // calculate total volume volume = sim.Resource("Atmosphere").capacity / 1e3; // calculate total surface surface = sim.Resource("Shielding").capacity; // determine if the vessel has pressure control capabilities pressurized = sim.Resource("Atmosphere").produced > 0.0 || env.breathable; // determine number of EVA's using available Nitrogen evas = (uint)(Math.Max(0, sim.Resource("Nitrogen").amount - 330) / PreferencesLifeSupport.Instance.evaAtmoLoss); // determine if the vessel has scrubbing capabilities scrubbed = sim.Resource("WasteAtmosphere").consumed > 0.0 || env.breathable; // determine if the vessel has humidity control capabilities humid = sim.Resource("MoistAtmosphere").consumed > 0.0 || env.breathable; // scan the parts double max_pressure = 1.0; foreach (Part p in parts) { // for each module foreach (PartModule m in p.Modules) { // skip disabled modules if (!m.isEnabled) { continue; } if (m.moduleName == "Habitat") { Habitat h = m as Habitat; max_pressure = Math.Min(max_pressure, h.max_pressure); } } } pressurized &= max_pressure >= Settings.PressureThreshold; }
/// <summary> /// run simulator to get statistics a fraction of a second after the vessel would spawn /// in the configured environment (celestial body, orbit height and presence of sunlight) /// </summary> public void Analyze(List<Part> parts, EnvironmentAnalyzer env, VesselAnalyzer va) { // reach steady state, so all initial resources like WasteAtmosphere are produced // it is assumed that one cycle is needed to produce things that don't need inputs // another cycle is needed for processes to pick that up // another cycle may be needed for results of those processes to be picked up // two additional cycles are for having some margin for (int i = 0; i < 5; i++) { RunSimulator(parts, env, va); } // Do the actual run people will see from the simulator UI foreach (SimulatedResource r in resources.Values) { r.ResetSimulatorDisplayValues(); } RunSimulator(parts, env, va); }
void Analyze_habitat(ResourceSimulator sim, EnvironmentAnalyzer env) { // calculate total volume volume = sim.Resource("Atmosphere").capacity / 1e3; // calculate total surface surface = sim.Resource("Shielding").capacity; // determine if the vessel has pressure control capabilities pressurized = sim.Resource("Atmosphere").produced > 0.0 || env.breathable; // determine number of EVA's using available Nitrogen evas = (uint)(Math.Max(0, sim.Resource("Nitrogen").amount - 330) / PreferencesLifeSupport.Instance.evaAtmoLoss); // determine if the vessel has scrubbing capabilities scrubbed = sim.Resource("WasteAtmosphere").consumed > 0.0 || env.breathable; // determine if the vessel has humidity control capabilities humid = sim.Resource("MoistAtmosphere").consumed > 0.0 || env.breathable; }
void Analyze_habitat(List <Part> parts, ResourceSimulator sim, EnvironmentAnalyzer env) { // calculate total volume volume = sim.Resource("Atmosphere").capacity / 1e3; // calculate total surface surface = sim.Resource("Shielding").capacity; // determine if the vessel has pressure control capabilities pressurized = sim.Resource("Atmosphere").produced > 0.0 || env.breathable; // determine if the vessel has scrubbing capabilities scrubbed = sim.Resource("WasteAtmosphere").consumed > 0.0 || env.breathable; // scan the parts double max_pressure = 1.0; foreach (Part p in parts) { // for each module foreach (PartModule m in p.Modules) { // skip disabled modules if (!m.isEnabled) { continue; } if (m.moduleName == "Habitat") { Habitat h = m as Habitat; max_pressure = Math.Min(max_pressure, h.max_pressure); } } } pressurized &= max_pressure >= Settings.PressureThreshold; }
void Process_panel(ModuleDeployableSolarPanel panel, EnvironmentAnalyzer env) { double generated = panel.resHandler.outputResources[0].rate * env.solar_flux / Sim.SolarFluxAtHome(); Resource("ElectricCharge").Produce(generated, "solar panel"); }
/// <summary>run a single timestamp of the simulator</summary> private void RunSimulator(List<Part> parts, EnvironmentAnalyzer env, VesselAnalyzer va) { // clear previous resource state resources.Clear(); // get amount and capacity from parts foreach (Part p in parts) { for (int i = 0; i < p.Resources.Count; ++i) { Process_part(p, p.Resources[i].resourceName); #if DEBUG_RESOURCES p.Resources[i].isVisible = true; p.Resources[i].isTweakable = true; #endif } } // process all rules foreach (Rule r in Profile.rules) { if (r.input.Length > 0 && r.rate > 0.0) { Process_rule(parts, r, env, va); } } // process all processes foreach (Process p in Profile.processes) { Process_process(parts, p, env, va); } // process all modules foreach (Part p in parts) { // get planner controller in the part PlannerController ctrl = p.FindModuleImplementing<PlannerController>(); // ignore all modules in the part if specified in controller if (ctrl != null && !ctrl.considered) continue; // for each module foreach (PartModule m in p.Modules) { // skip disabled modules // rationale: the Selector disable non-selected modules in this way if (!m.isEnabled) continue; if (IsApiModule(m)) { Process_apiModule(m, env, va); } else { switch (m.moduleName) { case "Greenhouse": Process_greenhouse(m as Greenhouse, env, va); break; case "GravityRing": Process_ring(m as GravityRing); break; case "Emitter": Process_emitter(m as Emitter); break; case "Laboratory": Process_laboratory(m as Laboratory); break; case "Experiment": Process_experiment(m as Experiment); break; case "ModuleCommand": Process_command(m as ModuleCommand); break; case "ModuleGenerator": Process_generator(m as ModuleGenerator, p); break; case "ModuleResourceConverter": Process_converter(m as ModuleResourceConverter, va); break; case "ModuleKPBSConverter": Process_converter(m as ModuleResourceConverter, va); break; case "ModuleResourceHarvester": Process_harvester(m as ModuleResourceHarvester, va); break; case "ModuleScienceConverter": Process_stocklab(m as ModuleScienceConverter); break; case "ModuleActiveRadiator": Process_radiator(m as ModuleActiveRadiator); break; case "ModuleWheelMotor": Process_wheel_motor(m as ModuleWheelMotor); break; case "ModuleWheelMotorSteering": Process_wheel_steering(m as ModuleWheelMotorSteering); break; case "ModuleLight": case "ModuleColoredLensLight": case "ModuleMultiPointSurfaceLight": Process_light(m as ModuleLight); Process_light(m as ModuleLight); Process_light(m as ModuleLight); break; case "KerbalismScansat": Process_scanner(m as KerbalismScansat); break; case "FissionGenerator": Process_fission_generator(p, m); break; case "ModuleRadioisotopeGenerator": Process_radioisotope_generator(p, m); break; case "ModuleCryoTank": Process_cryotank(p, m); break; case "ModuleRTAntennaPassive": case "ModuleRTAntenna": Process_rtantenna(m); break; case "ModuleDataTransmitter": Process_datatransmitter(m as ModuleDataTransmitter); break; case "ModuleEngines": Process_engines(m as ModuleEngines); break; case "ModuleEnginesFX": Process_enginesfx(m as ModuleEnginesFX); break; case "ModuleRCS": Process_rcs(m as ModuleRCS); break; case "ModuleRCSFX": Process_rcsfx(m as ModuleRCSFX); break; case "SolarPanelFixer": Process_solarPanel(m as SolarPanelFixer, env); break; } } } } // execute all possible recipes bool executing = true; while (executing) { executing = false; for (int i = 0; i < recipes.Count; ++i) { SimulatedRecipe recipe = recipes[i]; if (recipe.left > double.Epsilon) { executing |= recipe.Execute(this); } } } recipes.Clear(); // clamp all resources foreach (KeyValuePair<string, SimulatedResource> pair in resources) pair.Value.Clamp(); }
void Process_greenhouse(Greenhouse g, EnvironmentAnalyzer env, VesselAnalyzer va) { // skip disabled greenhouses if (!g.active) return; // shortcut to resources SimulatedResource ec = Resource("ElectricCharge"); SimulatedResource res = Resource(g.crop_resource); // calculate natural and artificial lighting double natural = env.solar_flux; double artificial = Math.Max(g.light_tolerance - natural, 0.0); // if lamps are on and artificial lighting is required if (artificial > 0.0) { // consume ec for the lamps ec.Consume(g.ec_rate * (artificial / g.light_tolerance), "greenhouse"); } // execute recipe SimulatedRecipe recipe = new SimulatedRecipe(g.part, "greenhouse"); foreach (ModuleResource input in g.resHandler.inputResources) { // WasteAtmosphere is primary combined input if (g.WACO2 && input.name == "WasteAtmosphere") recipe.Input(input.name, env.breathable ? 0.0 : input.rate, "CarbonDioxide"); // CarbonDioxide is secondary combined input else if (g.WACO2 && input.name == "CarbonDioxide") recipe.Input(input.name, env.breathable ? 0.0 : input.rate, ""); // if atmosphere is breathable disable WasteAtmosphere / CO2 else if (!g.WACO2 && (input.name == "CarbonDioxide" || input.name == "WasteAtmosphere")) recipe.Input(input.name, env.breathable ? 0.0 : input.rate, ""); else recipe.Input(input.name, input.rate); } foreach (ModuleResource output in g.resHandler.outputResources) { // if atmosphere is breathable disable Oxygen if (output.name == "Oxygen") recipe.Output(output.name, env.breathable ? 0.0 : output.rate, true); else recipe.Output(output.name, output.rate, true); } recipes.Add(recipe); // determine environment conditions bool lighting = natural + artificial >= g.light_tolerance; bool pressure = va.pressurized || g.pressure_tolerance <= double.Epsilon; bool radiation = (env.landed ? env.surface_rad : env.magnetopause_rad) * (1.0 - va.shielding) < g.radiation_tolerance; // if all conditions apply // note: we are assuming the inputs are satisfied, we can't really do otherwise here if (lighting && pressure && radiation) { // produce food res.Produce(g.crop_size * g.crop_rate, "greenhouse"); // add harvest info res.harvests.Add(Lib.BuildString(g.crop_size.ToString("F0"), " in ", Lib.HumanReadableDuration(1.0 / g.crop_rate))); } }
/// <summary> /// determine if the resources involved are restricted to a part, and then process /// the process and add/remove the resources from the simulator /// </summary> /// <remarks>while rules are usually input or output only, processes transform input to output</remarks> public void Process_process(List<Part> parts, Process pr, EnvironmentAnalyzer env, VesselAnalyzer va) { Process_process_vessel_wide(pr, env, va); }