static void ProcessGenerator(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleGenerator generator, vessel_resources resources, double elapsed_s) { // play nice with BackgroundProcessing if (Kerbalism.detected_mods.BackgroundProcessing) { return; } // if active if (Lib.Proto.GetBool(m, "generatorIsActive")) { // get malfunction penalty double penalty = Reliability.Penalty(p, "Generator"); // create and commit recipe resource_recipe recipe = new resource_recipe(resource_recipe.converter_priority); foreach (var ir in generator.inputList) { recipe.Input(ir.name, ir.rate * elapsed_s); } foreach (var or in generator.outputList) { recipe.Output(or.name, or.rate * penalty * elapsed_s); } resources.Transform(recipe); } }
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: using hard-coded crew bonus values from the wiki because the module data make zero sense (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")) { // do nothing if full // note: comparing against previous amount if (resources.Info(v, harvester.ResourceName).level < harvester.FillAmount - double.Epsilon) { // get malfunction penalty double penalty = Reliability.Penalty(p, "Harvester"); // deduce crew bonus int exp_level = -1; if (harvester.UseSpecialistBonus) { foreach (ProtoCrewMember c in v.protoVessel.GetVesselCrew()) { exp_level = Math.Max(exp_level, c.trait == harvester.Specialty ? c.experienceLevel : -1); } } double exp_bonus = exp_level < 0 ? 1.0 : 5.0 + (double)exp_level * 4.0; // 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(resource_recipe.harvester_priority); foreach (var ir in harvester.inputList) { recipe.Input(ir.ResourceName, ir.Ratio * elapsed_s); } recipe.Output(harvester.ResourceName, abundance * harvester.Efficiency * exp_bonus * penalty * elapsed_s); resources.Transform(recipe); } } // undo stock behaviour by forcing last_update_time to now Lib.Proto.Set(m, "lastUpdateTime", Planetarium.GetUniversalTime()); } }
// apply deferred requests for a vessel and synchronize the new amount in the vessel public void Sync(Vessel v, double elapsed_s) { // execute all possible recipes bool executing = true; while (executing) { executing = false; for (int i = 0; i < recipes.Count; ++i) { resource_recipe recipe = recipes[i]; if (recipe.left > double.Epsilon) { executing |= recipe.Execute(v, this); } } } // forget the recipes recipes.Clear(); // apply all deferred requests and synchronize to vessel foreach (var pair in resources) { pair.Value.Sync(v, elapsed_s); } }
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: using hard-coded crew bonus values from the wiki because the module data make zero sense (DERP ALERT) // 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) // note: support PlanetaryBaseSystem converters // note: support NearFuture reactors // note: assume dump overboard is false for all outputs // 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 ? 1.0 : 5.0 + (double)exp_level * 4.0; // create and commit recipe resource_recipe recipe = new resource_recipe(); foreach (var ir in converter.inputList) { recipe.Input(ir.ResourceName, ir.Ratio * elapsed_s); } foreach (var or in converter.outputList) { recipe.Output(or.ResourceName, or.Ratio * exp_bonus * elapsed_s); } 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, Harvester harvester, double elapsed_s) { if (Lib.Proto.GetBool(m, "deployed") && Lib.Proto.GetBool(m, "running") && !Lib.Proto.GetBool(m, "starved")) { resource_recipe recipe = new resource_recipe(true); recipe.Input("ElectricCharge", harvester.ec_rate * elapsed_s); recipe.Output(harvester.resource, harvester.rate * elapsed_s); ResourceCache.Transform(v, recipe); } }
// implement scrubber mechanics public void FixedUpdate() { // do nothing in the editor if (HighLogic.LoadedSceneIsEditor) return; // do nothing until tech tree is ready if (!Lib.TechReady()) return; // deduce quality from technological level if necessary if (efficiency <= double.Epsilon) efficiency = DeduceEfficiency(); // get vessel info from the cache vessel_info vi = Cache.VesselInfo(vessel); // do nothing if vessel is invalid if (!vi.is_valid) return; // get resource cache vessel_resources resources = ResourceCache.Get(vessel); // get elapsed time double elapsed_s = Kerbalism.elapsed_s * vi.time_dilation; // if inside breathable atmosphere if (vi.breathable) { // produce oxygen from the intake resources.Produce(vessel, resource_name, intake_rate * elapsed_s); // set status Status = "Intake"; } // if outside breathable atmosphere and enabled else if (is_enabled) { // transform waste + ec into resource resource_recipe recipe = new resource_recipe(resource_recipe.scrubber_priority); recipe.Input(waste_name, co2_rate * elapsed_s); recipe.Input("ElectricCharge", ec_rate * elapsed_s); recipe.Output(resource_name, co2_rate * co2_ratio * efficiency * elapsed_s); resources.Transform(recipe); // set status Status = "Running"; } // if outside breathable atmosphere and disabled else { // set status Status = "Off"; } // add efficiency to status Status += Lib.BuildString(" (Efficiency: ", (efficiency * 100.0).ToString("F0"), "%)"); }
private static void ResourceUpdate(Vessel v, ProtoPartModuleSnapshot m, Harvester harvester, double elapsed_s) { if (Lib.Proto.GetBool(m, "deployed") && Lib.Proto.GetBool(m, "running") && Lib.Proto.GetString(m, "issue").Length == 0) { double abundance = SampleAbundance(v, harvester); if (abundance > Lib.Proto.GetDouble(m, "min_abundance")) { resource_recipe recipe = new resource_recipe(); recipe.Input("ElectricCharge", harvester.ec_rate * elapsed_s); recipe.Output(harvester.resource, harvester.rate * abundance * elapsed_s, true); ResourceCache.Transform(v, recipe); } } }
// implement recycler mechanics public void FixedUpdate() { // do nothing in the editor if (HighLogic.LoadedSceneIsEditor) return; // do nothing until tech tree is ready if (!Lib.TechReady()) return; if (use_efficiency) { // deduce quality from technological level if necessary if (efficiency <= double.Epsilon) efficiency = Scrubber.DeduceEfficiency(); } if (is_enabled) { // get vessel info from the cache vessel_info vi = Cache.VesselInfo(vessel); // do nothing if vessel is invalid if (!vi.is_valid) return; // get elapsed time double elapsed_s = Kerbalism.elapsed_s * vi.time_dilation; // get resource cache vessel_resources resources = ResourceCache.Get(vessel); // recycle EC+waste+filter into resource resource_recipe recipe = new resource_recipe(resource_recipe.scrubber_priority); recipe.Input(waste_name, waste_rate * elapsed_s); recipe.Input("ElectricCharge", ec_rate * elapsed_s); if (filter_name.Length > 0 && filter_rate > double.Epsilon) { recipe.Input(filter_name, filter_rate * elapsed_s); } recipe.Output(resource_name, waste_rate * waste_ratio * efficiency * elapsed_s); resources.Transform(recipe); // set status Status = "Running"; } else { Status = "Off"; } // add efficiency to status if (use_efficiency) Status += Lib.BuildString(" (Efficiency: ", (efficiency * 100.0).ToString("F0"), "%)"); }
public void FixedUpdate() { if (Lib.IsEditor()) { return; } if (deployed && running && !starved) { resource_recipe recipe = new resource_recipe(true); recipe.Input("ElectricCharge", ec_rate * Kerbalism.elapsed_s); recipe.Output(resource, rate * Kerbalism.elapsed_s); ResourceCache.Transform(vessel, recipe); } }
// implement recycler mechanics for unloaded vessels public static void BackgroundUpdate(Vessel vessel, ProtoPartModuleSnapshot m, Recycler recycler, vessel_resources resources, double elapsed_s) { if (Lib.Proto.GetBool(m, "is_enabled")) { // recycle EC+waste+filter into resource resource_recipe recipe = new resource_recipe(resource_recipe.scrubber_priority); recipe.Input(recycler.waste_name, recycler.waste_rate * elapsed_s); recipe.Input("ElectricCharge", recycler.ec_rate * elapsed_s); if (recycler.filter_name.Length > 0 && recycler.filter_rate > double.Epsilon) { recipe.Input(recycler.filter_name, recycler.filter_rate * elapsed_s); } recipe.Output(recycler.resource_name, recycler.waste_rate * recycler.waste_ratio * Lib.Proto.GetDouble(m, "efficiency", 1.0) * elapsed_s); resources.Transform(recipe); } }
static void ProcessGenerator(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleGenerator generator, vessel_resources resources, double elapsed_s) { // if active if (Lib.Proto.GetBool(m, "generatorIsActive")) { // create and commit recipe resource_recipe recipe = new resource_recipe(); foreach (ModuleResource ir in generator.resHandler.inputResources) { recipe.Input(ir.name, ir.rate * elapsed_s); } foreach (ModuleResource or in generator.resHandler.outputResources) { recipe.Output(or.name, or.rate * elapsed_s, true); } resources.Transform(recipe); } }
// implement scrubber mechanics for unloaded vessels public static void BackgroundUpdate(Vessel vessel, ProtoPartModuleSnapshot m, Scrubber scrubber, vessel_info info, vessel_resources resources, double elapsed_s) { // if inside breathable atmosphere if (info.breathable) { // produce oxygen from the intake resources.Produce(vessel, scrubber.resource_name, scrubber.intake_rate * elapsed_s); } // if outside breathable atmosphere and enabled else if (Lib.Proto.GetBool(m, "is_enabled")) { // transform waste + ec into resource resource_recipe recipe = new resource_recipe(resource_recipe.scrubber_priority); recipe.Input(scrubber.waste_name, scrubber.co2_rate * elapsed_s); recipe.Input("ElectricCharge", scrubber.ec_rate * elapsed_s); recipe.Output(scrubber.resource_name, scrubber.co2_rate * scrubber.co2_ratio * Lib.Proto.GetDouble(m, "efficiency", 0.5) * elapsed_s); resources.Transform(recipe); } }
public void Execute(Vessel v, vessel_info vi, vessel_resources resources, double elapsed_s) { // evaluate modifiers double k = Modifiers.evaluate(v, vi, resources, modifiers); // only execute processes if necessary if (k > double.Epsilon) { // prepare recipe resource_recipe recipe = new resource_recipe(); foreach (var p in inputs) { recipe.Input(p.Key, p.Value * k * elapsed_s); } foreach (var p in outputs) { recipe.Output(p.Key, p.Value * k * elapsed_s, dump.check(p.Key)); } resources.Transform(recipe); } }
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: using hard-coded crew bonus values from the wiki because the module data make zero sense (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")) { // 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 v.protoVessel.GetVesselCrew()) { exp_level = Math.Max(exp_level, c.trait == asteroid_drill.Specialty ? c.experienceLevel : -1); } } double exp_bonus = exp_level < 0 ? 1.0 : 5.0 + (double)exp_level * 4.0; // 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(resource_recipe.harvester_priority); recipe.Input("ElectricCharge", asteroid_drill.PowerConsumption * elapsed_s); recipe.Output(res_name, res_amount); 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()); } }
// used to sort recipes by priority public static int Compare(resource_recipe a, resource_recipe b) { return(a.priority < b.priority ? -1 : a.priority == b.priority ? 0 : 1); }
static void ProcessConverter(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Part part_prefab, int index, vessel_resources resources, double elapsed_s) { // note: support multiple resource converters // note: ignore stock temperature mechanic of converters // note: ignore autoshutdown // note: using hard-coded crew bonus values from the wiki because the module data make zero sense (DERP ALERT) // 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) // note: support PlanetaryBaseSystem converters // note: support NearFuture reactors // get converter var converter_prefabs = part_prefab.Modules.GetModules <ModuleResourceConverter>(); if (index >= converter_prefabs.Count) { return; } ModuleResourceConverter converter = converter_prefabs[index] as ModuleResourceConverter; // 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) { // get malfunction penalty double penalty = Reliability.Penalty(p, "Converter"); // deduce crew bonus int exp_level = -1; if (converter.UseSpecialistBonus) { foreach (ProtoCrewMember c in v.protoVessel.GetVesselCrew()) { exp_level = Math.Max(exp_level, c.trait == converter.Specialty ? c.experienceLevel : -1); } } double exp_bonus = exp_level < 0 ? 1.0 : 5.0 + (double)exp_level * 4.0; // create and commit recipe resource_recipe recipe = new resource_recipe(resource_recipe.converter_priority); foreach (var ir in converter.inputList) { recipe.Input(ir.ResourceName, ir.Ratio * elapsed_s); } foreach (var or in converter.outputList) { recipe.Output(or.ResourceName, or.Ratio * penalty * exp_bonus * elapsed_s); } resources.Transform(recipe); } // undo stock behaviour by forcing last_update_time to now Lib.Proto.Set(m, "lastUpdateTime", Planetarium.GetUniversalTime()); } }
// record deferred execution of a recipe public void Transform(resource_recipe recipe) { recipes.Add(recipe); }
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 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 and output_only is false else if (!output_only) { // 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); } // if output_only then do not consume input resource else { // simply produce (that is faster) resources.Produce(v, output, required); } } // degenerate: // - if the environment modifier is not telling to reset (by being zero) // - if the input threshold is reached if used // - if this rule is resource-less, or if there was not enough resource in the vessel if (input_threshold >= double.Epsilon) { if (res.amount >= double.Epsilon && res.capacity >= double.Epsilon) { trigger = res.amount / res.capacity >= input_threshold; } else { trigger = false; } } else { trigger = input.Length == 0 || res.amount <= double.Epsilon; } if (k > 0.0 && trigger) { 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); } }
// register deferred execution of a recipe (shortcut) public static void Transform(Vessel v, resource_recipe recipe) { Get(v).Transform(recipe); }
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; } }
public static void applyRules(Vessel v, vessel_info vi, vessel_data vd, vessel_resources resources, double elapsed_s) { // get crew List<ProtoCrewMember> crew = v.loaded ? v.GetVesselCrew() : v.protoVessel.GetVesselCrew(); // get breathable modifier double breathable = vi.breathable ? 0.0 : 1.0; // get temp diff modifier double temp_diff = v.altitude < 2000.0 && v.mainBody == FlightGlobals.GetHomeBody() ? 0.0 : Sim.TempDiff(vi.temperature); // for each rule foreach(Rule r in Kerbalism.rules) { // get resource handler resource_info res = r.resource_name.Length > 0 ? resources.Info(v, r.resource_name) : null; // if a resource is specified if (res != null) { // get data from db vmon_data vmon = DB.VmonData(v.id, r.name); // message obey user config bool show_msg = (r.resource_name == "ElectricCharge" ? vd.cfg_ec > 0 : vd.cfg_supply > 0); // no messages with no capacity if (res.capacity > double.Epsilon) { // manned/probe message variant uint variant = crew.Count > 0 ? 0 : 1u; // manage messages if (res.level <= double.Epsilon && vmon.message < 2) { if (r.empty_message.Length > 0 && show_msg) Message.Post(Severity.danger, Lib.ExpandMsg(r.empty_message, v, null, variant)); vmon.message = 2; } else if (res.level < r.low_threshold && vmon.message < 1) { if (r.low_message.Length > 0 && show_msg) Message.Post(Severity.warning, Lib.ExpandMsg(r.low_message, v, null, variant)); vmon.message = 1; } else if (res.level > r.low_threshold && vmon.message > 0) { if (r.refill_message.Length > 0 && show_msg) Message.Post(Severity.relax, Lib.ExpandMsg(r.refill_message, v, null, variant)); vmon.message = 0; } } } // for each crew foreach(ProtoCrewMember c in crew) { // get kerbal data kerbal_data kd = DB.KerbalData(c.name); // skip resque kerbals if (kd.resque == 1) continue; // skip disabled kerbals if (kd.disabled == 1) continue; // get supply data from db kmon_data kmon = DB.KmonData(c.name, r.name); // get product of all environment modifiers double k = 1.0; foreach(string modifier in r.modifier) { switch(modifier) { case "breathable": k *= breathable; break; case "temperature": k *= temp_diff; break; case "radiation": k *= vi.radiation * (1.0 - kd.shielding); break; case "qol": k /= QualityOfLife.Bonus(kd.living_space, kd.entertainment, vi.landed, vi.link.linked, vi.crew_count == 1); break; } } // if continuous double step; if (r.interval <= double.Epsilon) { // influence consumption by elapsed time step = elapsed_s; } // if interval-based else { // accumulate time kmon.time_since += elapsed_s; // determine number of steps step = Math.Floor(kmon.time_since / r.interval); // consume time kmon.time_since -= step * r.interval; // remember if a meal is consumed in this simulation step res.meal_consumed |= step > 0.99; } // if continuous, or if one or more intervals elapsed if (step > double.Epsilon) { // indicate if we must degenerate bool must_degenerate = true; // if there is a resource specified, and this isn't just a monitoring rule if (res != null && r.rate > double.Epsilon) { // determine amount of resource to consume double required = r.rate // rate per-second or per interval * k // product of environment modifiers * step; // seconds elapsed or number of steps // if there is no waste if (r.waste_name.Length == 0) { // simply consume (that is faster) res.Consume(required); } // if there is waste else { // transform resource into waste resource_recipe recipe = new resource_recipe(resource_recipe.rule_priority); recipe.Input(r.resource_name, required); recipe.Output(r.waste_name, required * r.waste_ratio); resources.Transform(recipe); } // reset degeneration when consumed, or when not required at all // note: evaluating amount from previous simulation step if (required <= double.Epsilon || res.amount > double.Epsilon) { // slowly recover instead of instant reset kmon.problem *= 1.0 / (1.0 + Math.Max(r.interval, 1.0) * step * 0.002); kmon.problem = Math.Max(kmon.problem, 0.0); // do not degenerate must_degenerate = false; } } // degenerate if this rule is resource-less, or if there was not enough resource in the vessel if (must_degenerate) { kmon.problem += r.degeneration // degeneration rate per-second or per-interval * k // product of environment modifiers * step // seconds elapsed or by number of steps * Variance(c, r.variance); // kerbal-specific variance } // determine message variant uint variant = vi.temperature < Settings.SurvivalTemperature ? 0 : 1u; // kill kerbal if necessary if (kmon.problem >= r.fatal_threshold) { if (r.fatal_message.Length > 0) Message.Post(r.breakdown ? Severity.breakdown : Severity.fatality, Lib.ExpandMsg(r.fatal_message, v, c, variant)); if (r.breakdown) { Kerbalism.Breakdown(v, c); kmon.problem = r.danger_threshold * 1.01; //< move back to danger threshold } else { Kerbalism.Kill(v, c); } } // show messages else if (kmon.problem >= r.danger_threshold && kmon.message < 2) { if (r.danger_message.Length > 0) Message.Post(Severity.danger, Lib.ExpandMsg(r.danger_message, v, c, variant)); kmon.message = 2; } else if (kmon.problem >= r.warning_threshold && kmon.message < 1) { if (r.warning_message.Length > 0) Message.Post(Severity.warning, Lib.ExpandMsg(r.warning_message, v, c, variant)); kmon.message = 1; } else if (kmon.problem < r.warning_threshold && kmon.message > 0) { if (r.relax_message.Length > 0) Message.Post(Severity.relax, Lib.ExpandMsg(r.relax_message, v, c, variant)); kmon.message = 0; } } } } }