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); } }
/// <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); }
void Process_harvester(Harvester harvester, VesselAnalyzer va) { if (harvester.running && harvester.simulated_abundance > harvester.min_abundance) { SimulatedRecipe recipe = new SimulatedRecipe(harvester.part, "harvester"); if (harvester.ec_rate > double.Epsilon) { recipe.Input("ElectricCharge", harvester.ec_rate); } recipe.Output( harvester.resource, Harvester.AdjustedRate(harvester, new CrewSpecs("Engineer@0"), va.crew, harvester.simulated_abundance), dump: false); recipes.Add(recipe); } }
/// <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 Process_harvester(ModuleResourceHarvester harvester, VesselAnalyzer va) { // calculate experience bonus float exp_bonus = harvester.UseSpecialistBonus ? harvester.EfficiencyBonus * (harvester.SpecialistBonusBase + (harvester.SpecialistEfficiencyFactor * (va.crew_engineer_maxlevel + 1))) : 1.0f; // use part name as recipe name // - include crew bonus in the recipe name string recipe_name = Lib.BuildString(harvester.part.partInfo.title, " (efficiency: ", Lib.HumanReadablePerc(exp_bonus), ")"); // generate recipe SimulatedRecipe recipe = new SimulatedRecipe(harvester.part, recipe_name); foreach (ResourceRatio res in harvester.inputList) { recipe.Input(res.ResourceName, res.Ratio); } recipe.Output(harvester.ResourceName, harvester.Efficiency * exp_bonus, true); recipes.Add(recipe); }
/// <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); }
/// <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>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); }
/// <summary>process a rule and add/remove the resources from the simulator</summary> 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) { // - 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); } }