// 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); }
// 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"), "%)"); }
// 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) { // 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); } }
// implement greenhouse mechanics for unloaded vessels public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Greenhouse greenhouse, vessel_info info, vessel_resources resources, double elapsed_s) { // get protomodule data bool door_opened = Lib.Proto.GetBool(m, "door_opened"); double growth = Lib.Proto.GetDouble(m, "growth"); float lamps = Lib.Proto.GetFloat(m, "lamps"); double lighting = Lib.Proto.GetDouble(m, "lighting"); // if lamp is on if (lamps > float.Epsilon) { // get resource handler resource_info ec = resources.Info(vessel, "ElectricCharge"); // consume ec ec.Consume(greenhouse.ec_rate * lamps * elapsed_s * Reliability.Penalty(p, "Greenhouse", 2.0)); // shut down the light if there isn't enough ec // note: comparing against amount at previous simulation step if (ec.amount <= double.Epsilon) lamps = 0.0f; } // determine lighting conditions // note: we ignore sun direction for gameplay reasons: else the user must reorient the greenhouse as the planets dance over time // - natural light depend on: distance from sun, direct sunlight, door status // - artificial light depend on: lamps tweakable and ec available, door status lighting = info.solar_flux / Sim.SolarFluxAtHome() * (door_opened ? 1.0 : 0.0) + lamps; // if can use waste, and there is some lighting double waste_perc = 0.0; if (greenhouse.waste_name.Length > 0 && lighting > double.Epsilon) { // get resource handler resource_info waste = resources.Info(vessel, greenhouse.waste_name); // consume waste waste.Consume(greenhouse.waste_rate * elapsed_s); // determine waste bonus // note: comparing against amount from previous simulation step waste_perc = Math.Min(waste.amount / greenhouse.waste_rate, 1.0); } // determine growth bonus double growth_bonus = greenhouse.soil_bonus * (info.landed ? 1.0 : 0.0) + greenhouse.waste_bonus * waste_perc; // grow the crop double growing = greenhouse.growth_rate * (1.0 + growth_bonus) * lighting; growth += elapsed_s * growing; // if it is harvest time if (growth >= 1.0) { // reset growth growth = 0.0; // produce food resources.Produce(vessel, greenhouse.resource_name, greenhouse.harvest_size); // show a message to the user Message.Post(Lib.BuildString("On <color=FFFFFF>", vessel.vesselName, "</color> the crop harvest produced <color=FFFFFF>", greenhouse.harvest_size.ToString("F0"), " ", greenhouse.resource_name, "</color>")); // record first space harvest if (!info.landed && DB.Ready()) DB.Landmarks().space_harvest = 1; } // store data Lib.Proto.Set(m, "growth", growth); Lib.Proto.Set(m, "lamps", lamps); Lib.Proto.Set(m, "lighting", lighting); Lib.Proto.Set(m, "growth_diff", growing); }
// implement greenhouse mechanics public void FixedUpdate() { // set emissive intensity from lamp tweakable if (emissive_object.Length > 0) { foreach(var rdr in part.GetComponentsInChildren<UnityEngine.Renderer>()) { if (rdr.name == emissive_object) { rdr.material.SetColor("_EmissiveColor", new Color(lamps, lamps, lamps, 1.0f)); break; } } } // do nothing else in the editor if (HighLogic.LoadedSceneIsEditor) return; // 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; // when the greenhouse is assembled using KIS, the growth field is set to NaN // at that point it remain NaN forever, so we fix it // also, a report indicated -infinity in growth if (double.IsNaN(growth) || double.IsInfinity(growth)) growth = 0.0; // if lamp is on if (lamps > float.Epsilon) { // get resource handler resource_info ec = resources.Info(vessel, "ElectricCharge"); // consume ec ec.Consume(ec_rate * lamps * elapsed_s); // shut down the light if there isn't enough ec // note: comparing against amount at previous simulation step if (ec.amount <= double.Epsilon) lamps = 0.0f; } // determine lighting conditions // note: we ignore sun direction for gameplay reasons: else the user must reorient the greenhouse as the planets dance over time lighting = vi.solar_flux / Sim.SolarFluxAtHome() * (door_opened ? 1.0 : 0.0) + lamps; // if can use waste, and there is some lighting double waste_perc = 0.0; if (waste_name.Length > 0 && waste_rate > double.Epsilon && lighting > double.Epsilon) { // get resource handler resource_info waste = resources.Info(vessel, waste_name); // consume waste waste.Consume(waste_rate * elapsed_s); // determine waste bonus // note: comparing against amount from previous simulation step waste_perc = Math.Min(waste.amount / waste_rate, 1.0); } // determine growth bonus double growth_bonus = soil_bonus * (vi.landed ? 1.0 : 0.0) + waste_bonus * waste_perc; // grow the crop growing = growth_rate * (1.0 + growth_bonus) * lighting; growth += elapsed_s * growing; // if it is harvest time if (growth >= 1.0) { // reset growth growth = 0.0; // produce food resources.Produce(vessel, resource_name, harvest_size); // show a message to the user Message.Post(Lib.BuildString("On <color=FFFFFF>", vessel.vesselName, "</color> the crop harvest produced <color=FFFFFF>", harvest_size.ToString("F0"), " ", resource_name, "</color>")); // record first space harvest if (!vi.landed && DB.Ready()) DB.Landmarks().space_harvest = 1; } // set rmb ui status GrowthStatus = Lib.HumanReadablePerc(growth); LightStatus = Lib.HumanReadablePerc(lighting); WasteStatus = Lib.HumanReadablePerc(waste_perc); SoilStatus = vi.landed ? "yes" : "no"; TTAStatus = Lib.HumanReadableDuration(growing > double.Epsilon ? (1.0 - growth) / growing : 0.0); // enable/disable emergency harvest Events["EmergencyHarvest"].active = (growth >= 0.5); }