public KeyValuePair <bool, double> modReturn; // Return from ECDevice public override void OnStart(StartState state) { // don't break tutorial scenarios & do something only in Flight scenario if (Lib.DisableScenario(this) || !Lib.IsFlight()) { return; } Lib.Debug("Executing OnStart"); // cache list of modules module = part.FindModulesImplementing <PartModule>().FindLast(k => k.moduleName == type); // get energy from cache resources = ResourceCache.Info(vessel, "ElectricCharge"); hasEnergy = resources.amount > double.Epsilon; // Force the update to run at least once lastBrokenState = !broken; hasEnergyChanged = !hasEnergy; hasFixedEnergyChanged = !hasEnergy; #if DEBUG // setup UI Fields["actualCost"].guiActive = true; Fields["broken"].guiActive = true; #endif }
void Indicator_EC(Panel p, Vessel v, Vessel_Info vi) { Resource_Info ec = ResourceCache.Info(v, "ElectricCharge"); Supply supply = Profile.supplies.Find(k => k.resource == "ElectricCharge"); double low_threshold = supply != null ? supply.low_threshold : 0.15; double depletion = ec.Depletion(vi.crew_count); string tooltip = Lib.BuildString ( "<align=left /><b>name\tlevel\tduration</b>\n", ec.level <= 0.005 ? "<color=#ff0000>" : ec.level <= low_threshold ? "<color=#ffff00>" : "<color=#cccccc>", "EC\t", Lib.HumanReadablePerc(ec.level), "\t", depletion <= double.Epsilon ? "depleted" : Lib.HumanReadableDuration(depletion), "</color>" ); Texture image = ec.level <= 0.005 ? Icons.battery_red : ec.level <= low_threshold ? Icons.battery_yellow : Icons.battery_white; p.SetIcon(image, tooltip); }
public void Execute(Vessel v, ScriptType type) { // do nothing if there is no EC left on the vessel Resource_Info ec = ResourceCache.Info(v, "ElectricCharge"); if (ec.amount <= double.Epsilon) { return; } // get the script Script script; if (scripts.TryGetValue(type, out script)) { // execute the script script.Execute(Boot(v)); // show message to the user // - unless the script is empty (can happen when being edited) if (script.states.Count > 0 && DB.Vessel(v).cfg_script) { Message.Post(Lib.BuildString("Script called on vessel <b>", v.vesselName, "</b>")); } } }
// return total radiation emitted in a vessel public static double Total(Vessel v) { // get resource cache Resource_Info ec = ResourceCache.Info(v, "ElectricCharge"); double tot = 0.0; if (v.loaded) { foreach (var emitter in Lib.FindModules <Emitter>(v)) { if (ec.amount > double.Epsilon || emitter.ec_rate <= double.Epsilon) { tot += emitter.running ? emitter.radiation : 0.0; } } } else { foreach (ProtoPartModuleSnapshot m in Lib.FindModules(v.protoVessel, "Emitter")) { if (ec.amount > double.Epsilon || Lib.Proto.GetDouble(m, "ec_rate") <= double.Epsilon) { tot += Lib.Proto.GetBool(m, "running") ? Lib.Proto.GetDouble(m, "radiation") : 0.0; } } } return(tot); }
// execute the recipe public bool Execute(Vessel v, Vessel_Resources resources) { // determine worst input ratio // - pure input recipes can just underflow double worst_input = left; if (outputs.Count > 0) { for (int i = 0; i < inputs.Count; ++i) { Entry e = inputs[i]; Resource_Info res = resources.Info(v, e.name); worst_input = Lib.Clamp((res.amount + res.deferred) * e.inv_quantity, 0.0, worst_input); } } // determine worst output ratio // - pure output recipes can just overflow double worst_output = left; if (inputs.Count > 0) { for (int i = 0; i < outputs.Count; ++i) { Entry e = outputs[i]; if (!e.dump) // ignore outputs that can dump overboard { Resource_Info res = resources.Info(v, e.name); worst_output = Lib.Clamp((res.capacity - (res.amount + res.deferred)) * e.inv_quantity, 0.0, worst_output); } } } // determine worst-io double worst_io = Math.Min(worst_input, worst_output); // consume inputs for (int i = 0; i < inputs.Count; ++i) { Entry e = inputs[i]; resources.Consume(v, e.name, e.quantity * worst_io); } // produce outputs for (int i = 0; i < outputs.Count; ++i) { Entry e = outputs[i]; resources.Produce(v, e.name, e.quantity * worst_io); } // update amount left to execute left -= worst_io; // the recipe was executed, at least partially return(worst_io > double.Epsilon); }
static void ProcessConverter(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleResourceConverter converter, Vessel_Resources resources, double elapsed_s) { // note: ignore stock temperature mechanic of converters // note: ignore autoshutdown // note: non-mandatory resources 'dynamically scale the ratios', that is exactly what mandatory resources do too (DERP ALERT) // note: 'undo' stock behaviour by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation) // if active if (Lib.Proto.GetBool(m, "IsActivated")) { // determine if vessel is full of all output resources // note: comparing against previous amount bool full = true; foreach (var or in converter.outputList) { Resource_Info res = resources.Info(v, or.ResourceName); full &= (res.level >= converter.FillAmount - double.Epsilon); } // if not full if (!full) { // deduce crew bonus int exp_level = -1; if (converter.UseSpecialistBonus) { foreach (ProtoCrewMember c in Lib.CrewList(v)) { if (c.experienceTrait.Effects.Find(k => k.Name == converter.ExperienceEffect) != null) { exp_level = Math.Max(exp_level, c.experienceLevel); } } } double exp_bonus = exp_level < 0 ? converter.EfficiencyBonus * converter.SpecialistBonusBase : converter.EfficiencyBonus * (converter.SpecialistBonusBase + (converter.SpecialistEfficiencyFactor * (exp_level + 1))); // create and commit recipe Resource_Recipe recipe = new Resource_Recipe(); foreach (var ir in converter.inputList) { recipe.Input(ir.ResourceName, ir.Ratio * exp_bonus * elapsed_s); } foreach (var or in converter.outputList) { recipe.Output(or.ResourceName, or.Ratio * exp_bonus * elapsed_s, or.DumpExcess); } resources.Transform(recipe); } // undo stock behaviour by forcing last_update_time to now Lib.Proto.Set(m, "lastUpdateTime", Planetarium.GetUniversalTime()); } }
public override void OnUpdate() { if (!Lib.IsFlight() || module == null) { return; } // get energy from cache resources = ResourceCache.Info(vessel, "ElectricCharge"); hasEnergy = resources.amount > double.Epsilon; // Update UI only if hasEnergy has changed or if is broken state has changed if (broken) { if (broken != lastBrokenState) { lastBrokenState = broken; Update_UI(!broken); } } else if (hasEnergyChanged != hasEnergy) { Lib.Debug("Energy state has changed: {0}", hasEnergy); // Wait 1 second before enabled UI. if (hasEnergy) { Lib.Delay(1f); } hasEnergyChanged = hasEnergy; lastBrokenState = false; // Update UI Update_UI(hasEnergy); } // Constantly Update UI for special modules if (broken) { Constant_OnGUI(!broken); } else { Constant_OnGUI(hasEnergy); } if (!hasEnergy || broken) { actualCost = 0; isConsuming = false; } else { isConsuming = GetIsConsuming(); } }
public virtual void Update() { if (Lib.IsFlight() && Features.Deploy) { // get ec resource handler resourceInfo = ResourceCache.Info(vessel, "ElectricCharge"); hasEC = resourceInfo.amount > double.Epsilon; isConsuming = GetIsConsuming; if (!isConsuming) { actualECCost = 0; } } }
public void Execute(Vessel v, VesselData vd, Vessel_Resources resources) { // get crew List <ProtoCrewMember> crew = Lib.CrewList(v); // get resource handler Resource_Info res = resources.Info(v, resource); // get data from db SupplyData sd = DB.Vessel(v).Supply(resource); // message obey user config bool show_msg = resource == "ElectricCharge" ? vd.cfg_ec : vd.cfg_supply; // messages are shown only if there is some capacity and the vessel is manned // special case: ElectricCharge related messages are shown for unmanned vessels too if (res.capacity > double.Epsilon && (crew.Count > 0 || resource == "ElectricCharge")) { // manned/probe message variant uint variant = crew.Count > 0 ? 0 : 1u; // manage messages if (res.level <= double.Epsilon && sd.message < 2) { if (empty_message.Length > 0 && show_msg) { Message.Post(Severity.danger, Lib.ExpandMsg(empty_message, v, null, variant)); } sd.message = 2; } else if (res.level < low_threshold && sd.message < 1) { if (low_message.Length > 0 && show_msg) { Message.Post(Severity.warning, Lib.ExpandMsg(low_message, v, null, variant)); } sd.message = 1; } else if (res.level > low_threshold && sd.message > 0) { if (refill_message.Length > 0 && show_msg) { Message.Post(Severity.relax, Lib.ExpandMsg(refill_message, v, null, variant)); } sd.message = 0; } } }
// return a resource handler public Resource_Info Info(Vessel v, string resource_name) { // try to get existing entry if any if (resources.TryGetValue(resource_name, out Resource_Info res)) { return(res); } // create new entry res = new Resource_Info(v, resource_name); // remember new entry resources.Add(resource_name, res); // return new entry return(res); }
public void FixedUpdate() { // do nothing in the editor if (Lib.IsEditor()) { return; } // if enabled, and there is ec consumption if (running && ec_rate > double.Epsilon) { // get resource cache Resource_Info ec = ResourceCache.Info(vessel, "ElectricCharge"); // consume EC ec.Consume(ec_rate * Kerbalism.elapsed_s); } }
void Indicator_Supplies(Panel p, Vessel v, Vessel_Info vi) { List <string> tooltips = new List <string>(); uint max_severity = 0; if (vi.crew_count > 0) { foreach (Supply supply in Profile.supplies.FindAll(k => k.resource != "ElectricCharge")) { Resource_Info res = ResourceCache.Info(v, supply.resource); double depletion = res.Depletion(vi.crew_count); if (res.capacity > double.Epsilon) { if (tooltips.Count == 0) { tooltips.Add("<align=left /><b>name\t\tlevel\tduration</b>"); } tooltips.Add(Lib.BuildString ( res.level <= 0.005 ? "<color=#ff0000>" : res.level <= supply.low_threshold ? "<color=#ffff00>" : "<color=#cccccc>", supply.resource, supply.resource != "Ammonia" ? "\t\t" : "\t", //< hack: make ammonia fit damn it Lib.HumanReadablePerc(res.level), "\t", depletion <= double.Epsilon ? "depleted" : Lib.HumanReadableDuration(depletion), "</color>" )); uint severity = res.level <= 0.005 ? 2u : res.level <= supply.low_threshold ? 1u : 0; max_severity = Math.Max(max_severity, severity); } } } Texture image = max_severity == 2 ? Icons.box_red : max_severity == 1 ? Icons.box_yellow : Icons.box_white; p.SetIcon(image, string.Join("\n", tooltips.ToArray())); }
static void ProcessCryoTank(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule simple_boiloff, Vessel_Resources resources, double elapsed_s) { // note: cryotank module already does a post-facto simulation of background boiling, and we could use that for the boiling // however, it also does simulate the ec consumption that way, so we have to disable the post-facto simulation // get fuel name string fuel_name = Lib.ReflectionValue <string>(simple_boiloff, "FuelName"); // get resource handlers Resource_Info ec = resources.Info(v, "ElectricCharge"); Resource_Info fuel = resources.Info(v, fuel_name); // if there is some fuel // note: comparing against amount in previous simulation step if (fuel.amount > double.Epsilon) { // get capacity in the part double capacity = p.resources.Find(k => k.resourceName == fuel_name).maxAmount; // if cooling is enabled and there was enough ec // note: comparing against amount in previous simulation step if (Lib.Proto.GetBool(m, "CoolingEnabled") && ec.amount > double.Epsilon) { // get cooling ec cost per 1000 units of fuel, per-second double cooling_cost = Lib.ReflectionValue <float>(simple_boiloff, "CoolingCost"); // consume ec ec.Consume(cooling_cost * capacity * 0.001 * elapsed_s); } // if there wasn't ec, or if cooling is disabled else { // get boiloff rate in proportion to fuel amount, per-second double boiloff_rate = Lib.ReflectionValue <float>(simple_boiloff, "BoiloffRate") * 0.00000277777; // let it boil off fuel.Consume(capacity * (1.0 - Math.Pow(1.0 - boiloff_rate, elapsed_s))); } } // disable post-facto simulation Lib.Proto.Set(m, "LastUpdateTime", v.missionTime); }
public static void Update(Vessel v) { // do nothing if not an eva kerbal if (!v.isEVA) { return; } // get KerbalEVA module KerbalEVA kerbal = Lib.FindModules <KerbalEVA>(v)[0]; // get resource handler Resource_Info ec = ResourceCache.Info(v, "ElectricCharge"); // determine if headlamps need ec // - not required if there is no EC capacity in eva kerbal (no ec supply in profile) // - not required if no EC cost for headlamps is specified (set by the user) bool need_ec = ec.capacity > double.Epsilon && Settings.HeadLampsCost > double.Epsilon; // consume EC for the headlamps if (need_ec && kerbal.lampOn) { ec.Consume(Settings.HeadLampsCost * Kerbalism.elapsed_s); } // force the headlamps on/off HeadLamps(kerbal, kerbal.lampOn && (!need_ec || ec.amount > double.Epsilon)); // if dead if (IsDead(v)) { // enforce freezed state Freeze(kerbal); // disable modules DisableModules(kerbal); // remove plant flag action kerbal.flagItems = 0; } }
static void Render_Supplies(Panel p, Vessel v, Vessel_Info vi, Vessel_Resources resources) { // for each supply int supplies = 0; foreach (Supply supply in Profile.supplies) { // get resource info Resource_Info res = resources.Info(v, supply.resource); // only show estimate if the resource is present if (res.amount <= double.Epsilon) { continue; } // render panel title, if not done already if (supplies == 0) { p.SetSection("SUPPLIES"); } // rate tooltip string rate_tooltip = Math.Abs(res.rate) >= 1e-10 ? Lib.BuildString ( res.rate > 0.0 ? "<color=#00ff00><b>" : "<color=#ff0000><b>", Lib.HumanReadableRate(Math.Abs(res.rate)), "</b></color>" ) : string.Empty; // determine label string label = supply.resource == "ElectricCharge" ? "battery" : Lib.SpacesOnCaps(supply.resource).ToLower(); // finally, render resource supply p.SetContent(label, Lib.HumanReadableDuration(res.Depletion(vi.crew_count)), rate_tooltip); ++supplies; } }
public void FixedUpdate() { // in flight if (Lib.IsFlight()) { // if we are transmitting using the stock system if (stream.Transmitting()) { // get ec resource handler Resource_Info ec = ResourceCache.Info(vessel, "ElectricCharge"); // if we are still linked, and there is ec left if (CanTransmit() && ec.amount > double.Epsilon) { // compression factor // - used to avoid making the user wait too much for transmissions that // don't happen in background, while keeping transmission rates realistic const double compression = 16.0; // transmit using the data stream stream.Update(DataRate * Kerbalism.elapsed_s * compression, vessel); // consume ec ec.Consume(DataResourceCost * Kerbalism.elapsed_s); } else { // abort transmission, return data to the vessel stream.Abort(vessel); // inform the user ScreenMessages.PostScreenMessage("Transmission aborted", 5.0f, ScreenMessageStyle.UPPER_LEFT); } } } }
public void FixedUpdate() { // do nothing in editor if (Lib.IsEditor()) { return; } // do nothing if there isn't a solar panel if (panel == null) { return; } // get resource handler Resource_Info ec = ResourceCache.Info(vessel, "ElectricCharge"); // get vessel data from cache Vessel_Info info = Cache.VesselInfo(vessel); // do nothing if vessel is invalid if (!info.is_valid) { return; } // detect if sunlight is evaluated analytically bool analytical_sunlight = info.sunlight > 0.0 && info.sunlight < 1.0; // detect occlusion from other vessel parts // - we are only interested when the sunlight evaluation is discrete var collider = panel.hit.collider; bool locally_occluded = !analytical_sunlight && collider != null && info.sunlight > 0.0; // if panel is enabled and extended, and if sun is not occluded, not even locally if (panel.isEnabled && panel.deployState == ModuleDeployablePart.DeployState.EXTENDED && info.sunlight > 0.0 && !locally_occluded) { // calculate cosine factor // - the stock module is already computing the tracking direction double cosine_factor = Math.Max(Vector3d.Dot(info.sun_dir, panel.trackingDotTransform.forward), 0.0); // calculate normalized solar flux // - this include fractional sunlight if integrated over orbit // - this include atmospheric absorption if inside an atmosphere double norm_solar_flux = info.solar_flux / Sim.SolarFluxAtHome(); // calculate output double output = rate // nominal panel charge rate at 1 AU * norm_solar_flux // normalized flux at panel distance from sun * cosine_factor; // cosine factor of panel orientation // produce EC ec.Produce(output * Kerbalism.elapsed_s); // update ui field_visibility = info.sunlight * 100.0; field_atmosphere = info.atmo_factor * 100.0; field_exposure = cosine_factor * 100.0; field_output = output; Fields["field_visibility"].guiActive = analytical_sunlight; Fields["field_atmosphere"].guiActive = info.atmo_factor < 1.0; Fields["field_exposure"].guiActive = true; Fields["field_output"].guiActive = true; } // if panel is disabled, retracted, or in shadow else { // hide ui Fields["field_visibility"].guiActive = false; Fields["field_atmosphere"].guiActive = false; Fields["field_exposure"].guiActive = false; Fields["field_output"].guiActive = false; } // update status ui field_status = analytical_sunlight ? "<color=#ffff22>Integrated over the orbit</color>" : locally_occluded ? "<color=#ff2222>Occluded by vessel</color>" : info.sunlight < 1.0 ? "<color=#ff2222>Occluded by celestial body</color>" : string.Empty; Fields["field_status"].guiActive = field_status.Length > 0; }
static void ProcessRadioisotopeGenerator(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule radioisotope_generator, Resource_Info ec, double elapsed_s) { // note: doesn't support easy mode double power = Lib.ReflectionValue <float>(radioisotope_generator, "BasePower"); double half_life = Lib.ReflectionValue <float>(radioisotope_generator, "HalfLife"); double mission_time = v.missionTime / (3600.0 * Lib.HoursInDay() * Lib.DaysInYear()); double remaining = Math.Pow(2.0, (-mission_time) / half_life); ec.Produce(power * remaining * elapsed_s); }
public static void Update(Vessel v, Vessel_Info vi, VesselData vd, Vessel_Resources resources, double elapsed_s) { // get most used resource handlers Resource_Info ec = resources.Info(v, "ElectricCharge"); // store data required to support multiple modules of same type in a part var PD = new Dictionary <string, Lib.module_prefab_data>(); // for each part foreach (ProtoPartSnapshot p in v.protoVessel.protoPartSnapshots) { // get part prefab (required for module properties) Part part_prefab = PartLoader.getPartInfoByName(p.partName).partPrefab; // get all module prefabs var module_prefabs = part_prefab.FindModulesImplementing <PartModule>(); // clear module indexes PD.Clear(); // for each module foreach (ProtoPartModuleSnapshot m in p.modules) { // get module type // if the type is unknown, skip it Module_Type type = ModuleType(m.moduleName); if (type == Module_Type.Unknown) { continue; } // get the module prefab // if the prefab doesn't contain this module, skip it PartModule module_prefab = Lib.ModulePrefab(module_prefabs, m.moduleName, PD); if (!module_prefab) { continue; } // if the module is disabled, skip it // note: this must be done after ModulePrefab is called, so that indexes are right if (!Lib.Proto.GetBool(m, "isEnabled")) { continue; } // process modules // note: this should be a fast switch, possibly compiled to a jump table switch (type) { case Module_Type.Reliability: Reliability.BackgroundUpdate(v, p, m, module_prefab as Reliability); break; case Module_Type.Experiment: Experiment.BackgroundUpdate(v, m, module_prefab as Experiment, ec, elapsed_s); break; case Module_Type.Greenhouse: Greenhouse.BackgroundUpdate(v, m, module_prefab as Greenhouse, vi, resources, elapsed_s); break; case Module_Type.GravityRing: GravityRing.BackgroundUpdate(v, p, m, module_prefab as GravityRing, ec, elapsed_s); break; case Module_Type.Emitter: Emitter.BackgroundUpdate(v, p, m, module_prefab as Emitter, ec, elapsed_s); break; case Module_Type.Harvester: Harvester.BackgroundUpdate(v, m, module_prefab as Harvester, elapsed_s); break; case Module_Type.Laboratory: Laboratory.BackgroundUpdate(v, p, m, module_prefab as Laboratory, ec, elapsed_s); break; case Module_Type.Command: ProcessCommand(v, p, m, module_prefab as ModuleCommand, resources, elapsed_s); break; case Module_Type.Panel: ProcessPanel(v, p, m, module_prefab as ModuleDeployableSolarPanel, vi, ec, elapsed_s); break; case Module_Type.Generator: ProcessGenerator(v, p, m, module_prefab as ModuleGenerator, resources, elapsed_s); break; case Module_Type.Converter: ProcessConverter(v, p, m, module_prefab as ModuleResourceConverter, resources, elapsed_s); break; case Module_Type.Drill: ProcessHarvester(v, p, m, module_prefab as ModuleResourceHarvester, resources, elapsed_s); break; case Module_Type.AsteroidDrill: ProcessAsteroidDrill(v, p, m, module_prefab as ModuleAsteroidDrill, resources, elapsed_s); break; case Module_Type.StockLab: ProcessStockLab(v, p, m, module_prefab as ModuleScienceConverter, ec, elapsed_s); break; case Module_Type.Light: ProcessLight(v, p, m, module_prefab as ModuleLight, ec, elapsed_s); break; case Module_Type.Scanner: ProcessScanner(v, p, m, module_prefab, part_prefab, vd, ec, elapsed_s); break; case Module_Type.CurvedPanel: ProcessCurvedPanel(v, p, m, module_prefab, part_prefab, vi, ec, elapsed_s); break; case Module_Type.FissionGenerator: ProcessFissionGenerator(v, p, m, module_prefab, ec, elapsed_s); break; case Module_Type.RadioisotopeGenerator: ProcessRadioisotopeGenerator(v, p, m, module_prefab, ec, elapsed_s); break; case Module_Type.CryoTank: ProcessCryoTank(v, p, m, module_prefab, resources, elapsed_s); break; case Module_Type.AntennaDeploy: AntennaDeploy.BackgroundUpdate(v, p, m, vi, ec, elapsed_s); break; } } } }
static void ProcessLight(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleLight light, Resource_Info ec, double elapsed_s) { if (light.useResources && Lib.Proto.GetBool(m, "isOn")) { ec.Consume(light.resourceAmount * elapsed_s); } }
public void Execute(Vessel v, Vessel_Info vi, Vessel_Resources resources, double elapsed_s) { // store list of crew to kill List <ProtoCrewMember> deferred_kills = new List <ProtoCrewMember>(); // get input resource handler Resource_Info res = input.Length > 0 ? resources.Info(v, input) : null; // determine message variant uint variant = vi.temperature < Settings.SurvivalTemperature ? 0 : 1u; // get product of all environment modifiers double k = Modifiers.Evaluate(v, vi, resources, modifiers); // for each crew foreach (ProtoCrewMember c in Lib.CrewList(v)) { // get kerbal data KerbalData kd = DB.Kerbal(c.name); // skip rescue kerbals if (kd.rescue) { continue; } // skip disabled kerbals if (kd.disabled) { continue; } // get kerbal property data from db RuleData rd = kd.Rule(name); // if continuous double step; if (interval <= double.Epsilon) { // influence consumption by elapsed time step = elapsed_s; } // if interval-based else { // accumulate time rd.time_since += elapsed_s; // determine number of steps step = Math.Floor(rd.time_since / interval); // consume time rd.time_since -= step * interval; // remember if a meal is consumed/produced in this simulation step res.meal_happened |= step > 0.99; if (output.Length > 0) { ResourceCache.Info(v, output).meal_happened |= step > 0.99; } } // if continuous, or if one or more intervals elapsed if (step > double.Epsilon) { // if there is a resource specified if (res != null && rate > double.Epsilon) { // determine amount of resource to consume double required = rate // consumption rate * k // product of environment modifiers * step; // seconds elapsed or number of steps // if there is no output if (output.Length == 0) { // simply consume (that is faster) res.Consume(required); } // if there is an output else { // transform input into output resource // - rules always dump excess overboard (because it is waste) Resource_Recipe recipe = new Resource_Recipe(); recipe.Input(input, required); recipe.Output(output, required * ratio, true); resources.Transform(recipe); } } // degenerate: // - if the environment modifier is not telling to reset (by being zero) // - if this rule is resource-less, or if there was not enough resource in the vessel if (k > 0.0 && (input.Length == 0 || res.amount <= double.Epsilon)) { rd.problem += degeneration // degeneration rate per-second or per-interval * k // product of environment modifiers * step // seconds elapsed or by number of steps * Variance(c, variance); // kerbal-specific variance } // else slowly recover else { rd.problem *= 1.0 / (1.0 + Math.Max(interval, 1.0) * step * 0.002); rd.problem = Math.Max(rd.problem, 0.0); } } // kill kerbal if necessary if (rd.problem >= fatal_threshold) { if (fatal_message.Length > 0) { Message.Post(breakdown ? Severity.breakdown : Severity.fatality, Lib.ExpandMsg(fatal_message, v, c, variant)); } if (breakdown) { // trigger breakdown event Misc.Breakdown(v, c); // move back between warning and danger level rd.problem = (warning_threshold + danger_threshold) * 0.5; // make sure next danger messagen is shown rd.message = 1; } else { deferred_kills.Add(c); } } // show messages else if (rd.problem >= danger_threshold && rd.message < 2) { if (danger_message.Length > 0) { Message.Post(Severity.danger, Lib.ExpandMsg(danger_message, v, c, variant)); } rd.message = 2; } else if (rd.problem >= warning_threshold && rd.message < 1) { if (warning_message.Length > 0) { Message.Post(Severity.warning, Lib.ExpandMsg(warning_message, v, c, variant)); } rd.message = 1; } else if (rd.problem < warning_threshold && rd.message > 0) { if (relax_message.Length > 0) { Message.Post(Severity.relax, Lib.ExpandMsg(relax_message, v, c, variant)); } rd.message = 0; } } // execute the deferred kills foreach (ProtoCrewMember c in deferred_kills) { Misc.Kill(v, c); } }
static void ProcessFissionGenerator(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule fission_generator, Resource_Info ec, double elapsed_s) { // note: ignore heat double power = Lib.ReflectionValue <float>(fission_generator, "PowerGeneration"); var reactor = p.modules.Find(k => k.moduleName == "FissionReactor"); double tweakable = reactor == null ? 1.0 : Lib.ConfigValue(reactor.moduleValues, "CurrentPowerPercent", 100.0) * 0.01; ec.Produce(power * tweakable * elapsed_s); }
public static void BackgroundUpdate(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, AdvancedEC advancedEC, Resource_Info ec, double elapsed_s) { if (advancedEC.isConsuming) { ec.Consume(advancedEC.extra_Cost * elapsed_s); } }
static void ProcessScanner(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule scanner, Part part_prefab, VesselData vd, Resource_Info ec, double elapsed_s) { // get ec consumption rate double power = SCANsat.EcConsumption(scanner); // if the scanner doesn't require power to operate, we aren't interested in simulating it if (power <= double.Epsilon) { return; } // get scanner state bool is_scanning = Lib.Proto.GetBool(m, "scanning"); // if its scanning if (is_scanning) { // consume ec ec.Consume(power * elapsed_s); // if there isn't ec // - comparing against amount in previous simulation step if (ec.amount <= double.Epsilon) { // unregister scanner SCANsat.StopScanner(v, m, part_prefab); is_scanning = false; // remember disabled scanner vd.scansat_id.Add(p.flightID); // give the user some feedback if (vd.cfg_ec) { Message.Post(Lib.BuildString("SCANsat sensor was disabled on <b>", v.vesselName, "</b>")); } } } // if it was disabled in background else if (vd.scansat_id.Contains(p.flightID)) { // if there is enough ec // note: comparing against amount in previous simulation step if (ec.level > 0.25) //< re-enable at 25% EC { // re-enable the scanner SCANsat.ResumeScanner(v, m, part_prefab); is_scanning = true; // give the user some feedback if (vd.cfg_ec) { Message.Post(Lib.BuildString("SCANsat sensor resumed operations on <b>", v.vesselName, "</b>")); } } } // forget active scanners if (is_scanning) { vd.scansat_id.Remove(p.flightID); } }
public static void BackgroundUpdate(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Laboratory lab, Resource_Info ec, double elapsed_s) { // if enabled if (Lib.Proto.GetBool(m, "running")) { // if a researcher is not required, or the researcher is present CrewSpecs researcher_cs = new CrewSpecs(lab.researcher); int qtty = 0, crewlvl = 0; if (!researcher_cs || researcher_cs.Check(p.protoModuleCrew, out qtty, out crewlvl)) { // get sample to analyze string sample_filename = Next_sample(v); // if there is a sample to analyze if (sample_filename.Length > 0) { // consume EC ec.Consume(lab.ec_rate * elapsed_s); // if there was ec // - comparing against amount in previous simulation step if (ec.amount > double.Epsilon) { double analysis_rateAVG = lab.analysis_rate; if (researcher_cs) { analysis_rateAVG *= qtty * crewlvl; } // analyze the sample Analyze(v, sample_filename, analysis_rateAVG * elapsed_s); } } } } }
public void FixedUpdate() { // do nothing in the editor if (Lib.IsEditor()) { return; } // if enabled if (running) { int qtty = 0, crewlvl = 0; // if a researcher is not required, or the researcher is present if (!researcher_cs || researcher_cs.Check(part.protoModuleCrew, out qtty, out crewlvl)) { // get next sample to analyze string sample_filename = Next_sample(vessel); // if there is a sample to analyze if (sample_filename.Length > 0) { // consume EC Resource_Info ec = ResourceCache.Info(vessel, "ElectricCharge"); ec.Consume(ec_rate * Kerbalism.elapsed_s); // if there was ec // - comparing against amount in previous simulation step if (ec.amount > double.Epsilon) { analysis_rateAVG = analysis_rate; if (researcher_cs) { analysis_rateAVG *= qtty * crewlvl; } // analyze the sample Analyze(vessel, sample_filename, analysis_rateAVG * Kerbalism.elapsed_s); //Analyze(vessel, sample_filename, analysis_rate * Kerbalism.elapsed_s); // update status status = Science.Experiment(sample_filename).name; } // if there was no ec else { // update status status = "<color=yellow>no electric charge</color>"; } } // if there is no sample to analyze else { // update status status = "no samples to analyze"; } } // if a researcher is required, but missing else { // update status status = Lib.BuildString("<color=yellow>", researcher_cs.Warning(), "</color>"); } } // if disabled else { // update status status = "disabled"; } }
static void ProcessStockLab(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleScienceConverter lab, Resource_Info ec, double elapsed_s) { // note: we are only simulating the EC consumption // note: there is no easy way to 'stop' the lab when there isn't enough EC // if active if (Lib.Proto.GetBool(m, "IsActivated")) { // consume ec ec.Consume(lab.powerRequirement * elapsed_s); } }
//trigger a random breakdown event public static void Breakdown(Vessel v, ProtoCrewMember c) { // constants const double res_penalty = 0.1; // proportion of food lost on 'depressed' and 'wrong_valve' // get a supply resource at random Resource_Info res = null; if (Profile.supplies.Count > 0) { Supply supply = Profile.supplies[Lib.RandomInt(Profile.supplies.Count)]; res = ResourceCache.Info(v, supply.resource); } // 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.HasData(v)) { events.Add(KerbalBreakdown.fat_finger); } if (Reliability.CanMalfunction(v)) { events.Add(KerbalBreakdown.rage); } if (res != null && res.amount > double.Epsilon) { events.Add(KerbalBreakdown.wrong_valve); } // choose a breakdown event KerbalBreakdown breakdown = events[Lib.RandomInt(events.Count)]; // generate message string text = ""; string subtext = ""; switch (breakdown) { case KerbalBreakdown.mumbling: text = "$ON_VESSEL$KERBAL has been in space for too long"; subtext = "Mumbling incoherently"; break; case KerbalBreakdown.fat_finger: text = "$ON_VESSEL$KERBAL is pressing buttons at random on the control panel"; subtext = "Science data has been lost"; break; case KerbalBreakdown.rage: text = "$ON_VESSEL$KERBAL is possessed by a blind rage"; subtext = "A component has been damaged"; break; case KerbalBreakdown.wrong_valve: text = "$ON_VESSEL$KERBAL opened the wrong valve"; subtext = res.resource_name + " has been lost"; break; } // post message first so this one is shown before malfunction message Message.Post(Severity.breakdown, Lib.ExpandMsg(text, v, c), subtext); // trigger the event switch (breakdown) { case KerbalBreakdown.mumbling: break; // do nothing case KerbalBreakdown.fat_finger: Lib.RemoveData(v); break; case KerbalBreakdown.rage: Reliability.CauseMalfunction(v); break; case KerbalBreakdown.wrong_valve: res.Consume(res.amount * res_penalty); break; } // remove reputation if (HighLogic.CurrentGame.Mode == Game.Modes.CAREER) { Reputation.Instance.AddReputation(-Settings.BreakdownReputation, TransactionReasons.Any); } }
// call scripts automatically when conditions are met public void Automate(Vessel v, Vessel_Info vi, Vessel_Resources resources) { // do nothing if automation is disabled if (!Features.Automation) { return; } // get current states Resource_Info ec = resources.Info(v, "ElectricCharge"); bool sunlight = vi.sunlight > double.Epsilon; bool power_low = ec.level < 0.2; bool power_high = ec.level > 0.8; bool radiation_low = vi.radiation < 0.000005552; //< 0.02 rad/h bool radiation_high = vi.radiation > 0.00001388; //< 0.05 rad/h bool signal = vi.connection.linked; // get current situation bool landed = false; bool atmo = false; bool space = false; switch (v.situation) { case Vessel.Situations.LANDED: case Vessel.Situations.SPLASHED: landed = true; break; case Vessel.Situations.FLYING: atmo = true; break; case Vessel.Situations.SUB_ORBITAL: case Vessel.Situations.ORBITING: case Vessel.Situations.ESCAPING: space = true; break; } // compile list of scripts that need to be called var to_exec = new List <Script>(); foreach (var p in scripts) { ScriptType type = p.Key; Script script = p.Value; if (script.states.Count == 0) { continue; //< skip empty scripts (may happen during editing) } switch (type) { case ScriptType.landed: if (landed && script.prev == "0") { to_exec.Add(script); } script.prev = landed ? "1" : "0"; break; case ScriptType.atmo: if (atmo && script.prev == "0") { to_exec.Add(script); } script.prev = atmo ? "1" : "0"; break; case ScriptType.space: if (space && script.prev == "0") { to_exec.Add(script); } script.prev = space ? "1" : "0"; break; case ScriptType.sunlight: if (sunlight && script.prev == "0") { to_exec.Add(script); } script.prev = sunlight ? "1" : "0"; break; case ScriptType.shadow: if (!sunlight && script.prev == "0") { to_exec.Add(script); } script.prev = !sunlight ? "1" : "0"; break; case ScriptType.power_high: if (power_high && script.prev == "0") { to_exec.Add(script); } script.prev = power_high ? "1" : "0"; break; case ScriptType.power_low: if (power_low && script.prev == "0") { to_exec.Add(script); } script.prev = power_low ? "1" : "0"; break; case ScriptType.rad_low: if (radiation_low && script.prev == "0") { to_exec.Add(script); } script.prev = radiation_low ? "1" : "0"; break; case ScriptType.rad_high: if (radiation_high && script.prev == "0") { to_exec.Add(script); } script.prev = radiation_high ? "1" : "0"; break; case ScriptType.linked: if (signal && script.prev == "0") { to_exec.Add(script); } script.prev = signal ? "1" : "0"; break; case ScriptType.unlinked: if (!signal && script.prev == "0") { to_exec.Add(script); } script.prev = !signal ? "1" : "0"; break; } } // if there are scripts to call if (to_exec.Count > 0) { // get list of devices // - we avoid creating it when there are no scripts to be executed, making its overall cost trivial var devices = Boot(v); // execute all scripts foreach (Script script in to_exec) { script.Execute(devices); } // show message to the user if (DB.Vessel(v).cfg_script) { Message.Post(Lib.BuildString("Script called on vessel <b>", v.vesselName, "</b>")); } } }
static void ProcessCurvedPanel(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule curved_panel, Part part_prefab, Vessel_Info info, Resource_Info ec, double elapsed_s) { // note: we assume deployed, this is a current limitation // if in sunlight if (info.sunlight > double.Epsilon) { // get values from module string transform_name = Lib.ReflectionValue <string>(curved_panel, "PanelTransformName"); float tot_rate = Lib.ReflectionValue <float>(curved_panel, "TotalEnergyRate"); // get components Transform[] components = part_prefab.FindModelTransforms(transform_name); if (components.Length == 0) { return; } // calculate normalized solar flux // note: this include fractional sunlight if integrated over orbit // note: this include atmospheric absorption if inside an atmosphere double norm_solar_flux = info.solar_flux / Sim.SolarFluxAtHome(); // calculate rate per component double rate = (double)tot_rate / (double)components.Length; // calculate world-space part rotation quaternion // note: a possible optimization here is to cache the transform lookup (unity was coded by monkeys) Quaternion rot = v.transform.rotation * p.rotation; // calculate output of all components double output = 0.0; foreach (Transform t in components) { output += rate // nominal rate per-component at 1 AU * norm_solar_flux // normalized solar flux at panel distance from sun * Math.Max(Vector3d.Dot(info.sun_dir, (rot * t.forward).normalized), 0.0); // cosine factor of component orientation } // produce EC ec.Produce(output * elapsed_s); } }