// return read-only list of scrubbers in a vessel public static List<Scrubber> GetScrubbers(Vessel v) { if (v.loaded) return v.FindPartModulesImplementing<Scrubber>(); else { List<Scrubber> ret = new List<Scrubber>(); foreach(ProtoPartSnapshot part in v.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot module in part.modules) { if (module.moduleName == "Scrubber") { Scrubber scrubber = new Scrubber(); scrubber.is_enabled = Lib.GetProtoValue<bool>(module, "is_enabled"); scrubber.ec_rate = Lib.GetProtoValue<double>(module, "ec_rate"); scrubber.co2_rate = Lib.GetProtoValue<double>(module, "co2_rate"); scrubber.efficiency = Lib.GetProtoValue<double>(module, "efficiency"); ret.Add(scrubber); } } } return ret; } }
public static void update(Vessel v, vessel_info vi, vessel_data vd, vessel_resources resources, double elapsed_s) { // get most used resource handlers resource_info ec = resources.Info(v, "ElectricCharge"); // for each part foreach (ProtoPartSnapshot p in v.protoVessel.protoPartSnapshots) { // a part can contain multiple resource converters int converter_index = 0; // get part prefab (required for module properties) Part part_prefab = PartLoader.getPartInfoByName(p.partName).partPrefab; // for each module foreach (ProtoPartModuleSnapshot m in p.modules) { // get the module prefab PartModule module_prefab = Lib.FindModule(part_prefab, m.moduleName); // if the prefab doesn't contain this module, skip it if (!module_prefab) { continue; } // process modules switch (m.moduleName) { case "Reliability": Reliability.BackgroundUpdate(v, m, module_prefab as Reliability, elapsed_s); break; case "Scrubber": Scrubber.BackgroundUpdate(v, m, module_prefab as Scrubber, vi, resources, elapsed_s); break; case "Recycler": Recycler.BackgroundUpdate(v, m, module_prefab as Recycler, resources, elapsed_s); break; case "Greenhouse": Greenhouse.BackgroundUpdate(v, p, m, module_prefab as Greenhouse, vi, resources, elapsed_s); break; case "GravityRing": GravityRing.BackgroundUpdate(v, p, m, module_prefab as GravityRing, resources, elapsed_s); break; case "Emitter": Emitter.BackgroundUpdate(v, p, m, module_prefab as Emitter, ec, elapsed_s); break; case "ModuleCommand": ProcessCommand(v, p, m, module_prefab as ModuleCommand, resources, elapsed_s); break; case "ModuleDeployableSolarPanel": ProcessPanel(v, p, m, module_prefab as ModuleDeployableSolarPanel, vi, ec, elapsed_s); break; case "ModuleGenerator": ProcessGenerator(v, p, m, module_prefab as ModuleGenerator, resources, elapsed_s); break; case "ModuleResourceConverter": case "ModuleKPBSConverter": case "FissionReactor": ProcessConverter(v, p, m, part_prefab, converter_index++, resources, elapsed_s); break; case "ModuleResourceHarvester": ProcessHarvester(v, p, m, module_prefab as ModuleResourceHarvester, resources, elapsed_s); break; case "ModuleAsteroidDrill": ProcessAsteroidDrill(v, p, m, module_prefab as ModuleAsteroidDrill, resources, elapsed_s); break; case "ModuleScienceConverter": ProcessLab(v, p, m, module_prefab as ModuleScienceConverter, ec, elapsed_s); break; case "ModuleLight": case "ModuleColoredLensLight": case "ModuleMultiPointSurfaceLight": ProcessLight(v, p, m, module_prefab as ModuleLight, ec, elapsed_s); break; case "SCANsat": case "ModuleSCANresourceScanner": ProcessScanner(v, p, m, module_prefab, part_prefab, vd, ec, elapsed_s); break; case "ModuleCurvedSolarPanel": ProcessCurvedPanel(v, p, m, module_prefab, part_prefab, vi, ec, elapsed_s); break; case "FissionGenerator": ProcessFissionGenerator(v, p, m, module_prefab, ec, elapsed_s); break; case "ModuleRadioisotopeGenerator": ProcessRadioisotopeGenerator(v, p, m, module_prefab, ec, elapsed_s); break; case "ModuleCryoTank": ProcessCryoTank(v, p, m, module_prefab, resources, elapsed_s); break; } } } }
// draw a vessel in the monitor // - return: 1 if vessel wasn't skipped uint render_vessel(Vessel v) { // avoid case when DB isn't ready for whatever reason if (!DB.Ready()) return 0; // skip invalid vessels if (!Lib.IsVessel(v)) return 0; // skip resque missions if (Lib.IsResqueMission(v)) return 0; // skip dead eva kerbals if (EVA.IsDead(v)) return 0; // get vessel info from cache vessel_info vi = Cache.VesselInfo(v); // get vessel data from the db vessel_data vd = DB.VesselData(v.id); // skip filtered vessels if (filtered() && vd.group != filter) return 0; // get vessel crew List<ProtoCrewMember> crew = v.loaded ? v.GetVesselCrew() : v.protoVessel.GetVesselCrew(); // get vessel name string vessel_name = v.isEVA ? crew[0].name : v.vesselName; // get body name string body_name = v.mainBody.name.ToUpper(); // get list of scrubbers List<Scrubber> scrubbers = Scrubber.GetScrubbers(v); // get list of greenhouses List<Greenhouse> greenhouses = Greenhouse.GetGreenhouses(v); // store problems icons & tooltips List<Texture> problem_icons = new List<Texture>(); List<string> problem_tooltips = new List<string>(); // detect problems problem_sunlight(vi, ref problem_icons, ref problem_tooltips); problem_storm(v, ref problem_icons, ref problem_tooltips); if (crew.Count > 0) { problem_kerbals(crew, ref problem_icons, ref problem_tooltips); problem_radiation(vi, ref problem_icons, ref problem_tooltips); problem_scrubbers(v, scrubbers, ref problem_icons, ref problem_tooltips); } problem_greenhouses(v, greenhouses, ref problem_icons, ref problem_tooltips); // choose problem icon const UInt64 problem_icon_time = 3; Texture problem_icon = icon_empty; if (problem_icons.Count > 0) { UInt64 problem_index = (Convert.ToUInt64(Time.realtimeSinceStartup) / problem_icon_time) % (UInt64)(problem_icons.Count); problem_icon = problem_icons[(int)problem_index]; } // generate problem tooltips string problem_tooltip = String.Join("\n", problem_tooltips.ToArray()); // render vessel name & icons GUILayout.BeginHorizontal(row_style); GUILayout.Label(new GUIContent("<b>" + Lib.Epsilon(vessel_name, 20) + "</b>", vessel_name.Length > 20 ? vessel_name : ""), name_style); GUILayout.Label(new GUIContent(Lib.Epsilon(body_name, 8), body_name.Length > 8 ? body_name : ""), body_style); GUILayout.Label(new GUIContent(problem_icon, problem_tooltip), icon_style); GUILayout.Label(indicator_ec(v), icon_style); GUILayout.Label(indicator_supplies(v, scrubbers, greenhouses), icon_style); GUILayout.Label(indicator_reliability(v), icon_style); GUILayout.Label(indicator_signal(v), icon_style); GUILayout.EndHorizontal(); // remember last vessel clicked if (Lib.IsClicked()) last_clicked_id = v.id; // render vessel config if (configured_id == v.id) render_config(v); // spacing between vessels GUILayout.Space(10.0f); // signal that the vessel wasn't skipped for whatever reason return 1; }
public ScrubberDevice(Scrubber scrubber) { this.scrubber = scrubber; }
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); }
// 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); } } } } }