/// <summary> /// Call ResourceUpdate on all part modules that have that method /// </summary> public void ResourceUpdate(VesselResources resources, double elapsed_s) { // only do this for loaded vessels. unloaded vessels will be handled in Background.cs if (!Vessel.loaded) { return; } if (resourceUpdateDelegates == null) { resourceUpdateDelegates = new List <ResourceUpdateDelegate>(); foreach (var part in Vessel.parts) { foreach (var module in part.Modules) { if (!module.isEnabled) { continue; } var resourceUpdateDelegate = ResourceUpdateDelegate.Instance(module); if (resourceUpdateDelegate != null) { resourceUpdateDelegates.Add(resourceUpdateDelegate); } } } } if (resourceUpdateDelegates.Count == 0) { return; } List <ResourceInfo> allResources = resources.GetAllResources(Vessel); // there might be some performance to be gained by caching the list of all resource Dictionary <string, double> availableResources = new Dictionary <string, double>(); foreach (var ri in allResources) { availableResources[ri.ResourceName] = ri.Amount; } List <KeyValuePair <string, double> > resourceChangeRequests = new List <KeyValuePair <string, double> >(); foreach (var resourceUpdateDelegate in resourceUpdateDelegates) { resourceChangeRequests.Clear(); string title = resourceUpdateDelegate.invoke(availableResources, resourceChangeRequests); foreach (var rc in resourceChangeRequests) { if (rc.Value > 0) { resources.Produce(Vessel, rc.Key, rc.Value * elapsed_s, title); } if (rc.Value < 0) { resources.Consume(Vessel, rc.Key, -rc.Value * elapsed_s, title); } } } }
private void ExecuteRecipe(double k, VesselResources resources, double elapsed_s, ResourceRecipe recipe) { // only execute processes if necessary if (Math.Abs(k) < double.Epsilon) { return; } foreach (var p in inputs) { recipe.AddInput(p.Key, p.Value * k * elapsed_s); } foreach (var p in outputs) { recipe.AddOutput(p.Key, p.Value * k * elapsed_s, dump.Check(p.Key)); } foreach (var p in cures) { // TODO this assumes that the cure modifies always put the resource first // works: modifier = _SickbayRDU,zerog works // fails: modifier = zerog,_SickbayRDU recipe.AddCure(p.Key, p.Value * k * elapsed_s, modifiers[0]); } resources.AddRecipe(recipe); }
public static void TelemetryPanel(this Panel p, Vessel v) { // avoid corner-case when this is called in a lambda after scene changes v = FlightGlobals.FindVessel(v.id); // if vessel doesn't exist anymore, leave the panel empty if (v == null) { return; } // get vessel data VesselData vd = v.KerbalismData(); // if not a valid vessel, leave the panel empty if (!vd.IsSimulated) { return; } // set metadata p.Title(Lib.BuildString(Lib.Ellipsis(v.vesselName, Styles.ScaleStringLength(20)), " ", Lib.Color(Local.TELEMETRY_title, Lib.Kolor.LightGrey))); //"TELEMETRY" p.Width(Styles.ScaleWidthFloat(355.0f)); p.paneltype = Panel.PanelType.telemetry; // time-out simulation if (p.Timeout(vd)) { return; } // get resources VesselResources resources = ResourceCache.Get(v); // get crew var crew = Lib.CrewList(v); // draw the content Render_crew(p, crew); if (Features.Science) { Render_science(p, v, vd); } Render_greenhouse(p, vd); Render_supplies(p, v, vd, resources); Render_habitat(p, v, vd); Render_environment(p, v, vd); // collapse eva kerbal sections into one if (v.isEVA) { p.Collapse(Local.TELEMETRY_EVASUIT); //"EVA SUIT" } }
public void Execute(Vessel v, VesselData vd, VesselResources resources, double elapsed_s) { // evaluate modifiers // if a given PartModule has a larger than 1 capacity for a process, then the multiplication happens here // remember that when a process is enabled the units of process are stored in the PartModule as a pseudo-resource double k = Modifiers.Evaluate(v, vd, resources, modifiers); ResourceRecipe recipe = new ResourceRecipe(name); ExecuteRecipe(k, resources, elapsed_s, recipe); }
void ToEVA(GameEvents.FromToAction <Part, Part> data) { OnVesselModified(data.from.vessel); OnVesselModified(data.to.vessel); // get total crew in the origin vessel double tot_crew = Lib.CrewCount(data.from.vessel) + 1.0; // get vessel resources handler VesselResources resources = ResourceCache.Get(data.from.vessel); // setup supply resources capacity in the eva kerbal Profile.SetupEva(data.to); string evaPropName = Lib.EvaPropellantName(); // 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]; // eva prop is handled differently if (res.resourceName == evaPropName) { continue; } double quantity = Math.Min(resources.GetResource(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); } // Airlock loss resources.Consume(data.from.vessel, "Nitrogen", Settings.LifeSupportAtmoLoss, ResourceBroker.Generic); KerbalEVA kerbal = data.to.FindModuleImplementing <KerbalEVA>(); // turn off headlamp light, to avoid stock bug that show them for a split second when going on eva EVA.HeadLamps(kerbal, false); // execute script data.from.vessel.KerbalismData().computer.Execute(data.from.vessel, ScriptType.eva_out); // Start a coroutine for doing eva propellant resource transfers once the kerbal EVA is started (this is too early here) data.to.StartCoroutine(PostEVATweaks(data.from, data.to, evaPropName)); }
public void Execute(Vessel v, VesselData vd, VesselResources resources) { // get crew List <ProtoCrewMember> crew = Lib.CrewList(v); // get resource handler ResourceInfo res = resources.GetResource(v, resource); // get data from db SupplyData sd = v.KerbalismData().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; } } }
/// <summary>Execute all recipes and record deferred consumption/production for inputs/ouputs</summary> public static void ExecuteRecipes(Vessel v, VesselResources resources, List <ResourceRecipe> recipes) { bool executing = true; while (executing) { executing = false; for (int i = 0; i < recipes.Count; ++i) { ResourceRecipe recipe = recipes[i]; if (recipe.left > double.Epsilon) { executing |= recipe.ExecuteRecipeStep(v, resources); } } } }
/// <summary> Return the VesselResources handler for this vessel </summary> public static VesselResources Get(Vessel v) { // try to get existing entry if any VesselResources entry; if (entries.TryGetValue(v.id, out entry)) { return(entry); } // create new entry entry = new VesselResources(); // remember new entry entries.Add(v.id, entry); // return new entry return(entry); }
public static void Execute(Vessel v, VesselData vd, VesselResources resources, double elapsed_s) { // execute all supplies foreach (Supply supply in supplies) { // this will just show warning messages if resources get low supply.Execute(v, vd, resources); } // execute all rules foreach (Rule rule in rules) { rule.Execute(v, vd, resources, elapsed_s); } // execute all processes foreach (Process process in processes) { process.Execute(v, vd, resources, elapsed_s); } }
void ToEVA(GameEvents.FromToAction <Part, Part> data) { OnVesselModified(data.from.vessel); OnVesselModified(data.to.vessel); // get total crew in the origin vessel double tot_crew = Lib.CrewCount(data.from.vessel) + 1.0; // get vessel resources handler VesselResources resources = ResourceCache.Get(data.from.vessel); // setup supply resources capacity in the eva kerbal Profile.SetupEva(data.to); String prop_name = Lib.EvaPropellantName(); // 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]; // eva prop is handled differently if (res.resourceName == prop_name) { continue; } double quantity = Math.Min(resources.GetResource(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); } // take as much of the propellant as possible. just imagine: there are 1.3 units left, and 12 occupants // in the ship. you want to send out an engineer to fix the chemical plant that produces monoprop, // and have to get from one end of the station to the other with just 0.1 units in the tank... // nope. double evaPropQuantity = data.from.RequestResource(prop_name, Lib.EvaPropellantCapacity()); // We can't just add the monoprop here, because that doesn't always work. It might be related // to the fact that stock KSP wants to add 5 units of monoprop to new EVAs. Instead of fighting KSP here, // we just let it do it's thing and set our amount later in EVA.cs - which seems to work just fine. // don't put that into Cache.VesselInfo because that can be deleted before we get there Cache.SetVesselObjectsCache(data.to.vessel, "eva_prop", evaPropQuantity); // Airlock loss resources.Consume(data.from.vessel, "Nitrogen", Settings.LifeSupportAtmoLoss, ResourceBroker.Generic); // show warning if there is little or no EVA propellant in the suit if (evaPropQuantity <= 0.05 && !Lib.Landed(data.from.vessel)) { Message.Post(Severity.danger, Local.CallBackMsg_EvaNoMP.Format("<b>" + prop_name + "</b>"), Local.CallBackMsg_EvaNoMP2); //Lib.BuildString("There isn't any <<1>> 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 data.from.vessel.KerbalismData().computer.Execute(data.from.vessel, ScriptType.eva_out); }
public static void BackgroundUpdate(Vessel v, ProtoPartModuleSnapshot m, Greenhouse g, VesselData vd, VesselResources 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 ResourceInfo ec = resources.GetResource(v, "ElectricCharge"); // calculate natural and artificial lighting double natural = vd.EnvSolarFluxTotal; 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, ResourceBroker.Greenhouse); } // 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 ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.Greenhouse); foreach (ModuleResource input in g.resHandler.inputResources) //recipe.Input(input.name, input.rate * elapsed_s); { // WasteAtmosphere is primary combined input if (g.WACO2 && input.name == "WasteAtmosphere") { recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * elapsed_s, "CarbonDioxide"); } // CarbonDioxide is secondary combined input else if (g.WACO2 && input.name == "CarbonDioxide") { recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * elapsed_s, ""); } // if atmosphere is breathable disable WasteAtmosphere / CO2 else if (!g.WACO2 && (input.name == "CarbonDioxide" || input.name == "WasteAtmosphere")) { recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate, ""); } else { recipe.AddInput(input.name, input.rate * elapsed_s); } } foreach (ModuleResource output in g.resHandler.outputResources) { // if atmosphere is breathable disable Oxygen if (output.name == "Oxygen") { recipe.AddOutput(output.name, vd.EnvBreathable ? 0.0 : output.rate * elapsed_s, true); } else { recipe.AddOutput(output.name, output.rate * elapsed_s, true); } } resources.AddRecipe(recipe); // determine environment conditions bool lighting = natural + artificial >= g.light_tolerance; bool pressure = g.pressure_tolerance <= 0 || vd.Pressure >= g.pressure_tolerance; bool radiation = g.radiation_tolerance <= 0 || vd.EnvRadiation * (1.0 - vd.Shielding) < g.radiation_tolerance; // determine inputs conditions // note: comparing against amounts in previous simulation step bool inputs = true; string missing_res = string.Empty; bool dis_WACO2 = false; foreach (ModuleResource input in g.resHandler.inputResources) { // combine WasteAtmosphere and CO2 if both exist if (input.name == "WasteAtmosphere" || input.name == "CarbonDioxide") { if (dis_WACO2 || vd.EnvBreathable) { continue; // skip if already checked or atmosphere is breathable } if (g.WACO2) { if (resources.GetResource(v, "WasteAtmosphere").Amount <= double.Epsilon && resources.GetResource(v, "CarbonDioxide").Amount <= double.Epsilon) { inputs = false; missing_res = "CarbonDioxide"; break; } dis_WACO2 = true; continue; } } if (resources.GetResource(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(Local.harvestedready_msg.Format("<b>" + v.vesselName + "</b>")); //Lib.BuildString("On <<1>> 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(Local.Greenhouse_resoucesmissing.Format(missing_res)) //"missing ", missing_res : !lighting ? Local.Greenhouse_issue1 //"insufficient lighting" : !pressure ? Local.Greenhouse_issue2 //"insufficient pressure" : !radiation ? Local.Greenhouse_issue3 //"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) VesselData vd = vessel.KerbalismData(); // get resource cache VesselResources resources = ResourceCache.Get(vessel); ResourceInfo ec = resources.GetResource(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 = vd.EnvSolarFluxTotal; 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, ResourceBroker.Greenhouse); } // 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 ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.Greenhouse); foreach (ModuleResource input in resHandler.inputResources) { // WasteAtmosphere is primary combined input if (WACO2 && input.name == "WasteAtmosphere") { recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * Kerbalism.elapsed_s, "CarbonDioxide"); } // CarbonDioxide is secondary combined input else if (WACO2 && input.name == "CarbonDioxide") { recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * Kerbalism.elapsed_s, ""); } // if atmosphere is breathable disable WasteAtmosphere / CO2 else if (!WACO2 && (input.name == "CarbonDioxide" || input.name == "WasteAtmosphere")) { recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate, ""); } else { recipe.AddInput(input.name, input.rate * Kerbalism.elapsed_s); } } foreach (ModuleResource output in resHandler.outputResources) { // if atmosphere is breathable disable Oxygen if (output.name == "Oxygen") { recipe.AddOutput(output.name, vd.EnvBreathable ? 0.0 : output.rate * Kerbalism.elapsed_s, true); } else { recipe.AddOutput(output.name, output.rate * Kerbalism.elapsed_s, true); } } resources.AddRecipe(recipe); // determine environment conditions bool lighting = natural + artificial >= light_tolerance; bool pressure = pressure_tolerance <= double.Epsilon || vd.Pressure >= pressure_tolerance; bool radiation = radiation_tolerance <= double.Epsilon || (1.0 - vd.Shielding) * vd.EnvHabitatRadiation < radiation_tolerance; // determine input resources conditions // - comparing against amounts in previous simulation step bool inputs = true; string missing_res = string.Empty; bool dis_WACO2 = false; foreach (ModuleResource input in resHandler.inputResources) { // combine WasteAtmosphere and CO2 if both exist if (input.name == "WasteAtmosphere" || input.name == "CarbonDioxide") { if (dis_WACO2 || vd.EnvBreathable) { continue; // skip if already checked or atmosphere is breathable } if (WACO2) { if (resources.GetResource(vessel, "WasteAtmosphere").Amount <= double.Epsilon && resources.GetResource(vessel, "CarbonDioxide").Amount <= double.Epsilon) { inputs = false; missing_res = "CarbonDioxide"; break; } dis_WACO2 = true; continue; } } if (resources.GetResource(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(Local.harvestedready_msg.Format("<b>" + vessel.vesselName + "</b>")); //Lib.BuildString("On <<1>> 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(Local.Greenhouse_resoucesmissing.Format(missing_res)) //"missing <<1>>" : !lighting ? Local.Greenhouse_issue1 //"insufficient lighting" : !pressure ? Local.Greenhouse_issue2 //"insufficient pressure" : !radiation ? Local.Greenhouse_issue3 //"excessive radiation" : string.Empty; } }
static void Render_supplies(Panel p, Vessel v, VesselData vd, VesselResources resources) { int supplies = 0; // for each supply foreach (Supply supply in Profile.supplies) { // get resource info ResourceInfo res = resources.GetResource(v, supply.resource); // only show estimate if the resource is present if (res.Capacity <= 1e-10) { continue; } // render panel title, if not done already if (supplies == 0) { p.AddSection(Local.TELEMETRY_SUPPLIES); //"SUPPLIES" } // determine label var resource = PartResourceLibrary.Instance.resourceDefinitions[supply.resource]; string label = Lib.SpacesOnCaps(resource.displayName).ToLower(); StringBuilder sb = new StringBuilder(); sb.Append("<align=left />"); if (res.AverageRate != 0.0) { sb.Append(Lib.Color(res.AverageRate > 0.0, Lib.BuildString("+", Lib.HumanReadableRate(Math.Abs(res.AverageRate))), Lib.Kolor.PosRate, Lib.BuildString("-", Lib.HumanReadableRate(Math.Abs(res.AverageRate))), Lib.Kolor.NegRate, true)); } else { sb.Append("<b>"); sb.Append(Local.TELEMETRY_nochange); //no change sb.Append("</b>"); } if (res.AverageRate < 0.0 && res.Level < 0.0001) { sb.Append(" <i>"); sb.Append(Local.TELEMETRY_empty); //(empty) sb.Append("</i>"); } else if (res.AverageRate > 0.0 && res.Level > 0.9999) { sb.Append(" <i>"); sb.Append(Local.TELEMETRY_full); //(full) sb.Append("</i>"); } else { sb.Append(" "); // spaces to prevent alignement issues } sb.Append("\t"); sb.Append(res.Amount.ToString("F1")); sb.Append("/"); sb.Append(res.Capacity.ToString("F1")); sb.Append(" ("); sb.Append(res.Level.ToString("P0")); sb.Append(")"); List <SupplyData.ResourceBrokerRate> brokers = vd.Supply(supply.resource).ResourceBrokers; if (brokers.Count > 0) { sb.Append("\n<b>------------ \t------------</b>"); foreach (SupplyData.ResourceBrokerRate rb in brokers) { sb.Append("\n"); sb.Append(Lib.Color(rb.rate > 0.0, Lib.BuildString("+", Lib.HumanReadableRate(Math.Abs(rb.rate)), " "), Lib.Kolor.PosRate, // spaces to mitigate alignement issues Lib.BuildString("-", Lib.HumanReadableRate(Math.Abs(rb.rate)), " "), Lib.Kolor.NegRate, // spaces to mitigate alignement issues true)); sb.Append("\t"); sb.Append(rb.broker.Title); } } string rate_tooltip = sb.ToString(); // finally, render resource supply p.AddContent(label, Lib.HumanReadableDuration(res.DepletionTime()), rate_tooltip); ++supplies; } }
// call scripts automatically when conditions are met public void Automate(Vessel v, VesselData vd, VesselResources resources) { // do nothing if automation is disabled if (!Features.Automation) { return; } // get current states ResourceInfo ec = resources.GetResource(v, "ElectricCharge"); bool sunlight = !vd.EnvInFullShadow; bool power_low = ec.Level < 0.2; bool power_high = ec.Level > 0.8; bool radiation_low = vd.EnvRadiation < 0.000005552; //< 0.02 rad/h bool radiation_high = vd.EnvRadiation > 0.00001388; //< 0.05 rad/h bool signal = vd.Connection.linked; bool drive_full = vd.DrivesFreeSpace < double.MaxValue && (vd.DrivesFreeSpace / vd.DrivesCapacity < 0.15); bool drive_empty = vd.DrivesFreeSpace >= double.MaxValue || (vd.DrivesFreeSpace / vd.DrivesCapacity > 0.9); // 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; case ScriptType.drive_full: if (drive_full && script.prev == "0") { to_exec.Add(script); } script.prev = drive_full ? "1" : "0"; break; case ScriptType.drive_empty: if (drive_empty && script.prev == "0") { to_exec.Add(script); } script.prev = drive_empty ? "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 List <Device> devices = GetModuleDevices(v); // execute all scripts foreach (Script script in to_exec) { script.Execute(devices); } // show message to the user if (v.KerbalismData().cfg_script) { Message.Post(Lib.BuildString("Script called on vessel <b>", v.vesselName, "</b>")); } } }
///<summary> Modifiers Evaluate method used for the Monitors background and current vessel simulation </summary> public static double Evaluate(Vessel v, VesselData vd, VesselResources resources, List <string> modifiers) { double k = 1.0; foreach (string mod in modifiers) { switch (mod) { case "zerog": k *= vd.EnvZeroG ? 1.0 : 0.0; break; case "landed": k *= vd.EnvLanded ? 1.0 : 0.0; break; case "breathable": k *= vd.EnvBreathable ? 1.0 : 0.0; break; case "non_breathable": k *= vd.EnvBreathable ? 0.0 : 1.0; break; case "temperature": k *= vd.EnvTempDiff; break; case "radiation": k *= vd.EnvHabitatRadiation; break; case "shielding": k *= 1.0 - vd.Shielding; break; case "volume": k *= vd.Volume; break; case "surface": k *= vd.Surface; break; case "living_space": k /= vd.LivingSpace; break; case "comfort": k /= vd.Comforts.factor; break; case "pressure": k *= vd.Pressure > Settings.PressureThreshold ? 1.0 : Settings.PressureFactor; break; case "poisoning": k *= vd.Poisoning > Settings.PoisoningThreshold ? 1.0 : Settings.PoisoningFactor; break; case "per_capita": k /= (double)Math.Max(vd.CrewCount, 1); break; default: k *= resources.GetResource(v, mod).Amount; break; } } return(k); }
static void ProcessConverter(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleResourceConverter converter, VesselResources resources, double elapsed_s) { // note: ignore stock temperature mechanic of converters // note: ignore auto shutdown // note: non-mandatory resources 'dynamically scale the ratios', that is exactly what mandatory resources do too (DERP ALERT) // note: 'undo' stock behavior 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) { ResourceInfo res = resources.GetResource(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 ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.StockConverter); foreach (var ir in converter.inputList) { recipe.AddInput(ir.ResourceName, ir.Ratio * exp_bonus * elapsed_s); } foreach (var or in converter.outputList) { recipe.AddOutput(or.ResourceName, or.Ratio * exp_bonus * elapsed_s, or.DumpExcess); } resources.AddRecipe(recipe); } // undo stock behavior by forcing last_update_time to now Lib.Proto.Set(m, "lastUpdateTime", Planetarium.GetUniversalTime()); } }
public static void Update(Vessel v, VesselData vd, VesselResources resources, double elapsed_s) { if (!Lib.IsVessel(v)) { return; } // get most used resource handlers ResourceInfo ec = resources.GetResource(v, "ElectricCharge"); List <ResourceInfo> allResources = resources.GetAllResources(v); Dictionary <string, double> availableResources = new Dictionary <string, double>(); foreach (var ri in allResources) { availableResources[ri.ResourceName] = ri.Amount; } List <KeyValuePair <string, double> > resourceChangeRequests = new List <KeyValuePair <string, double> >(); foreach (var e in Background_PMs(v)) { switch (e.type) { case Module_type.Reliability: Reliability.BackgroundUpdate(v, e.p, e.m, e.module_prefab as Reliability, elapsed_s); break; case Module_type.Experiment: (e.module_prefab as Experiment).BackgroundUpdate(v, vd, e.m, ec, resources, elapsed_s); break; // experiments use the prefab as a singleton instead of a static method case Module_type.Greenhouse: Greenhouse.BackgroundUpdate(v, e.m, e.module_prefab as Greenhouse, vd, resources, elapsed_s); break; case Module_type.GravityRing: GravityRing.BackgroundUpdate(v, e.p, e.m, e.module_prefab as GravityRing, ec, elapsed_s); break; case Module_type.Harvester: Harvester.BackgroundUpdate(v, e.m, e.module_prefab as Harvester, elapsed_s); break; // Kerbalism ground and air harvester module case Module_type.Laboratory: Laboratory.BackgroundUpdate(v, e.p, e.m, e.module_prefab as Laboratory, ec, elapsed_s); break; case Module_type.Command: ProcessCommand(v, e.p, e.m, e.module_prefab as ModuleCommand, resources, elapsed_s); break; case Module_type.Generator: ProcessGenerator(v, e.p, e.m, e.module_prefab as ModuleGenerator, resources, elapsed_s); break; case Module_type.Converter: ProcessConverter(v, e.p, e.m, e.module_prefab as ModuleResourceConverter, resources, elapsed_s); break; case Module_type.Drill: ProcessDrill(v, e.p, e.m, e.module_prefab as ModuleResourceHarvester, resources, elapsed_s); break; // Stock ground harvester module // case Module_type.AsteroidDrill: ProcessAsteroidDrill(v, e.p, e.m, e.module_prefab as ModuleAsteroidDrill, resources, elapsed_s); break; // Stock asteroid harvester module case Module_type.StockLab: ProcessStockLab(v, e.p, e.m, e.module_prefab as ModuleScienceConverter, ec, elapsed_s); break; case Module_type.Light: ProcessLight(v, e.p, e.m, e.module_prefab as ModuleLight, ec, elapsed_s); break; case Module_type.Scanner: KerbalismScansat.BackgroundUpdate(v, e.p, e.m, e.module_prefab as KerbalismScansat, e.part_prefab, vd, ec, elapsed_s); break; case Module_type.FissionGenerator: ProcessFissionGenerator(v, e.p, e.m, e.module_prefab, ec, elapsed_s); break; case Module_type.RadioisotopeGenerator: ProcessRadioisotopeGenerator(v, e.p, e.m, e.module_prefab, ec, elapsed_s); break; case Module_type.CryoTank: ProcessCryoTank(v, e.p, e.m, e.module_prefab, resources, ec, elapsed_s); break; case Module_type.FNGenerator: ProcessFNGenerator(v, e.p, e.m, e.module_prefab, ec, elapsed_s); break; case Module_type.SolarPanelFixer: SolarPanelFixer.BackgroundUpdate(v, e.m, e.module_prefab as SolarPanelFixer, vd, ec, elapsed_s); break; case Module_type.KerbalismSentinel: KerbalismSentinel.BackgroundUpdate(v, e.m, e.module_prefab as KerbalismSentinel, vd, ec, elapsed_s); break; case Module_type.APIModule: ProcessApiModule(v, e.p, e.m, e.part_prefab, e.module_prefab, resources, availableResources, resourceChangeRequests, elapsed_s); break; } } }
static void ProcessCommand(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleCommand command, VesselResources resources, double elapsed_s) { // do not consume if this is a MCM with no crew // rationale: for consistency, the game doesn't consume resources for MCM without crew in loaded vessels // this make some sense: you left a vessel with some battery and nobody on board, you expect it to not consume EC if (command.minimumCrew == 0 || p.protoModuleCrew.Count > 0) { // for each input resource foreach (ModuleResource ir in command.resHandler.inputResources) { // consume the resource resources.Consume(v, ir.name, ir.rate * elapsed_s, ResourceBroker.Command); } } }
private static void ProcessApiModule(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Part part_prefab, PartModule module_prefab, VesselResources resources, Dictionary <string, double> availableResources, List <KeyValuePair <string, double> > resourceChangeRequests, double elapsed_s) { resourceChangeRequests.Clear(); try { string title = BackgroundDelegate.Instance(module_prefab).invoke(v, p, m, module_prefab, part_prefab, availableResources, resourceChangeRequests, elapsed_s); foreach (var cr in resourceChangeRequests) { if (cr.Value > 0) { resources.Produce(v, cr.Key, cr.Value * elapsed_s, ResourceBroker.GetOrCreate(title)); } else if (cr.Value < 0) { resources.Consume(v, cr.Key, -cr.Value * elapsed_s, ResourceBroker.GetOrCreate(title)); } } } catch (Exception ex) { Lib.Log("BackgroundUpdate in PartModule " + module_prefab.moduleName + " excepted: " + ex.Message + "\n" + ex.ToString()); } }
static void ProcessCryoTank(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule cryotank, VesselResources resources, ResourceInfo ec, double elapsed_s) { // Note. Currently background simulation of Cryotanks has an irregularity in that boiloff of a fuel type in a tank removes resources from all tanks // but at least some simulation is better than none ;) // get list of fuels, do nothing if no fuels IList fuels = Lib.ReflectionValue <IList>(cryotank, "fuels"); if (fuels == null) { return; } // is cooling available, note: comparing against amount in previous simulation step bool available = (Lib.Proto.GetBool(m, "CoolingEnabled") && ec.Amount > double.Epsilon); // get cooling cost double cooling_cost = Lib.ReflectionValue <float>(cryotank, "CoolingCost"); string fuel_name = ""; double amount = 0.0; double total_cost = 0.0; double boiloff_rate = 0.0; foreach (var item in fuels) { fuel_name = Lib.ReflectionValue <string>(item, "fuelName"); // if fuel_name is null, don't do anything if (fuel_name == null) { continue; } //get fuel resource ResourceInfo fuel = resources.GetResource(v, fuel_name); // if there is some fuel // note: comparing against amount in previous simulation step if (fuel.Amount > double.Epsilon) { // Try to find resource "fuel_name" in PartResources ProtoPartResourceSnapshot proto_fuel = p.resources.Find(k => k.resourceName == fuel_name); // If part doesn't have the fuel, don't do anything. if (proto_fuel == null) { continue; } // get amount in the part amount = proto_fuel.amount; // if cooling is enabled and there is enough EC if (available) { // calculate ec consumption total_cost += cooling_cost * amount * 0.001; } // if cooling is disabled or there wasn't any EC else { // get boiloff rate per-second boiloff_rate = Lib.ReflectionValue <float>(item, "boiloffRate") / 360000.0f; // let it boil off fuel.Consume(amount * (1.0 - Math.Pow(1.0 - boiloff_rate, elapsed_s)), ResourceBroker.Boiloff); } } } // apply EC consumption ec.Consume(total_cost * elapsed_s, ResourceBroker.Cryotank); }
// Doesn't work since squad refactored the ModuleAsteroidInfo / ModuleAsteroidResource for Comets (in 1.10 ?), and was probably not working even before that. static void ProcessAsteroidDrill(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleAsteroidDrill asteroid_drill, VesselResources resources, double elapsed_s) { // note: untested // note: ignore stock temperature mechanic of asteroid drills // note: ignore auto shutdown // note: 'undo' stock behavior 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 ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.StockDrill); recipe.AddInput("ElectricCharge", asteroid_drill.PowerConsumption * elapsed_s); recipe.AddOutput(res_name, res_amount, true); resources.AddRecipe(recipe); // if there was ec // note: comparing against amount in previous simulation step if (resources.GetResource(v, "ElectricCharge").Amount > double.Epsilon) { // consume asteroid mass Lib.Proto.Set(asteroid_info, "currentMassVal", (mass - res_density * res_amount)); } } } // undo stock behavior by forcing last_update_time to now Lib.Proto.Set(m, "lastUpdateTime", Planetarium.GetUniversalTime()); } }
static void ProcessDrill(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleResourceHarvester harvester, VesselResources resources, double elapsed_s) { // note: ignore stock temperature mechanic of harvesters // note: ignore auto shutdown // note: ignore depletion (stock seem to do the same) // note: 'undo' stock behavior 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.GetResource(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 ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.StockDrill); foreach (var ir in harvester.inputList) { recipe.AddInput(ir.ResourceName, ir.Ratio * elapsed_s); } recipe.AddOutput(harvester.ResourceName, abundance * harvester.Efficiency * exp_bonus * elapsed_s, true); resources.AddRecipe(recipe); } } // undo stock behavior by forcing last_update_time to now Lib.Proto.Set(m, "lastUpdateTime", Planetarium.GetUniversalTime()); } }
/// <summary> /// Execute the recipe and record deferred consumption/production for inputs/ouputs. /// This need to be called multiple times until left <= 0.0 for complete execution of the recipe. /// return true if recipe execution is completed, false otherwise /// </summary> private bool ExecuteRecipeStep(Vessel v, VesselResources 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]; ResourceInfo res = resources.GetResource(v, e.name); // handle combined inputs if (e.combined != null) { // is combined resource the primary if (e.combined != "") { Entry sec_e = inputs.Find(x => x.name.Contains(e.combined)); ResourceInfo sec = resources.GetResource(v, sec_e.name); double pri_worst = Lib.Clamp((res.Amount + res.Deferred) * e.inv_quantity, 0.0, worst_input); if (pri_worst > 0.0) { worst_input = pri_worst; } else { worst_input = Lib.Clamp((sec.Amount + sec.Deferred) * sec_e.inv_quantity, 0.0, worst_input); } } } else { 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 { ResourceInfo res = resources.GetResource(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]; ResourceInfo res = resources.GetResource(v, e.name); // handle combined inputs if (e.combined != null) { // is combined resource the primary if (e.combined != "") { Entry sec_e = inputs.Find(x => x.name.Contains(e.combined)); ResourceInfo sec = resources.GetResource(v, sec_e.name); double need = (e.quantity * worst_io) + (sec_e.quantity * worst_io); // do we have enough primary to satisfy needs, if so don't consume secondary if (res.Amount + res.Deferred >= need) { resources.Consume(v, e.name, need, name); } // consume primary if any available and secondary else { need -= res.Amount + res.Deferred; res.Consume(res.Amount + res.Deferred, name); sec.Consume(need, name); } } } else { res.Consume(e.quantity * worst_io, name); } } // produce outputs for (int i = 0; i < outputs.Count; ++i) { Entry e = outputs[i]; ResourceInfo res = resources.GetResource(v, e.name); res.Produce(e.quantity * worst_io, name); } // produce cures for (int i = 0; i < cures.Count; ++i) { Entry entry = cures[i]; List <RuleData> curingRules = new List <RuleData>(); foreach (ProtoCrewMember crew in v.GetVesselCrew()) { KerbalData kd = DB.Kerbal(crew.name); if (kd.sickbay.IndexOf(entry.combined + ",", StringComparison.Ordinal) >= 0) { curingRules.Add(kd.Rule(entry.name)); } } foreach (RuleData rd in curingRules) { rd.problem -= entry.quantity * worst_io / curingRules.Count; rd.problem = Math.Max(rd.problem, 0); } } // update amount left to execute left -= worst_io; // the recipe was executed, at least partially return(worst_io > double.Epsilon); }
void FixedUpdate() { // remove control locks in any case Misc.ClearLocks(); // do nothing if paused if (Lib.IsPaused()) { return; } // convert elapsed time to double only once double fixedDeltaTime = TimeWarp.fixedDeltaTime; // and detect warp blending if (Math.Abs(fixedDeltaTime - elapsed_s) < 0.001) { warp_blending = 0; } else { ++warp_blending; } // update elapsed time elapsed_s = fixedDeltaTime; // store info for oldest unloaded vessel double last_time = 0.0; Guid last_id = Guid.Empty; Vessel last_v = null; VesselData last_vd = null; VesselResources last_resources = null; foreach (VesselData vd in DB.VesselDatas) { vd.EarlyUpdate(); } // for each vessel foreach (Vessel v in FlightGlobals.Vessels) { // get vessel data VesselData vd = v.KerbalismData(); // update the vessel data validity vd.Update(v); // set locks for active vessel if (v.isActiveVessel) { Misc.SetLocks(v); } // maintain eva dead animation and helmet state if (v.loaded && v.isEVA) { EVA.Update(v); } // keep track of rescue mission kerbals, and gift resources to their vessels on discovery if (v.loaded && vd.is_vessel) { // manage rescue mission mechanics Misc.ManageRescueMission(v); } // do nothing else for invalid vessels if (!vd.IsSimulated) { continue; } // get resource cache VesselResources resources = ResourceCache.Get(v); // if loaded if (v.loaded) { //UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.FixedUpdate.Loaded.VesselDataEval"); // update the vessel info vd.Evaluate(false, elapsed_s); //UnityEngine.Profiling.Profiler.EndSample(); // get most used resource ResourceInfo ec = resources.GetResource(v, "ElectricCharge"); UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.FixedUpdate.Loaded.Radiation"); // show belt warnings Radiation.BeltWarnings(v, vd); // update storm data Storm.Update(v, vd, elapsed_s); UnityEngine.Profiling.Profiler.EndSample(); UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.FixedUpdate.Loaded.Comms"); Communications.Update(v, vd, ec, elapsed_s); UnityEngine.Profiling.Profiler.EndSample(); // Habitat equalization ResourceBalance.Equalizer(v); UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.FixedUpdate.Loaded.Science"); // transmit science data Science.Update(v, vd, ec, elapsed_s); UnityEngine.Profiling.Profiler.EndSample(); UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.FixedUpdate.Loaded.Profile"); // apply rules Profile.Execute(v, vd, resources, elapsed_s); UnityEngine.Profiling.Profiler.EndSample(); UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.FixedUpdate.Loaded.Profile"); // part module resource updates vd.ResourceUpdate(resources, elapsed_s); UnityEngine.Profiling.Profiler.EndSample(); UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.FixedUpdate.Loaded.Resource"); // apply deferred requests resources.Sync(v, vd, elapsed_s); UnityEngine.Profiling.Profiler.EndSample(); // call automation scripts vd.computer.Automate(v, vd, resources); // remove from unloaded data container unloaded.Remove(vd.VesselId); } // if unloaded else { // get unloaded data, or create an empty one Unloaded_data ud; if (!unloaded.TryGetValue(vd.VesselId, out ud)) { ud = new Unloaded_data(); unloaded.Add(vd.VesselId, ud); } // accumulate time ud.time += elapsed_s; // maintain oldest entry if (ud.time > last_time) { last_time = ud.time; last_v = v; last_vd = vd; last_resources = resources; } } } // at most one vessel gets background processing per physics tick : // if there is a vessel that is not the currently loaded vessel, then // we will update the vessel whose most recent background update is the oldest if (last_v != null) { //UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.FixedUpdate.Unloaded.VesselDataEval"); // update the vessel info (high timewarp speeds reevaluation) last_vd.Evaluate(false, last_time); //UnityEngine.Profiling.Profiler.EndSample(); // get most used resource ResourceInfo last_ec = last_resources.GetResource(last_v, "ElectricCharge"); UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.FixedUpdate.Unloaded.Radiation"); // show belt warnings Radiation.BeltWarnings(last_v, last_vd); // update storm data Storm.Update(last_v, last_vd, last_time); UnityEngine.Profiling.Profiler.EndSample(); UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.FixedUpdate.Unloaded.Comms"); Communications.Update(last_v, last_vd, last_ec, last_time); UnityEngine.Profiling.Profiler.EndSample(); UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.FixedUpdate.Unloaded.Profile"); // apply rules Profile.Execute(last_v, last_vd, last_resources, last_time); UnityEngine.Profiling.Profiler.EndSample(); UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.FixedUpdate.Unloaded.Background"); // simulate modules in background Background.Update(last_v, last_vd, last_resources, last_time); UnityEngine.Profiling.Profiler.EndSample(); UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.FixedUpdate.Unloaded.Science"); // transmit science data Science.Update(last_v, last_vd, last_ec, last_time); UnityEngine.Profiling.Profiler.EndSample(); UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.FixedUpdate.Unloaded.Resource"); // apply deferred requests last_resources.Sync(last_v, last_vd, last_time); UnityEngine.Profiling.Profiler.EndSample(); // call automation scripts last_vd.computer.Automate(last_v, last_vd, last_resources); // remove from unloaded data container unloaded.Remove(last_vd.VesselId); } // update storm data for one body per-step if (storm_bodies.Count > 0) { storm_bodies.ForEach(k => k.time += elapsed_s); Storm_data sd = storm_bodies[storm_index]; Storm.Update(sd.body, sd.time); sd.time = 0.0; storm_index = (storm_index + 1) % storm_bodies.Count; } }
public void Execute(Vessel v, VesselData vd, VesselResources resources, double elapsed_s) { // store list of crew to kill List <ProtoCrewMember> deferred_kills = new List <ProtoCrewMember>(); // get input resource handler ResourceInfo res = input.Length > 0 ? resources.GetResource(v, input) : null; // determine message variant uint variant = vd.EnvTemperature < Settings.LifeSupportSurvivalTemperature ? 0 : 1u; // get product of all environment modifiers double k = Modifiers.Evaluate(v, vd, resources, modifiers); bool lifetime_enabled = PreferencesRadiation.Instance.lifetime; // 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); rd.lifetime = lifetime_enabled && lifetime; // influence consumption by elapsed time double step = elapsed_s; // if interval-based if (interval > 0.0) { // accumulate time rd.time_since += elapsed_s; // determine number of intervals that has passed (can be 2 or more if elapsed_s > interval * 2) step = Math.Floor(rd.time_since / interval); // consume time rd.time_since -= step * interval; } // if there is a resource specified if (res != null && rate > double.Epsilon) { // get rate including per-kerbal variance double resRate = rate // consumption rate * Variance(name, c, individuality) // kerbal-specific variance * k; // product of environment modifiers // determine amount of resource to consume double required = resRate * step; // seconds elapsed or interval amount // remember if a meal is consumed/produced in this simulation step if (interval > 0.0) { double ratePerStep = resRate / interval; res.UpdateIntervalRule(-required, -ratePerStep, name); if (output.Length > 0) { ResourceCache.GetResource(v, output).UpdateIntervalRule(required * ratio, ratePerStep * ratio, name); } } // if continuous, or if one or more intervals elapsed if (step > 0.0) { // if there is no output if (output.Length == 0) { // simply consume (that is faster) res.Consume(required, name); } // if there is an output else { // transform input into output resource // - rules always dump excess overboard (because it is waste) ResourceRecipe recipe = new ResourceRecipe(name); recipe.AddInput(input, required); recipe.AddOutput(output, required * ratio, true); resources.AddRecipe(recipe); } } } // if continuous, or if one or more intervals elapsed if (step > 0.0) { // 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(name, c, variance); // kerbal-specific variance } // else slowly recover else { rd.problem *= 1.0 / (1.0 + Math.Max(interval, 1.0) * step * 0.002); } } bool do_breakdown = false; if (breakdown) { // don't do breakdowns and don't show stress message if disabled if (!PreferencesComfort.Instance.stressBreakdowns) { return; } // stress level double breakdown_probability = rd.problem / warning_threshold; breakdown_probability = Lib.Clamp(breakdown_probability, 0.0, 1.0); // use the stupidity of a kerbal. // however, nobody is perfect - not even a kerbal with a stupidity of 0. breakdown_probability *= c.stupidity * 0.6 + 0.4; // apply the weekly error rate breakdown_probability *= PreferencesComfort.Instance.stressBreakdownRate; // now we have the probability for one failure per week, based on the // individual stupidity and stress level of the kerbal. breakdown_probability = (breakdown_probability * elapsed_s) / (Lib.DaysInYear * Lib.HoursInDay * 3600); if (breakdown_probability > Lib.RandomDouble()) { do_breakdown = true; // we're stressed out and just made a major mistake, this further increases the stress level... rd.problem += warning_threshold * 0.05; // add 5% of the warning treshold to current stress level } } // kill kerbal if necessary if (rd.problem >= fatal_threshold) { #if DEBUG || DEVBUILD Lib.Log("Rule " + name + " kills " + c.name + " at " + rd.problem + " " + degeneration + "/" + k + "/" + step + "/" + Variance(name, c, variance)); #endif if (fatal_message.Length > 0) { Message.Post(breakdown ? Severity.breakdown : Severity.fatality, Lib.ExpandMsg(fatal_message, v, c, variant)); } if (breakdown) { do_breakdown = true; // move back between warning and danger level rd.problem = (warning_threshold + danger_threshold) * 0.5; // make sure next danger message 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; } if (do_breakdown) { // trigger breakdown event Misc.Breakdown(v, c); } } // execute the deferred kills foreach (ProtoCrewMember c in deferred_kills) { Misc.Kill(v, c); } }
static void ProcessGenerator(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleGenerator generator, VesselResources resources, double elapsed_s) { // if active if (Lib.Proto.GetBool(m, "generatorIsActive")) { // create and commit recipe ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.StockConverter); foreach (ModuleResource ir in generator.resHandler.inputResources) { recipe.AddInput(ir.name, ir.rate * elapsed_s); } foreach (ModuleResource or in generator.resHandler.outputResources) { recipe.AddOutput(or.name, or.rate * elapsed_s, true); } resources.AddRecipe(recipe); } }