// calculate a cache entry for the vessel static vessel_info Compute(Vessel v) { vessel_info info = new vessel_info(); info.position = Lib.VesselPosition(v); info.sunlight = Sim.RaytraceBody(v, Sim.Sun(), out info.sun_dir, out info.sun_dist); info.temperature = Sim.Temperature(v, info.sunlight); info.cosmic_radiation = Radiation.CosmicRadiation(v); info.belt_radiation = Radiation.BeltRadiation(v); info.storm_radiation = Radiation.StormRadiation(v, info.sunlight); info.env_radiation = info.cosmic_radiation + info.belt_radiation + info.storm_radiation; info.breathable = Sim.Breathable(v); foreach(var p in Kerbalism.rules) { Rule r = p.Value; if (r.resource_name.Length > 0) { var vmon = new vmon_cache(); vmon.depletion = r.EstimateLifetime(v); double amount = Lib.GetResourceAmount(v, r.resource_name); double capacity = Lib.GetResourceCapacity(v, r.resource_name); vmon.level = capacity > double.Epsilon ? amount / capacity : 1.0; //< level is 1 with no capacity info.vmon.Add(p.Value.name, vmon); } } return info; }
[KSPField(isPersistant = true)] public bool is_dead = false; // indicate if eva kerbal is dead public void FixedUpdate() { // get KerbalEVA module KerbalEVA kerbal = part.FindModuleImplementing<KerbalEVA>(); // show/hide helmet SetHelmet(kerbal, has_helmet); // consume EC for the headlamp if (has_helmet && kerbal.lampOn) part.RequestResource("ElectricCharge", Settings.HeadlightCost * TimeWarp.fixedDeltaTime); // determine if it has EC left bool ec_left = Lib.GetResourceAmount(part, "ElectricCharge") > double.Epsilon; // force the headlamp lights on/off depending on ec amount left and if it has an helmet SetHeadlamp(kerbal, has_helmet && kerbal.lampOn && ec_left); // synchronize helmet flares with headlamp state SetFlares(kerbal, has_helmet && kerbal.lampOn && ec_left); // if dead if (is_dead) { // enforce freezed state SetFreezed(kerbal); // remove plant flag action kerbal.flagItems = 0; // remove experiment actions (game engine keeps readding them) RemoveExperiments(kerbal); } }
public static radiation_data analyze_radiation(List<Part> parts, environment_data env, crew_data crew) { // store data radiation_data radiation = new radiation_data(); // scan the parts foreach(Part p in parts) { // accumulate shielding amount and capacity radiation.shielding_amount += Lib.GetResourceAmount(p, "Shielding"); radiation.shielding_capacity += Lib.GetResourceCapacity(p, "Shielding"); } // calculate radiation data double shielding = Radiation.Shielding(radiation.shielding_amount, radiation.shielding_capacity); double belt_strength = Settings.BeltRadiation * Radiation.Dynamo(env.body) * 0.5; //< account for the 'ramp' if (crew.capacity > 0) { radiation.life_expectancy = new double[] { Settings.RadiationFatalThreshold / (Settings.CosmicRadiation * (1.0 - shielding)), Settings.RadiationFatalThreshold / (Settings.StormRadiation * (1.0 - shielding)), Radiation.HasBelt(env.body) ? Settings.RadiationFatalThreshold / (belt_strength * (1.0 - shielding)) : double.NaN }; } else { radiation.life_expectancy = new double[]{double.NaN, double.NaN, double.NaN}; } // return data return radiation; }
GUIContent indicator_ec(Vessel v) { double amount = Lib.GetResourceAmount(v, "ElectricCharge"); double capacity = Lib.GetResourceCapacity(v, "ElectricCharge"); double level = capacity > 0.0 ? amount / capacity : 1.0; GUIContent state = new GUIContent(); state.tooltip = capacity > 0.0 ? "EC: " + (level * 100.0).ToString("F0") + "%" : ""; state.image = icon_battery_nominal; double low_threshold = 0.15; foreach(var p in Kerbalism.rules) { Rule r = p.Value; if (r.resource_name == "ElectricCharge") { low_threshold = r.low_threshold; break; } } if (level <= double.Epsilon) state.image = icon_battery_danger; else if (level <= low_threshold) state.image = icon_battery_warning; return state; }
// fill antennas data void BuildAntennas() { // get error-correcting code factor double ecc = ECC(); // forget previous antennas antennas.Clear(); // for each vessel foreach(Vessel v in FlightGlobals.Vessels) { // skip invalid vessels if (!Lib.IsVessel(v)) continue; // store best antenna values double best_range = 0.0; double best_relay_range = 0.0; double best_relay_cost = 0.0; // get ec available double ec_amount = Lib.GetResourceAmount(v, "ElectricCharge"); // if the vessel is loaded if (v.loaded) { // choose the best antenna foreach(Antenna a in v.FindPartModulesImplementing<Antenna>()) { double range = Range(a.scope, a.penalty, ecc); best_range = Math.Max(best_range, range); if (a.relay && range > best_relay_range && ec_amount >= a.relay_cost * TimeWarp.deltaTime) { best_relay_range = range; best_relay_cost = a.relay_cost; } } } // if the vessel isn't loaded else { // choose the best antenna foreach(ProtoPartSnapshot p in v.protoVessel.protoPartSnapshots) { foreach(ProtoPartModuleSnapshot m in p.modules) { if (m.moduleName == "Antenna") { double range = Range(m.moduleValues.GetValue("scope"), Malfunction.Penalty(p), ecc); double relay_cost = Lib.GetProtoValue<double>(m, "relay_cost"); bool relay = Lib.GetProtoValue<bool>(m, "relay"); best_range = Math.Max(best_range, range); if (relay && range > best_relay_range && ec_amount >= relay_cost * TimeWarp.deltaTime) { best_relay_range = range; best_relay_cost = relay_cost; } } } } } // add antenna data antennas.Add(v.id, new antenna_data(Lib.VesselPosition(v), best_range, best_relay_range, best_relay_cost)); } }
public static double Shielding(ConnectedLivingSpace.ICLSSpace space) { double amount = 0.0; double capacity = 0.0; foreach (var part in space.Parts) { amount += Lib.GetResourceAmount(part.Part, "Shielding"); capacity += Lib.GetResourceCapacity(part.Part, "Shielding"); } return(Shielding(amount, capacity)); }
void problem_recyclers(Vessel v, List<Scrubber> scrubbers, List<Recycler> recyclers, ref List<Texture> icons, ref List<string> tooltips) { bool no_ec_left = Lib.GetResourceAmount(v, "ElectricCharge") <= double.Epsilon; HashSet<string> disabled_recyclers = new HashSet<string>(); HashSet<string> no_power_recyclers = new HashSet<string>(); HashSet<string> no_filter_recyclers = new HashSet<string>(); // analyze scrubbers foreach(Scrubber scrubber in scrubbers) { if (!scrubber.is_enabled) disabled_recyclers.Add("Scrubber"); else if (no_ec_left) no_power_recyclers.Add("Scrubber"); } // analyze recyclers foreach(Recycler recycler in recyclers) { if (!recycler.is_enabled) disabled_recyclers.Add(recycler.display_name); else if (no_ec_left) no_power_recyclers.Add(recycler.display_name); else { if (recycler.filter_name.Length > 0 && recycler.filter_rate > 0.0) { if (Lib.GetResourceAmount(v, recycler.filter_name) <= double.Epsilon) { no_filter_recyclers.Add(recycler.display_name); } } } } if (disabled_recyclers.Count > 0) { icons.Add(icon_scrubber_warning); foreach(string s in disabled_recyclers) tooltips.Add(s + " disabled"); } if (no_power_recyclers.Count > 0) { icons.Add(icon_scrubber_danger); foreach(string s in no_power_recyclers) tooltips.Add(s + " has no power"); } if (no_filter_recyclers.Count > 0) { icons.Add(icon_scrubber_danger); foreach(string s in no_filter_recyclers) tooltips.Add(s + " has no filter left"); } }
public static oxygen_data analyze_oxygen(List<Part> parts, environment_data env, crew_data crew) { // store data oxygen_data oxygen = new oxygen_data(); // get scrubber efficiency oxygen.scrubber_efficiency = Scrubber.DeduceEfficiency(); // calculate oxygen consumed oxygen.consumed = !env.breathable ? (double)crew.count * Settings.OxygenPerSecond : 0.0; // deduce co2 produced by the crew per-second double simulated_co2 = oxygen.consumed; // scan the parts foreach(Part p in parts) { // accumulate food storage oxygen.storage += Lib.GetResourceAmount(p, "Oxygen"); // for each module foreach(PartModule m in p.Modules) { // scrubber if (m.moduleName == "Scrubber") { Scrubber mm = (Scrubber)m; // do nothing inside breathable atmosphere if (mm.is_enabled && !env.breathable) { double co2_scrubbed = Math.Min(simulated_co2, mm.co2_rate); if (co2_scrubbed > double.Epsilon) { oxygen.scrubber_cost += mm.ec_rate * (co2_scrubbed / mm.co2_rate); oxygen.recycled += co2_scrubbed * oxygen.scrubber_efficiency; simulated_co2 -= co2_scrubbed; } } } } } // calculate life expectancy oxygen.life_expectancy = !env.breathable ? oxygen.storage / Math.Max(oxygen.consumed - oxygen.recycled, 0.0) : double.NaN; // return data return oxygen; }
GUIContent indicator_ec(Vessel v) { // note: if there isn't ec capacity, show danger double amount = Lib.GetResourceAmount(v, "ElectricCharge"); double capacity = Lib.GetResourceCapacity(v, "ElectricCharge"); double level = capacity > 0.0 ? amount / capacity : 0.0; GUIContent state = new GUIContent(); state.tooltip = "EC: " + (level * 100.0).ToString("F0") + "%"; if (level <= Settings.ResourceDangerThreshold) state.image = icon_battery_danger; else if (level <= Settings.ResourceWarningThreshold) state.image = icon_battery_warning; else state.image = icon_battery_nominal; return state; }
// estimate lifetime for the rule resource // note: this function must be called only once for simulation step // return 0 if no resource left, NaN if rate of change is positive, or seconds left in other cases public double EstimateLifetime(Vessel v) { // get prev amount for the vessel double prev_amount = 0.0; if (!prev_amounts.TryGetValue(v.id, out prev_amount)) prev_amount = 0.0; // calculate delta double amount = Lib.GetResourceAmount(v, this.resource_name); double meal_rate = this.interval > double.Epsilon ? this.rate / this.interval : 0.0; double delta = (amount - prev_amount) / TimeWarp.fixedDeltaTime - meal_rate * Lib.CrewCount(v); // remember prev amount prev_amounts[v.id] = amount; // return lifetime in seconds return amount <= double.Epsilon ? 0.0 : delta >= -double.Epsilon ? double.NaN : amount / -delta; }
void problem_scrubbers(Vessel v, List<Scrubber> scrubbers, ref List<Texture> icons, ref List<string> tooltips) { bool no_ec_left = Lib.GetResourceAmount(v, "ElectricCharge") <= double.Epsilon; bool scrubber_disabled = false; bool scrubber_nopower = false; foreach(Scrubber scrubber in scrubbers) { scrubber_disabled |= !scrubber.is_enabled; scrubber_nopower |= scrubber.is_enabled && no_ec_left; } if (scrubber_disabled) { icons.Add(icon_scrubber_warning); tooltips.Add("Scrubber disabled"); } if (scrubber_nopower) { icons.Add(icon_scrubber_danger); tooltips.Add("Scrubber has no power"); } }
// implement recycler mechanics for unloaded vessels public static void BackgroundUpdate(Vessel vessel, uint flight_id) { // get data ProtoPartModuleSnapshot m = Lib.GetProtoModule(vessel, flight_id, "Recycler"); bool is_enabled = Lib.GetProtoValue<bool>(m, "is_enabled"); string resource_name = Lib.GetProtoValue<string>(m, "resource_name"); string waste_name = Lib.GetProtoValue<string>(m, "waste_name"); double ec_rate = Lib.GetProtoValue<double>(m, "ec_rate"); double waste_rate = Lib.GetProtoValue<double>(m, "waste_rate"); double waste_ratio = Lib.GetProtoValue<double>(m, "waste_ratio"); string filter_name = Lib.GetProtoValue<string>(m, "filter_name"); double filter_rate = Lib.GetProtoValue<double>(m, "filter_rate"); if (is_enabled) { // calculate worst required resource percentual double worst_input = 1.0; double waste_required = waste_rate * TimeWarp.fixedDeltaTime; double waste_amount = Lib.GetResourceAmount(vessel, waste_name); worst_input = Math.Min(worst_input, waste_amount / waste_required); double ec_required = ec_rate * TimeWarp.fixedDeltaTime; double ec_amount = Lib.GetResourceAmount(vessel, "ElectricCharge"); worst_input = Math.Min(worst_input, ec_amount / ec_required); double filter_required = filter_rate * TimeWarp.fixedDeltaTime; if (filter_name.Length > 0 && filter_rate > 0.0) { double filter_amount = Lib.GetResourceAmount(vessel, filter_name); worst_input = Math.Min(worst_input, filter_amount / filter_required); } // recycle EC+waste+filter into resource Lib.RequestResource(vessel, waste_name, waste_required * worst_input); Lib.RequestResource(vessel, "ElectricCharge", ec_required * worst_input); Lib.RequestResource(vessel, filter_name, filter_required * worst_input); Lib.RequestResource(vessel, resource_name, -waste_required * worst_input * waste_ratio); } }
void IScienceDataTransmitter.TransmitData(List<ScienceData> dataQueue) { // if there is no signal if (!can_transmit) { // show a message to the user Message.Post(Severity.warning, "No signal", "We can't send the data"); // return data to the containers ReturnData(dataQueue); // do not transmit the data return; } // calculate total ec cost of transmission double total_amount = 0.0; foreach(ScienceData sd in dataQueue) total_amount += sd.dataAmount; double total_cost = total_amount * this.packetResourceCost; // if there is no EC to transmit the data if (total_cost > Lib.GetResourceAmount(vessel, "ElectricCharge")) { // show a message to the user Message.Post(Severity.warning, "Not enough power, <b>" + total_cost.ToString("F0") + " ElectricCharge</b> required", "We can't send the data"); // return data to the containers ReturnData(dataQueue); // do not transmit the data return; } // transmit the data ModuleDataTransmitter transmitter = (ModuleDataTransmitter)this; transmitter.TransmitData(dataQueue); }
void updateThermometers(Vessel v) { // get temperature double temp = Cache.VesselInfo(v).temperature; // get amount of ec double ec_amount = Lib.GetResourceAmount(v, "ElectricCharge"); // replace thermometer sensor readings with our own, to give the user some feedback about climate control mechanic foreach (ModuleEnviroSensor m in v.FindPartModulesImplementing <ModuleEnviroSensor>()) { if (m.sensorType == "TEMP" && m.sensorActive) { if (ec_amount <= double.Epsilon) { m.readoutInfo = "NO POWER!"; } else { m.readoutInfo = Lib.HumanReadableTemp(temp); } } } }
public void Update() { Animation[] anim = this.part.FindModelAnimators(animation_name); if (anim.Length > 0) { double ls = Lib.GetResourceAmount(this.part, resource_name); double capacity = Lib.GetResourceCapacity(this.part, resource_name); double red_capacity = capacity * threshold; if (ls <= red_capacity && green_status) { anim[0][animation_name].normalizedTime = 0.0f; anim[0][animation_name].speed = Math.Abs(anim[0][animation_name].speed); anim[0].Play(animation_name); green_status = false; } if (ls > red_capacity && !green_status) { anim[0][animation_name].normalizedTime = 1.0f; anim[0][animation_name].speed = -Math.Abs(anim[0][animation_name].speed); anim[0].Play(animation_name); green_status = true; } } }
void Breakdown(Vessel v, ProtoCrewMember c) { // constants const double food_penality = 0.2; // proportion of food lost on 'depressed' const double oxygen_penality = 0.2; // proportion of oxygen lost on 'wrong_valve' // get info double food_amount = Lib.GetResourceAmount(v, "Food"); double oxygen_amount = Lib.GetResourceAmount(v, "Oxygen"); // compile list of events with condition satisfied List<KerbalBreakdown> events = new List<KerbalBreakdown>(); events.Add(KerbalBreakdown.mumbling); //< do nothing, here so there is always something that can happen if (Lib.CrewCount(v) > 1) events.Add(KerbalBreakdown.argument); //< do nothing, add some variation to messages if (Lib.HasData(v)) events.Add(KerbalBreakdown.fat_finger); if (Malfunction.CanMalfunction(v)) events.Add(KerbalBreakdown.rage); if (food_amount > double.Epsilon) events.Add(KerbalBreakdown.depressed); if (oxygen_amount > double.Epsilon) events.Add(KerbalBreakdown.wrong_valve); // choose a breakdown event KerbalBreakdown breakdown = events[Lib.RandomInt(events.Count)]; // post message first so this one is shown before malfunction message Message.Post(Severity.breakdown, KerbalEvent.stress, v, c, breakdown); // trigger the event switch(breakdown) { case KerbalBreakdown.mumbling: break; // do nothing case KerbalBreakdown.argument: break; // do nothing case KerbalBreakdown.fat_finger: Lib.RemoveData(v); break; case KerbalBreakdown.rage: Malfunction.CauseMalfunction(v); break; case KerbalBreakdown.depressed: Lib.RequestResource(v, "Food", food_amount * food_penality); break; case KerbalBreakdown.wrong_valve: Lib.RequestResource(v, "Oxygen", oxygen_amount * oxygen_penality); break; } }
// implement recycler mechanics public void FixedUpdate() { // do nothing in the editor if (HighLogic.LoadedSceneIsEditor) return; if (is_enabled) { // calculate worst required resource percentual double worst_input = 1.0; double waste_required = waste_rate * TimeWarp.fixedDeltaTime; double waste_amount = Lib.GetResourceAmount(vessel, waste_name); worst_input = Math.Min(worst_input, waste_amount / waste_required); double ec_required = ec_rate * TimeWarp.fixedDeltaTime; double ec_amount = Lib.GetResourceAmount(vessel, "ElectricCharge"); worst_input = Math.Min(worst_input, ec_amount / ec_required); double filter_required = filter_rate * TimeWarp.fixedDeltaTime; if (filter_name.Length > 0 && filter_rate > 0.0) { double filter_amount = Lib.GetResourceAmount(vessel, filter_name); worst_input = Math.Min(worst_input, filter_amount / filter_required); } // recycle EC+waste+filter into resource this.part.RequestResource(waste_name, waste_required * worst_input); this.part.RequestResource("ElectricCharge", ec_required * worst_input); this.part.RequestResource(filter_name, filter_required * worst_input); this.part.RequestResource(resource_name, -waste_required * worst_input * waste_ratio); // set status Status = waste_amount <= double.Epsilon ? "No " + waste_name : ec_amount <= double.Epsilon ? "No Power" : "Running"; } else { Status = "Off"; } }
public static ec_data analyze_ec(List<Part> parts, environment_data env, crew_data crew, food_data food, oxygen_data oxygen, signal_data signal) { // store data ec_data ec = new ec_data(); // calculate climate cost ec.consumed = (double)crew.count * env.temp_diff * Settings.ElectricChargePerSecond; // scan the parts foreach(Part p in parts) { // accumulate EC storage ec.storage += Lib.GetResourceAmount(p, "ElectricCharge"); // remember if we already considered a resource converter module // rationale: we assume only the first module in a converter is active bool first_converter = true; // for each module foreach(PartModule m in p.Modules) { // command if (m.moduleName == "ModuleCommand") { ModuleCommand mm = (ModuleCommand)m; foreach(ModuleResource res in mm.inputResources) { if (res.name == "ElectricCharge") { ec.consumed += res.rate; } } } // solar panel else if (m.moduleName == "ModuleDeployableSolarPanel") { ModuleDeployableSolarPanel mm = (ModuleDeployableSolarPanel)m; double solar_k = (mm.useCurve ? mm.powerCurve.Evaluate((float)env.sun_dist) : env.sun_flux / Sim.SolarFluxAtHome()); double generated = mm.chargeRate * solar_k * env.atmo_factor; ec.generated_sunlight += generated; ec.best_ec_generator = Math.Max(ec.best_ec_generator, generated); } // generator else if (m.moduleName == "ModuleGenerator") { // skip launch clamps, that include a generator if (p.partInfo.name == "launchClamp1") continue; ModuleGenerator mm = (ModuleGenerator)m; foreach(ModuleResource res in mm.inputList) { if (res.name == "ElectricCharge") { ec.consumed += res.rate; } } foreach(ModuleResource res in mm.outputList) { if (res.name == "ElectricCharge") { ec.generated_shadow += res.rate; ec.generated_sunlight += res.rate; ec.best_ec_generator = Math.Max(ec.best_ec_generator, res.rate); } } } // converter // note: only electric charge is considered for resource converters // note: we only consider the first resource converter in a part, and ignore the rest else if (m.moduleName == "ModuleResourceConverter" && first_converter) { ModuleResourceConverter mm = (ModuleResourceConverter)m; foreach(ResourceRatio rr in mm.inputList) { if (rr.ResourceName == "ElectricCharge") { ec.consumed += rr.Ratio; } } foreach(ResourceRatio rr in mm.outputList) { if (rr.ResourceName == "ElectricCharge") { ec.generated_shadow += rr.Ratio; ec.generated_sunlight += rr.Ratio; ec.best_ec_generator = Math.Max(ec.best_ec_generator, rr.Ratio); } } first_converter = false; } // harvester // note: only electric charge is considered for resource harvesters else if (m.moduleName == "ModuleResourceHarvester") { ModuleResourceHarvester mm = (ModuleResourceHarvester)m; foreach(ResourceRatio rr in mm.inputList) { if (rr.ResourceName == "ElectricCharge") { ec.consumed += rr.Ratio; } } } // active radiators else if (m.moduleName == "ModuleActiveRadiator") { ModuleActiveRadiator mm = (ModuleActiveRadiator)m; if (mm.IsCooling) { foreach(var rr in mm.inputResources) { if (rr.name == "ElectricCharge") { ec.consumed += rr.rate; } } } } // wheels else if (m.moduleName == "ModuleWheelMotor") { ModuleWheelMotor mm = (ModuleWheelMotor)m; if (mm.motorEnabled && mm.inputResource.name == "ElectricCharge") { ec.consumed += mm.inputResource.rate; } } else if (m.moduleName == "ModuleWheelMotorSteering") { ModuleWheelMotorSteering mm = (ModuleWheelMotorSteering)m; if (mm.motorEnabled && mm.inputResource.name == "ElectricCharge") { ec.consumed += mm.inputResource.rate; } } // SCANsat support else if (m.moduleName == "SCANsat" || m.moduleName == "ModuleSCANresourceScanner") { // include it in ec consumption, if deployed if (SCANsat.isDeployed(p, m)) ec.consumed += Lib.ReflectionValue<float>(m, "power"); } // NearFutureSolar support // note: assume half the components are in sunlight, and average inclination is half else if (m.moduleName == "ModuleCurvedSolarPanel") { // 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 ec.generated_sunlight += 0.7071 * tot_rate; } } } // include cost from greenhouses artificial lighting ec.consumed += food.greenhouse_cost; // include cost from scrubbers ec.consumed += oxygen.scrubber_cost; // include relay cost for the best relay antenna ec.consumed += signal.relay_cost; // finally, calculate life expectancy of ec ec.life_expectancy_sunlight = ec.storage / Math.Max(ec.consumed - ec.generated_sunlight, 0.0); ec.life_expectancy_shadow = ec.storage / Math.Max(ec.consumed - ec.generated_shadow, 0.0); // return data return ec; }
// 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); } } } } }
GUIContent indicator_supplies(Vessel v, List<Scrubber> scrubbers, List<Greenhouse> greenhouses) { // get food & oxygen info double food_amount = Lib.GetResourceAmount(v, "Food"); double food_capacity = Lib.GetResourceCapacity(v, "Food"); double food_level = food_capacity > 0.0 ? food_amount / food_capacity : 1.0; double oxygen_amount = Lib.GetResourceAmount(v, "Oxygen"); double oxygen_capacity = Lib.GetResourceCapacity(v, "Oxygen"); double oxygen_level = oxygen_capacity > 0.0 ? oxygen_amount / oxygen_capacity : 1.0; double level = Math.Min(food_level, oxygen_level); // store the icon and tooltip GUIContent state = new GUIContent(); // choose an icon if (level <= Settings.ResourceDangerThreshold) state.image = icon_supplies_danger; else if (level <= Settings.ResourceWarningThreshold) state.image = icon_supplies_warning; else state.image = icon_supplies_nominal; // if there is someone on board List<string> tooltips = new List<string>(); int crew_count = Lib.CrewCount(v); if (crew_count > 0) { // get oxygen recycled by scrubbers double oxygen_recycled = 0.0; double ec_left = Lib.GetResourceAmount(v, "ElectricCharge"); double co2_left = Lib.GetResourceAmount(v, "CO2"); foreach(Scrubber scrubber in scrubbers) { if (scrubber.is_enabled) { double co2_consumed = Math.Max(co2_left, scrubber.co2_rate); ec_left -= scrubber.ec_rate; co2_left -= co2_consumed; if (ec_left > -double.Epsilon && co2_left > -double.Epsilon) oxygen_recycled += co2_consumed * scrubber.efficiency; else break; } } // calculate time until depletion for food double food_consumption = (double)crew_count * Settings.FoodPerMeal / Settings.MealFrequency; if (food_capacity > double.Epsilon && food_consumption > double.Epsilon) { double food_depletion = food_amount / food_consumption; tooltips.Add(food_amount / food_capacity > Settings.ResourceDangerThreshold ? "Food: <b>" + (food_level * 100.0).ToString("F0") + "%, </b>deplete in <b>" + Lib.HumanReadableDuration(food_depletion) + "</b>" : "Food: <b>depleted</b>"); } // calculate time until depletion for oxygen double oxygen_consumption = !LifeSupport.BreathableAtmosphere(v) ? (double)crew_count * Settings.OxygenPerSecond - oxygen_recycled : 0.0; if (oxygen_capacity > double.Epsilon && oxygen_consumption > double.Epsilon) { double oxygen_depletion = oxygen_amount / oxygen_consumption; tooltips.Add(oxygen_amount / oxygen_capacity > Settings.ResourceDangerThreshold ? "Oxygen: <b>" + (oxygen_level * 100.0).ToString("F0") + "%, </b>deplete in <b>" + Lib.HumanReadableDuration(oxygen_depletion) + "</b>" : "Oxygen: <b>depleted</b>"); } } state.tooltip = string.Join("\n", tooltips.ToArray()); return state; }
// 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); } } } } }
void resourceWarnings() { // for each vessel foreach (Vessel v in FlightGlobals.Vessels) { // skip invalid vessels if (!Lib.IsVessel(v)) { continue; } // skip resque missions if (Lib.IsResqueMission(v)) { continue; } // skip dead eva kerbal if (EVA.IsDead(v)) { continue; } // get vessel data vessel_data vd = DB.VesselData(v.id); // get EC amount and capacity double ec_amount = Lib.GetResourceAmount(v, "ElectricCharge"); double ec_capacity = Lib.GetResourceCapacity(v, "ElectricCharge"); double ec_perc = ec_capacity > 0.0 ? ec_amount / ec_capacity : 0.0; // if it has EC capacity if (ec_capacity > 0.0) { // check EC thresholds and show messages if (ec_perc <= Settings.ResourceDangerThreshold && vd.msg_ec < 2) { if (vd.cfg_ec == 1) { Message.Post(Severity.danger, VesselEvent.ec, v); } vd.msg_ec = 2; } else if (ec_perc <= Settings.ResourceWarningThreshold && vd.msg_ec < 1) { if (vd.cfg_ec == 1) { Message.Post(Severity.warning, VesselEvent.ec, v); } vd.msg_ec = 1; } else if (ec_perc > Settings.ResourceWarningThreshold && vd.msg_ec > 0) { if (vd.cfg_ec == 1) { Message.Post(Severity.relax, VesselEvent.ec, v); } vd.msg_ec = 0; } } // get food amount and capacity double food_amount = Lib.GetResourceAmount(v, "Food"); double food_capacity = Lib.GetResourceCapacity(v, "Food"); double food_perc = food_capacity > 0.0 ? food_amount / food_capacity : 0.0; // if it has food capacity if (food_capacity > 0.0) { // check food thresholds and show messages // note: no warnings at prelaunch if (food_perc <= Settings.ResourceDangerThreshold && vd.msg_food < 2) { if (vd.cfg_supply == 1 && v.situation != Vessel.Situations.PRELAUNCH) { Message.Post(Severity.danger, VesselEvent.food, v); } vd.msg_food = 2; } else if (food_perc <= Settings.ResourceWarningThreshold && vd.msg_food < 1) { if (vd.cfg_supply == 1 && v.situation != Vessel.Situations.PRELAUNCH) { Message.Post(Severity.warning, VesselEvent.food, v); } vd.msg_food = 1; } else if (food_perc > Settings.ResourceWarningThreshold && vd.msg_food > 0) { if (vd.cfg_supply == 1 && v.situation != Vessel.Situations.PRELAUNCH) { Message.Post(Severity.relax, VesselEvent.food, v); } vd.msg_food = 0; } } // get oxygen amount and capacity double oxygen_amount = Lib.GetResourceAmount(v, "Oxygen"); double oxygen_capacity = Lib.GetResourceCapacity(v, "Oxygen"); double oxygen_perc = oxygen_capacity > 0.0 ? oxygen_amount / oxygen_capacity : 0.0; // if it has oxygen capacity if (oxygen_capacity > 0.0) { // check oxygen thresholds and show messages // note: no warnings at prelaunch if (oxygen_perc <= Settings.ResourceDangerThreshold && vd.msg_oxygen < 2) { if (vd.cfg_supply == 1 && v.situation != Vessel.Situations.PRELAUNCH) { Message.Post(Severity.danger, VesselEvent.oxygen, v); } vd.msg_oxygen = 2; } else if (oxygen_perc <= Settings.ResourceWarningThreshold && vd.msg_oxygen < 1) { if (vd.cfg_supply == 1 && v.situation != Vessel.Situations.PRELAUNCH) { Message.Post(Severity.warning, VesselEvent.oxygen, v); } vd.msg_oxygen = 1; } else if (oxygen_perc > Settings.ResourceWarningThreshold && vd.msg_oxygen > 0) { if (vd.cfg_supply == 1 && v.situation != Vessel.Situations.PRELAUNCH) { Message.Post(Severity.relax, VesselEvent.oxygen, v); } vd.msg_oxygen = 0; } } } }
void toEVA(GameEvents.FromToAction <Part, Part> data) { // determine if inside breathable atmosphere bool breathable = LifeSupport.BreathableAtmosphere(data.from.vessel); // get total crew in the origin vessel double tot_crew = (double)data.from.vessel.GetVesselCrew().Count + 1.0; // add resource definitions to EVA vessel part Lib.SetupResource(data.to, "ElectricCharge", 0.0, Settings.ElectricChargeOnEVA); if (!breathable) { Lib.SetupResource(data.to, "Oxygen", 0.0, Settings.OxygenOnEVA); } // determine how much MonoPropellant to get // note: never more that the 'share' of this kerbal double monoprop = Math.Min(Lib.GetResourceAmount(data.from.vessel, "MonoPropellant") / tot_crew, Settings.MonoPropellantOnEVA); // determine how much ElectricCharge to get // note: never more that the 'share' of this kerbal // note: always keep half the ec in the vessel double ec = Math.Min(Lib.GetResourceAmount(data.from.vessel, "ElectricCharge") / (tot_crew * 2.0), Settings.ElectricChargeOnEVA); // EVA vessels start with 5 units of eva fuel, remove them data.to.RequestResource("EVA Propellant", 5.0); // transfer monoprop data.to.RequestResource("EVA Propellant", -data.from.RequestResource("MonoPropellant", monoprop)); // transfer ec data.to.RequestResource("ElectricCharge", -data.from.RequestResource("ElectricCharge", ec)); // if outside breathable atmosphere if (!breathable) { // determine how much Oxygen to get // note: never more that the 'share' of this kerbal double oxygen = Math.Min(Lib.GetResourceAmount(data.from.vessel, "Oxygen") / tot_crew, Settings.OxygenOnEVA); // transfer oxygen data.to.RequestResource("Oxygen", -data.from.RequestResource("Oxygen", oxygen)); } // get KerbalEVA KerbalEVA kerbal = data.to.FindModuleImplementing <KerbalEVA>(); // turn off headlamp light, to avoid stock bug that show the light for a split second when going on eva EVA.SetHeadlamp(kerbal, false); EVA.SetFlares(kerbal, false); // remove the helmet if inside breathable atmosphere // note: done in EVA::FixedUpdate(), but also done here avoid 'popping' of the helmet when going on eva EVA.SetHelmet(kerbal, !breathable); // remember if the kerbal has an helmet in the EVA module data.to.FindModuleImplementing <EVA>().has_helmet = !breathable; // show warning if there isn't monoprop in the eva suit if (monoprop <= double.Epsilon && !Lib.Landed(data.from.vessel)) { Message.Post(Severity.danger, "There isn't any <b>MonoPropellant</b> in the EVA suit", "Don't let the ladder go!"); } }
public static food_data analyze_food(List<Part> parts, environment_data env, crew_data crew) { // store data food_data food = new food_data(); // calculate food consumed food.consumed = (double)crew.count * Settings.FoodPerMeal / Settings.MealFrequency; // deduce waste produced by the crew per-second double simulated_waste = food.consumed; // scan the parts foreach(Part p in parts) { // accumulate food storage food.storage += Lib.GetResourceAmount(p, "Food"); // for each module foreach(PartModule m in p.Modules) { // greenhouse if (m.moduleName == "Greenhouse") { Greenhouse mm = (Greenhouse)m; // calculate natural lighting double natural_lighting = Greenhouse.NaturalLighting(env.sun_dist); // calculate ec consumed food.greenhouse_cost += mm.ec_rate * mm.lamps; // calculate lighting double lighting = natural_lighting * (mm.door_opened ? 1.0 : 0.0) + mm.lamps * (mm.door_opened ? 1.0 : 1.0 + Settings.GreenhouseDoorBonus); // calculate waste used double waste_used = Math.Min(simulated_waste, mm.waste_rate); double waste_perc = waste_used / mm.waste_rate; simulated_waste -= waste_used; // calculate growth bonus double growth_bonus = 0.0; growth_bonus += Settings.GreenhouseSoilBonus * (env.landed ? 1.0 : 0.0); growth_bonus += Settings.GreenhouseWasteBonus * waste_perc; // calculate growth factor double growth_factor = (mm.growth_rate * (1.0 + growth_bonus)) * lighting; // calculate food cultivated food.cultivated += mm.harvest_size * growth_factor; // calculate time-to-harvest if (growth_factor > double.Epsilon) { food.cultivated_tooltip += (food.cultivated_tooltip.Length > 0 ? "\n" : "") + "Time-to-harvest: <b>" + Lib.HumanReadableDuration(1.0 / growth_factor) + "</b>"; } } } } // calculate life expectancy food.life_expectancy = food.storage / Math.Max(food.consumed - food.cultivated, 0.0); // add formatting to tooltip if (food.cultivated_tooltip.Length > 0) food.cultivated_tooltip = "<i>" + food.cultivated_tooltip + "</i>"; // return data return food; }
// return percentage of radiations blocked by shielding public static double Shielding(Vessel v) { return(Shielding(Lib.GetResourceAmount(v, "Shielding"), Lib.GetResourceCapacity(v, "Shielding"))); }