// 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); }
public static double Evaluate(Vessel v, Vessel_Info vi, Vessel_Resources resources, List <string> modifiers) { double k = 1.0; foreach (string mod in modifiers) { switch (mod) { case "breathable": k *= vi.breathable ? 0.0 : 1.0; break; case "temperature": k *= vi.temp_diff; break; case "radiation": k *= vi.radiation; break; case "shielding": k *= 1.0 - vi.shielding; break; case "volume": k *= vi.volume; break; case "surface": k *= vi.surface; break; case "living_space": k /= vi.living_space; break; case "comfort": k /= vi.comforts.factor; break; case "pressure": k *= vi.pressure > Settings.PressureThreshold ? 1.0 : Settings.PressureFactor; break; case "poisoning": k *= vi.poisoning > Settings.PoisoningThreshold ? 1.0 : Settings.PoisoningFactor; break; case "per_capita": k /= (double)Math.Max(vi.crew_count, 1); break; default: k *= resources.Info(v, mod).amount; break; } } return(k); }
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); }
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 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; } } }
void ToEVA(GameEvents.FromToAction <Part, Part> data) { // get total crew in the origin vessel double tot_crew = (double)Lib.CrewCount(data.from.vessel) + 1.0; // get vessel resources handler Vessel_Resources resources = ResourceCache.Get(data.from.vessel); // setup supply resources capacity in the eva kerbal Profile.SetupEva(data.to); // for each resource in the kerbal for (int i = 0; i < data.to.Resources.Count; ++i) { // get the resource PartResource res = data.to.Resources[i]; // determine quantity to take double quantity = Math.Min(resources.Info(data.from.vessel, res.resourceName).amount / tot_crew, res.maxAmount); // remove resource from vessel quantity = data.from.RequestResource(res.resourceName, quantity); // add resource to eva kerbal data.to.RequestResource(res.resourceName, -quantity); } // show warning if there isn't monoprop in the eva suit string prop_name = Lib.EvaPropellantName(); if (Lib.Amount(data.to, prop_name) <= double.Epsilon && !Lib.Landed(data.from.vessel)) { Message.Post(Severity.danger, Lib.BuildString("There isn't any <b>", prop_name, "</b> in the EVA suit"), "Don't let the ladder go!"); } // turn off headlamp light, to avoid stock bug that show them for a split second when going on eva KerbalEVA kerbal = data.to.FindModuleImplementing <KerbalEVA>(); EVA.HeadLamps(kerbal, false); // execute script DB.Vessel(data.from.vessel).computer.Execute(data.from.vessel, ScriptType.eva_out); }
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 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); } }
// 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>")); } } }
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 ProcessAsteroidDrill(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleAsteroidDrill asteroid_drill, Vessel_Resources resources, double elapsed_s) { // 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) // if active if (Lib.Proto.GetBool(m, "IsActivated")) { // get asteroid data ProtoPartModuleSnapshot asteroid_info = null; ProtoPartModuleSnapshot asteroid_resource = null; foreach (ProtoPartSnapshot pp in v.protoVessel.protoPartSnapshots) { if (asteroid_info == null) { asteroid_info = pp.modules.Find(k => k.moduleName == "ModuleAsteroidInfo"); } if (asteroid_resource == null) { asteroid_resource = pp.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 = Lib.Proto.GetDouble(asteroid_info, "massThresholdVal"); double mass = Lib.Proto.GetDouble(asteroid_info, "currentMassVal"); double abundance = Lib.Proto.GetDouble(asteroid_resource, "abundance"); string res_name = Lib.Proto.GetString(asteroid_resource, "resourceName"); double res_density = PartResourceLibrary.Instance.GetDefinition(res_name).density; // if asteroid isn't depleted if (mass > mass_threshold && abundance > double.Epsilon) { // deduce crew bonus int exp_level = -1; if (asteroid_drill.UseSpecialistBonus) { foreach (ProtoCrewMember c in Lib.CrewList(v)) { if (c.experienceTrait.Effects.Find(k => k.Name == asteroid_drill.ExperienceEffect) != null) { exp_level = Math.Max(exp_level, c.experienceLevel); } } } double exp_bonus = exp_level < 0 ? asteroid_drill.EfficiencyBonus * asteroid_drill.SpecialistBonusBase : asteroid_drill.EfficiencyBonus * (asteroid_drill.SpecialistBonusBase + (asteroid_drill.SpecialistEfficiencyFactor * (exp_level + 1))); // determine resource extracted double res_amount = abundance * asteroid_drill.Efficiency * exp_bonus * elapsed_s; // transform EC into mined resource Resource_Recipe recipe = new Resource_Recipe(); recipe.Input("ElectricCharge", asteroid_drill.PowerConsumption * elapsed_s); recipe.Output(res_name, res_amount, true); resources.Transform(recipe); // if there was ec // note: comparing against amount in previous simulation step if (resources.Info(v, "ElectricCharge").amount > double.Epsilon) { // consume asteroid mass Lib.Proto.Set(asteroid_info, "currentMassVal", (mass - res_density * res_amount)); } } } // undo stock behaviour by forcing last_update_time to now Lib.Proto.Set(m, "lastUpdateTime", Planetarium.GetUniversalTime()); } }
static void ProcessHarvester(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleResourceHarvester harvester, Vessel_Resources resources, double elapsed_s) { // 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) // if active if (Lib.Proto.GetBool(m, "IsActivated")) { // do nothing if full // note: comparing against previous amount if (resources.Info(v, harvester.ResourceName).level < harvester.FillAmount - double.Epsilon) { // deduce crew bonus int exp_level = -1; if (harvester.UseSpecialistBonus) { foreach (ProtoCrewMember c in Lib.CrewList(v)) { if (c.experienceTrait.Effects.Find(k => k.Name == harvester.ExperienceEffect) != null) { exp_level = Math.Max(exp_level, c.experienceLevel); } } } double exp_bonus = exp_level < 0 ? harvester.EfficiencyBonus * harvester.SpecialistBonusBase : harvester.EfficiencyBonus * (harvester.SpecialistBonusBase + (harvester.SpecialistEfficiencyFactor * (exp_level + 1))); // detect amount of ore in the ground AbundanceRequest request = new AbundanceRequest { Altitude = v.altitude, BodyId = v.mainBody.flightGlobalsIndex, CheckForLock = false, Latitude = v.latitude, Longitude = v.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) { // create and commit recipe Resource_Recipe recipe = new Resource_Recipe(); foreach (var ir in harvester.inputList) { recipe.Input(ir.ResourceName, ir.Ratio * elapsed_s); } recipe.Output(harvester.ResourceName, abundance * harvester.Efficiency * exp_bonus * elapsed_s, true); resources.Transform(recipe); } } // undo stock behaviour by forcing last_update_time to now Lib.Proto.Set(m, "lastUpdateTime", Planetarium.GetUniversalTime()); } }
public static void BackgroundUpdate(Vessel v, ProtoPartModuleSnapshot m, Greenhouse g, Vessel_Info vi, Vessel_Resources resources, double elapsed_s) { // get protomodule data bool active = Lib.Proto.GetBool(m, "active"); double growth = Lib.Proto.GetDouble(m, "growth"); // if enabled and not ready for harvest if (active && growth < 0.99) { // get resource handler Resource_Info ec = resources.Info(v, "ElectricCharge"); // calculate natural and artificial lighting double natural = vi.solar_flux; double artificial = Math.Max(g.light_tolerance - natural, 0.0); // consume EC for the lamps, scaled by artificial light intensity if (artificial > double.Epsilon) { ec.Consume(g.ec_rate * (artificial / g.light_tolerance) * elapsed_s); } // reset artificial lighting if there is no ec left // note: comparing against amount in previous simulation step if (ec.amount <= double.Epsilon) { artificial = 0.0; } // execute recipe Resource_Recipe recipe = new Resource_Recipe(); foreach (ModuleResource input in g.resHandler.inputResources) { recipe.Input(input.name, input.rate * elapsed_s); } foreach (ModuleResource output in g.resHandler.outputResources) { recipe.Output(output.name, output.rate * elapsed_s, true); } resources.Transform(recipe); // determine environment conditions bool lighting = natural + artificial >= g.light_tolerance; bool pressure = g.pressure_tolerance <= double.Epsilon || vi.pressure >= g.pressure_tolerance; bool radiation = g.radiation_tolerance <= double.Epsilon || vi.radiation * (1.0 - vi.shielding) < g.radiation_tolerance; // determine inputs conditions // note: comparing against amounts in previous simulation step bool inputs = true; string missing_res = string.Empty; foreach (ModuleResource input in g.resHandler.inputResources) { if (resources.Info(v, input.name).amount <= double.Epsilon) { inputs = false; missing_res = input.name; break; } } // if growing if (lighting && pressure && radiation && inputs) { // increase growth growth += g.crop_rate * elapsed_s; growth = Math.Min(growth, 1.0); // notify the user when crop can be harvested if (growth >= 0.99) { Message.Post(Lib.BuildString("On <b>", v.vesselName, "</b> the crop is ready to be harvested")); growth = 1.0; } } // update time-to-harvest double tta = (1.0 - growth) / g.crop_rate; // update issues string issue = !inputs?Lib.BuildString("missing ", missing_res) : !lighting ? "insufficient lighting" : !pressure ? "insufficient pressure" : !radiation ? "excessive radiation" : string.Empty; // update protomodule data Lib.Proto.Set(m, "natural", natural); Lib.Proto.Set(m, "artificial", artificial); Lib.Proto.Set(m, "tta", tta); Lib.Proto.Set(m, "issue", issue); Lib.Proto.Set(m, "growth", growth); } }
public void FixedUpdate() { // do nothing in the editor if (Lib.IsEditor()) { return; } // if enabled and not ready for harvest if (active && growth < 0.99) { // get vessel info from the cache // - if the vessel is not valid (eg: flagged as debris) then solar flux will be 0 and landed false (but that's okay) Vessel_Info vi = Cache.VesselInfo(vessel); // get resource cache Vessel_Resources resources = ResourceCache.Get(vessel); Resource_Info ec = resources.Info(vessel, "ElectricCharge"); // deal with corner cases when greenhouse is assembled using KIS if (double.IsNaN(growth) || double.IsInfinity(growth)) { growth = 0.0; } // calculate natural and artificial lighting natural = vi.solar_flux; artificial = Math.Max(light_tolerance - natural, 0.0); // consume EC for the lamps, scaled by artificial light intensity if (artificial > double.Epsilon) { ec.Consume(ec_rate * (artificial / light_tolerance) * Kerbalism.elapsed_s); } // reset artificial lighting if there is no ec left // - comparing against amount in previous simulation step if (ec.amount <= double.Epsilon) { artificial = 0.0; } // execute recipe Resource_Recipe recipe = new Resource_Recipe(); foreach (ModuleResource input in resHandler.inputResources) { recipe.Input(input.name, input.rate * Kerbalism.elapsed_s); } foreach (ModuleResource output in resHandler.outputResources) { recipe.Output(output.name, output.rate * Kerbalism.elapsed_s, true); } resources.Transform(recipe); // determine environment conditions bool lighting = natural + artificial >= light_tolerance; bool pressure = pressure_tolerance <= double.Epsilon || vi.pressure >= pressure_tolerance; bool radiation = radiation_tolerance <= double.Epsilon || vi.radiation * (1.0 - vi.shielding) < radiation_tolerance; // determine input resources conditions // - comparing against amounts in previous simulation step bool inputs = true; string missing_res = string.Empty; foreach (ModuleResource input in resHandler.inputResources) { if (resources.Info(vessel, input.name).amount <= double.Epsilon) { inputs = false; missing_res = input.name; break; } } // if growing if (lighting && pressure && radiation && inputs) { // increase growth growth += crop_rate * Kerbalism.elapsed_s; growth = Math.Min(growth, 1.0); // notify the user when crop can be harvested if (growth >= 0.99) { Message.Post(Lib.BuildString("On <b>", vessel.vesselName, "</b> the crop is ready to be harvested")); growth = 1.0; } } // update time-to-harvest tta = (1.0 - growth) / crop_rate; // update issues issue = !inputs?Lib.BuildString("missing ", missing_res) : !lighting ? "insufficient lighting" : !pressure ? "insufficient pressure" : !radiation ? "excessive radiation" : string.Empty; } }
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 // As far as I know, Simple_boiloff consumes fuel and EC only if the fuel is on the Fuel List (previously added in Game Load) // The test that I did, I was able to enable to "Enable Cooling" only when fuel was into List<BoiloffFuel> fuels // get fuel name: FuelName in Simple_Boiloff is always NULL, this Get doesn't make sense, but I will leave here. string fuel_name = Lib.ReflectionValue <string>(simple_boiloff, "FuelName"); // get resource handlers Resource_Info ec = resources.Info(v, "ElectricCharge"); // if fuel_name is null as expected, this would cause error if (string.IsNullOrEmpty(fuel_name)) { // Take name from fuel list inside part System.Collections.IList fuelList = Lib.ReflectionValue <System.Collections.IList>(simple_boiloff, "fuels"); foreach (var item in fuelList) { fuel_name = (string)item.GetType().GetField("fuelName").GetValue(item); // if fuel_name still null, do anything if (fuel_name == null) { continue; } 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) { // Try find resource "fuel_name" into PartResources ProtoPartResourceSnapshot protoPartResource = p.resources.Find(k => k.resourceName == fuel_name); // If part doesn't have the fuel, do anything. if (protoPartResource == null) { continue; } // get capacity in the part double capacity = protoPartResource.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); } } else { 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) { // Try find resource "fuel_name" into PartResources ProtoPartResourceSnapshot protoPartResource = p.resources.Find(k => k.resourceName == fuel_name); // If part doesn't have the fuel, do anything. if (protoPartResource == null) { return; } // get capacity in the part double capacity = protoPartResource.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); } }