static void Render_supplies(Panel p, Vessel v, Vessel_info vi, Vessel_resources resources) { // for each supply int supplies = 0; foreach (Supply supply in Profile.supplies) { // get resource info Resource_info res = resources.Info(v, supply.resource); // only show estimate if the resource is present if (res.amount <= 1e-10) continue; // render panel title, if not done already if (supplies == 0) p.AddSection("SUPPLIES"); // rate tooltip string rate_tooltip = Math.Abs(res.rate) >= 1e-10 ? Lib.BuildString ( res.rate > 0.0 ? "<color=#00ff00><b>" : "<color=#ffaa00><b>", Lib.HumanReadableRate(Math.Abs(res.rate)), "</b></color>" ) : string.Empty; // determine label string label = supply.resource == "ElectricCharge" ? "battery" : Lib.SpacesOnCaps(supply.resource).ToLower(); // finally, render resource supply p.AddContent(label, Lib.HumanReadableDuration(res.Depletion(vi.crew_count)), rate_tooltip); ++supplies; } }
private void ExecuteRecipe(double k, Vessel_resources resources, double elapsed_s, Resource_recipe recipe) { // only execute processes if necessary if (Math.Abs(k) < double.Epsilon) { return; } 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)); } 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.Cure(p.Key, p.Value * k * elapsed_s, modifiers[0]); } resources.Transform(recipe); }
bool Render_vessel(Panel p, Vessel v) { // get vessel info Vessel_info vi = Cache.VesselInfo(v); // skip invalid vessels if (!vi.is_valid) return false; // get data from db VesselData vd = DB.Vessel(v); // determine if filter must be shown show_filter |= vd.group.Length > 0 && vd.group != "NONE"; // skip filtered vessels if (Filtered() && !Filter_match(vd.group)) return false; // get resource handler Vessel_resources resources = ResourceCache.Get(v); // get vessel crew List<ProtoCrewMember> crew = Lib.CrewList(v); // get vessel name string vessel_name = v.isEVA ? crew[0].name : v.vesselName; // get body name string body_name = v.mainBody.name.ToUpper(); // render entry p.AddHeader ( Lib.BuildString("<b>", Lib.Ellipsis(vessel_name, Styles.ScaleStringLength(((page == MonitorPage.data || page == MonitorPage.log || selected_id == Guid.Empty) && !Lib.IsFlight()) ? 50 : 30)), "</b> <size=", Styles.ScaleInteger(9).ToString(), "><color=#cccccc>", Lib.Ellipsis(body_name, Styles.ScaleStringLength(8)), "</color></size>"), string.Empty, () => { selected_id = selected_id != v.id ? v.id : Guid.Empty; } ); // problem indicator Indicator_problems(p, v, vi, crew); // battery indicator Indicator_ec(p, v, vi); // supply indicator if (Features.Supplies) Indicator_supplies(p, v, vi); // reliability indicator if (Features.Reliability) Indicator_reliability(p, v, vi); // signal indicator if (RemoteTech.Enabled || HighLogic.fetch.currentGame.Parameters.Difficulty.EnableCommNet) Indicator_signal(p, v, vi); // done return true; }
public void Execute(Vessel v, Vessel_info vi, Vessel_resources resources, double elapsed_s) { if (restricted) { ExecutePerPart(v, vi, resources, elapsed_s); } else { ExecuteVesselWide(v, vi, resources, elapsed_s); } }
private void ExecuteVesselWide(Vessel v, Vessel_info vi, Vessel_resources 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, vi, resources, modifiers); Resource_recipe recipe = new Resource_recipe((Part)null); ExecuteRecipe(k, resources, elapsed_s, 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 info from the cache Vessel_info vi = Cache.VesselInfo(v); // if not a valid vessel, leave the panel empty if (!vi.is_valid) { return; } // set metadata p.Title(Lib.BuildString(Lib.Ellipsis(v.vesselName, Styles.ScaleStringLength(20)), " <color=#cccccc>TELEMETRY</color>")); p.Width(Styles.ScaleWidthFloat(355.0f)); p.paneltype = Panel.PanelType.telemetry; // time-out simulation if (p.Timeout(vi)) { return; } // get vessel data VesselData vd = DB.Vessel(v); // get resources Vessel_resources resources = ResourceCache.Get(v); // get crew var crew = Lib.CrewList(v); // draw the content Render_crew(p, crew); Render_greenhouse(p, vi); Render_supplies(p, v, vi, resources); Render_habitat(p, v, vi); Render_environment(p, v, vi); // collapse eva kerbal sections into one if (v.isEVA) { p.Collapse("EVA SUIT"); } }
public void Execute(Vessel v, VesselData vd, Vessel_resources resources) { // get crew List <ProtoCrewMember> crew = Lib.CrewList(v); // get resource handler Resource_info res = resources.Info(v, resource); // get data from db SupplyData sd = DB.Vessel(v).Supply(resource); // message obey user config bool show_msg = resource == "ElectricCharge" ? vd.cfg_ec : vd.cfg_supply; // messages are shown only if there is some capacity and the vessel is manned // special case: ElectricCharge related messages are shown for unmanned vessels too if (res.capacity > double.Epsilon && (crew.Count > 0 || resource == "ElectricCharge")) { // manned/probe message variant uint variant = crew.Count > 0 ? 0 : 1u; // manage messages if (res.level <= double.Epsilon && sd.message < 2) { if (empty_message.Length > 0 && show_msg) { Message.Post(Severity.danger, Lib.ExpandMsg(empty_message, v, null, variant)); } sd.message = 2; } else if (res.level < low_threshold && sd.message < 1) { if (low_message.Length > 0 && show_msg) { Message.Post(Severity.warning, Lib.ExpandMsg(low_message, v, null, variant)); } sd.message = 1; } else if (res.level > low_threshold && sd.message > 0) { if (refill_message.Length > 0 && show_msg) { Message.Post(Severity.relax, Lib.ExpandMsg(refill_message, v, null, variant)); } sd.message = 0; } } }
private static void RunProcessTick(Vessel v, double elapsed_s, double ec_produced, List <KeyValuePair <string, double> > resourcesProduced, double ec_consumed, List <KeyValuePair <string, double> > resourcesConsumed, Resource_info ec, Vessel_resources resources) { // evaluate process rate double rate = 1; if (ec_consumed < ec.amount) { rate = ec.amount / ec_consumed; } foreach (var consumed in resourcesConsumed) { var ri = resources.Info(v, consumed.Key); rate = Math.Min(rate, Lib.Clamp(ri.amount / (consumed.Value * elapsed_s), 0, 1)); } foreach (var produced in resourcesProduced) { var ri = resources.Info(v, produced.Key); var capacityAvailable = ri.capacity - ri.amount; var amountProduced = produced.Value * elapsed_s; if (capacityAvailable < amountProduced) { rate = Math.Min(rate, Lib.Clamp(capacityAvailable / amountProduced, 0, 1)); } } // produce/consume according to rate if (rate < double.Epsilon) { return; } ec.Consume(ec_consumed * elapsed_s * rate, "module process"); ec.Produce(ec_produced * elapsed_s * rate, "module process"); foreach (var consumed in resourcesConsumed) { resources.Info(v, consumed.Key).Consume(consumed.Value * elapsed_s * rate, "module process"); } foreach (var produced in resourcesProduced) { resources.Info(v, produced.Key).Produce(produced.Value * elapsed_s * rate, "module process"); } }
void ToEVA(GameEvents.FromToAction <Part, Part> data) { // get total crew in the origin vessel double tot_crew = (double)Lib.CrewCount(data.from.vessel) + 1.0; // get vessel resources handler Vessel_resources resources = ResourceCache.Get(data.from.vessel); // setup supply resources capacity in the eva kerbal Profile.SetupEva(data.to); // for each resource in the kerbal for (int i = 0; i < data.to.Resources.Count; ++i) { // get the resource PartResource res = data.to.Resources[i]; // determine quantity to take double quantity = Math.Min(resources.Info(data.from.vessel, res.resourceName).amount / tot_crew, res.maxAmount); // remove resource from vessel quantity = data.from.RequestResource(res.resourceName, quantity); // add resource to eva kerbal data.to.RequestResource(res.resourceName, -quantity); } // show warning if there isn't monoprop in the eva suit string prop_name = Lib.EvaPropellantName(); if (Lib.Amount(data.to, prop_name) <= double.Epsilon && !Lib.Landed(data.from.vessel)) { Message.Post(Severity.danger, Lib.BuildString("There isn't any <b>", prop_name, "</b> in the EVA suit"), "Don't let the ladder go!"); } // turn off headlamp light, to avoid stock bug that show them for a split second when going on eva KerbalEVA kerbal = data.to.FindModuleImplementing <KerbalEVA>(); EVA.HeadLamps(kerbal, false); // execute script DB.Vessel(data.from.vessel).computer.Execute(data.from.vessel, ScriptType.eva_out); }
// return resource cache for a vessel public static Vessel_resources Get(Vessel v) { // try to get existing entry if any Vessel_resources entry; if (entries.TryGetValue(v.id, out entry)) { return(entry); } // create new entry entry = new Vessel_resources(); // remember new entry entries.Add(v.id, entry); // return new entry return(entry); }
public static void Execute(Vessel v, Vessel_info vi, VesselData vd, Vessel_resources resources, double elapsed_s) { // execute all supplies foreach (Supply supply in supplies) { supply.Execute(v, vd, resources); } // execute all rules foreach (Rule rule in rules) { rule.Execute(v, vi, resources, elapsed_s); } // execute all processes foreach (Process process in processes) { process.Execute(v, vi, resources, elapsed_s); } }
public static void Execute(Vessel v, Vessel_info vi, VesselData vd, Vessel_resources 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, vi, resources, elapsed_s); } // execute all processes foreach (Process process in processes) { process.Execute(v, vi, resources, elapsed_s); } }
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); } }
private void ExecutePerPart(Vessel v, Vessel_info vi, Vessel_resources resources, double elapsed_s) { if (v.loaded) { foreach (Part p in v.Parts) { double k = Modifiers.Evaluate(v, vi, resources, modifiers, p, null); Resource_recipe recipe = new Resource_recipe(p); ExecuteRecipe(k, resources, elapsed_s, recipe); } } else { foreach (ProtoPartSnapshot p in v.protoVessel.protoPartSnapshots) { double k = Modifiers.Evaluate(v, vi, resources, modifiers, null, p); Resource_recipe recipe = new Resource_recipe(p); ExecuteRecipe(k, resources, elapsed_s, recipe); } } }
public static void BackgroundUpdate(Vessel v, ProtoPartModuleSnapshot m, Experiment experiment, Resource_info ec, Vessel_resources resources, double elapsed_s) { bool didPrepare = Lib.Proto.GetBool(m, "didPrepare", false); bool shrouded = Lib.Proto.GetBool(m, "shrouded", false); string last_subject_id = Lib.Proto.GetString(m, "last_subject_id", ""); double remainingSampleMass = Lib.Proto.GetDouble(m, "remainingSampleMass", 0); bool broken = Lib.Proto.GetBool(m, "broken", false); bool forcedRun = Lib.Proto.GetBool(m, "forcedRun", false); bool recording = Lib.Proto.GetBool(m, "recording", false); uint privateHdId = Lib.Proto.GetUInt(m, "privateHdId", 0); string issue = TestForIssues(v, ec, experiment, privateHdId, broken, remainingSampleMass, didPrepare, shrouded, last_subject_id); if (string.IsNullOrEmpty(issue)) { issue = TestForResources(v, KerbalismProcess.ParseResources(experiment.resources), elapsed_s, resources); } Lib.Proto.Set(m, "issue", issue); if (!string.IsNullOrEmpty(issue)) { return; } var subject_id = Science.Generate_subject_id(experiment.experiment_id, v); Lib.Proto.Set(m, "last_subject_id", subject_id); double dataSampled = Lib.Proto.GetDouble(m, "dataSampled"); if (last_subject_id != subject_id) { dataSampled = 0; Lib.Proto.Set(m, "forcedRun", false); } double scienceValue = Science.Value(last_subject_id); Lib.Proto.Set(m, "scienceValue", scienceValue); var state = GetState(scienceValue, issue, recording, forcedRun); if (state != State.RUNNING) { return; } if (dataSampled >= Science.Experiment(subject_id).max_amount) { return; } var stored = DoRecord(experiment, subject_id, v, ec, privateHdId, resources, KerbalismProcess.ParseResources(experiment.resources), remainingSampleMass, dataSampled, out dataSampled, out remainingSampleMass); if (!stored) { Lib.Proto.Set(m, "issue", insufficient_storage); } Lib.Proto.Set(m, "dataSampled", dataSampled); Lib.Proto.Set(m, "remainingSampleMass", remainingSampleMass); }
private static double Rate(Vessel v, double chunkSize, double maxCapacity, double elapsed, Resource_info ec, double ec_rate, Vessel_resources resources, List <KeyValuePair <string, double> > resourceDefs) { double result = Lib.Clamp(maxCapacity / chunkSize, 0, 1); result = Math.Min(result, Lib.Clamp(ec.amount / (ec_rate * elapsed), 0, 1)); foreach (var p in resourceDefs) { var ri = resources.Info(v, p.Key); result = Math.Min(result, Lib.Clamp(ri.amount / (p.Value * elapsed), 0, 1)); } return(result); }
private static bool DoRecord(Experiment experiment, string subject_id, Vessel vessel, Resource_info ec, uint hdId, Vessel_resources resources, List <KeyValuePair <string, double> > resourceDefs, double remainingSampleMass, double dataSampled, out double sampledOut, out double remainingSampleMassOut) { // default output values for early returns sampledOut = dataSampled; remainingSampleMassOut = remainingSampleMass; var exp = Science.Experiment(subject_id); if (Done(exp, dataSampled)) { return(true); } double elapsed = Kerbalism.elapsed_s; double chunkSize = Math.Min(experiment.data_rate * elapsed, exp.max_amount); double massDelta = experiment.sample_mass * chunkSize / exp.max_amount; Drive drive = GetDrive(experiment, vessel, hdId, chunkSize, subject_id); // on high time warp this chunk size could be too big, but we could store a sizable amount if we process less bool isFile = experiment.sample_mass < float.Epsilon; double maxCapacity = isFile ? drive.FileCapacityAvailable() : drive.SampleCapacityAvailable(subject_id); Drive warpCacheDrive = null; if (isFile) { if (drive.GetFileSend(subject_id)) { warpCacheDrive = Cache.WarpCache(vessel); } if (warpCacheDrive != null) { maxCapacity += warpCacheDrive.FileCapacityAvailable(); } } double factor = Rate(vessel, chunkSize, maxCapacity, elapsed, ec, experiment.ec_rate, resources, resourceDefs); if (factor < double.Epsilon) { return(false); } chunkSize *= factor; massDelta *= factor; elapsed *= factor; bool stored = false; if (chunkSize > double.Epsilon) { if (isFile) { if (warpCacheDrive != null) { double s = Math.Min(chunkSize, warpCacheDrive.FileCapacityAvailable()); stored = warpCacheDrive.Record_file(subject_id, s, true); if (chunkSize > s) // only write to persisted drive if the data cannot be transmitted in this tick { stored &= drive.Record_file(subject_id, chunkSize - s, true); } } else { stored = drive.Record_file(subject_id, chunkSize, true); } } else { stored = drive.Record_sample(subject_id, chunkSize, massDelta); } } if (!stored) { return(false); } // consume resources ec.Consume(experiment.ec_rate * elapsed, "experiment"); foreach (var p in resourceDefs) { resources.Consume(vessel, p.Key, p.Value * elapsed, "experiment"); } dataSampled += chunkSize; dataSampled = Math.Min(dataSampled, exp.max_amount); sampledOut = dataSampled; if (!experiment.sample_collecting) { remainingSampleMass -= massDelta; remainingSampleMass = Math.Max(remainingSampleMass, 0); } remainingSampleMassOut = remainingSampleMass; return(true); }
public static double Evaluate(Vessel v, Vessel_info vi, Vessel_resources resources, List<string> modifiers, Part p = null, ProtoPartSnapshot pp = null) { double k = 1.0; foreach (string mod in modifiers) { switch (mod) { case "zerog": k *= vi.zerog ? 1.0 : 0.0; break; case "landed": k *= vi.landed ? 1.0 : 0.0; break; case "breathable": k *= vi.breathable ? 1.0 : 0.0; break; case "non_breathable": k *= vi.breathable ? 0.0 : 1.0; break; case "temperature": k *= vi.temp_diff; break; case "radiation": k *= vi.radiation; break; case "shielding": k *= 1.0 - vi.shielding; break; case "volume": k *= vi.volume; break; case "surface": k *= vi.surface; break; case "living_space": k /= vi.living_space; break; case "comfort": k /= vi.comforts.factor; break; case "pressure": k *= vi.pressure > Settings.PressureThreshold ? 1.0 : Settings.PressureFactor; break; case "poisoning": k *= vi.poisoning > Settings.PoisoningThreshold ? 1.0 : Settings.PoisoningFactor; break; case "humidity": k *= vi.humidity > Settings.HumidityThreshold ? 1.0 : Settings.HumidityFactor; break; case "per_capita": k /= (double)Math.Max(vi.crew_count, 1); break; default: // If a psuedo resource is flowable, per part processing would result in every part producing the entire capacity of the vessel if (!Lib.IsResourceImpossibleToFlow(mod)) throw new Exception("psuedo-resource " + mod + " must be NO_FLOW"); if (p != null) k *= resources.Info(v, mod).GetResourceInfoView(p).amount; // loaded part else if (pp != null) k *= resources.Info(v, mod).GetResourceInfoView(pp).amount; // unloaded part else k *= resources.Info(v, mod).amount; // vessel-wide break; } } return k; }
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, "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 Resource_recipe recipe = new Resource_recipe(g.part, "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.Input(input.name, vi.breathable ? 0.0 : input.rate * elapsed_s, "CarbonDioxide"); } // CarbonDioxide is secondary combined input else if (g.WACO2 && input.name == "CarbonDioxide") { recipe.Input(input.name, vi.breathable ? 0.0 : input.rate * elapsed_s, ""); } // if atmosphere is breathable disable WasteAtmosphere / CO2 else if (!g.WACO2 && (input.name == "CarbonDioxide" || input.name == "WasteAtmosphere")) { recipe.Input(input.name, vi.breathable ? 0.0 : input.rate, ""); } else { recipe.Input(input.name, input.rate * elapsed_s); } } foreach (ModuleResource output in g.resHandler.outputResources) { // if atmosphere is breathable disable Oxygen if (output.name == "Oxygen") { recipe.Output(output.name, vi.breathable ? 0.0 : output.rate * elapsed_s, true); } else { 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; 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 || vi.breathable) { continue; // skip if already checked or atmosphere is breathable } if (g.WACO2) { if (resources.Info(v, "WasteAtmosphere").amount <= double.Epsilon && resources.Info(v, "CarbonDioxide").amount <= double.Epsilon) { inputs = false; missing_res = "CarbonDioxide"; break; } dis_WACO2 = true; continue; } } 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); } }
// call scripts automatically when conditions are met public void Automate(Vessel v, Vessel_info vi, Vessel_resources resources) { // do nothing if automation is disabled if (!Features.Automation) { return; } // get current states Resource_info ec = resources.Info(v, "ElectricCharge"); bool sunlight = vi.sunlight > double.Epsilon; bool power_low = ec.level < 0.2; bool power_high = ec.level > 0.8; bool radiation_low = vi.radiation < 0.000005552; //< 0.02 rad/h bool radiation_high = vi.radiation > 0.00001388; //< 0.05 rad/h bool signal = vi.connection.linked; // get current situation bool landed = false; bool atmo = false; bool space = false; switch (v.situation) { case Vessel.Situations.LANDED: case Vessel.Situations.SPLASHED: landed = true; break; case Vessel.Situations.FLYING: atmo = true; break; case Vessel.Situations.SUB_ORBITAL: case Vessel.Situations.ORBITING: case Vessel.Situations.ESCAPING: space = true; break; } // compile list of scripts that need to be called var to_exec = new List <Script>(); foreach (var p in scripts) { ScriptType type = p.Key; Script script = p.Value; if (script.states.Count == 0) { continue; //< skip empty scripts (may happen during editing) } switch (type) { case ScriptType.landed: if (landed && script.prev == "0") { to_exec.Add(script); } script.prev = landed ? "1" : "0"; break; case ScriptType.atmo: if (atmo && script.prev == "0") { to_exec.Add(script); } script.prev = atmo ? "1" : "0"; break; case ScriptType.space: if (space && script.prev == "0") { to_exec.Add(script); } script.prev = space ? "1" : "0"; break; case ScriptType.sunlight: if (sunlight && script.prev == "0") { to_exec.Add(script); } script.prev = sunlight ? "1" : "0"; break; case ScriptType.shadow: if (!sunlight && script.prev == "0") { to_exec.Add(script); } script.prev = !sunlight ? "1" : "0"; break; case ScriptType.power_high: if (power_high && script.prev == "0") { to_exec.Add(script); } script.prev = power_high ? "1" : "0"; break; case ScriptType.power_low: if (power_low && script.prev == "0") { to_exec.Add(script); } script.prev = power_low ? "1" : "0"; break; case ScriptType.rad_low: if (radiation_low && script.prev == "0") { to_exec.Add(script); } script.prev = radiation_low ? "1" : "0"; break; case ScriptType.rad_high: if (radiation_high && script.prev == "0") { to_exec.Add(script); } script.prev = radiation_high ? "1" : "0"; break; case ScriptType.linked: if (signal && script.prev == "0") { to_exec.Add(script); } script.prev = signal ? "1" : "0"; break; case ScriptType.unlinked: if (!signal && script.prev == "0") { to_exec.Add(script); } script.prev = !signal ? "1" : "0"; break; } } // if there are scripts to call if (to_exec.Count > 0) { // get list of devices // - we avoid creating it when there are no scripts to be executed, making its overall cost trivial var devices = Boot(v); // execute all scripts foreach (Script script in to_exec) { script.Execute(devices); } // show message to the user if (DB.Vessel(v).cfg_script) { Message.Post(Lib.BuildString("Script called on vessel <b>", v.vesselName, "</b>")); } } }
static void 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 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 Resource_recipe recipe = new Resource_recipe(p); recipe.Input("ElectricCharge", asteroid_drill.PowerConsumption * elapsed_s); recipe.Output(res_name, res_amount, true); resources.Transform(recipe); // if there was ec // note: comparing against amount in previous simulation step if (resources.Info(v, "ElectricCharge").amount > double.Epsilon) { // consume asteroid mass Lib.Proto.Set(asteroid_info, "currentMassVal", (mass - res_density * res_amount)); } } } // undo stock behavior by forcing last_update_time to now Lib.Proto.Set(m, "lastUpdateTime", Planetarium.GetUniversalTime()); } }
static void ProcessCryoTank(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule cryotank, Vessel_resources resources, Resource_info 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 Resource_info fuel = resources.Info(v, fuel_name); // if there is some fuel // note: comparing against amount in previous simulation step if (fuel.amount > double.Epsilon) { // Try 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))); } } } // apply EC consumption ec.Consume(total_cost * elapsed_s); }
public static void Update(Vessel v, Vessel_info vi, VesselData vd, Vessel_resources resources, double elapsed_s) { // get most used resource handlers Resource_info ec = resources.Info(v, "ElectricCharge"); // store data required to support multiple modules of same type in a part var PD = new Dictionary <string, Lib.Module_prefab_data>(); // This is basically handled in cache. However, when accelerating time warp while // the vessel is in shadow, the cache logic doesn't kick in soon enough. So we double-check here if (TimeWarp.CurrentRate > 1000.0f || elapsed_s > 150) // we're time warping fast... { vi.highspeedWarp(v); } // for each part foreach (ProtoPartSnapshot p in v.protoVessel.protoPartSnapshots) { // get part prefab (required for module properties) Part part_prefab = PartLoader.getPartInfoByName(p.partName).partPrefab; // get all module prefabs var module_prefabs = part_prefab.FindModulesImplementing <PartModule>(); // clear module indexes PD.Clear(); // for each module foreach (ProtoPartModuleSnapshot m in p.modules) { // get module type // if the type is unknown, skip it Module_type type = ModuleType(m.moduleName); if (type == Module_type.Unknown) { continue; } // get the module prefab // if the prefab doesn't contain this module, skip it PartModule module_prefab = Lib.ModulePrefab(module_prefabs, m.moduleName, PD); if (!module_prefab) { continue; } // if the module is disabled, skip it // note: this must be done after ModulePrefab is called, so that indexes are right if (!Lib.Proto.GetBool(m, "isEnabled")) { continue; } // process modules // note: this should be a fast switch, possibly compiled to a jump table switch (type) { case Module_type.Reliability: Reliability.BackgroundUpdate(v, p, m, module_prefab as Reliability); break; case Module_type.Experiment: Experiment.BackgroundUpdate(v, m, module_prefab as Experiment, ec, elapsed_s); break; case Module_type.Greenhouse: Greenhouse.BackgroundUpdate(v, m, module_prefab as Greenhouse, vi, resources, elapsed_s); break; case Module_type.GravityRing: GravityRing.BackgroundUpdate(v, p, m, module_prefab as GravityRing, ec, elapsed_s); break; case Module_type.Emitter: Emitter.BackgroundUpdate(v, p, m, module_prefab as Emitter, ec, elapsed_s); break; case Module_type.Harvester: Harvester.BackgroundUpdate(v, m, module_prefab as Harvester, elapsed_s); break; // Kerbalism ground and air harvester module case Module_type.Laboratory: Laboratory.BackgroundUpdate(v, p, m, module_prefab as Laboratory, ec, elapsed_s); break; case Module_type.Command: ProcessCommand(v, p, m, module_prefab as ModuleCommand, resources, elapsed_s); break; case Module_type.Panel: ProcessPanel(v, p, m, module_prefab as ModuleDeployableSolarPanel, vi, ec, elapsed_s); break; case Module_type.Generator: ProcessGenerator(v, p, m, module_prefab as ModuleGenerator, resources, elapsed_s); break; case Module_type.Converter: ProcessConverter(v, p, m, module_prefab as ModuleResourceConverter, resources, elapsed_s); break; case Module_type.Drill: ProcessDrill(v, p, m, module_prefab as ModuleResourceHarvester, resources, elapsed_s); break; // Stock ground harvester module case Module_type.AsteroidDrill: ProcessAsteroidDrill(v, p, m, module_prefab as ModuleAsteroidDrill, resources, elapsed_s); break; // Stock asteroid harvester module case Module_type.StockLab: ProcessStockLab(v, p, m, module_prefab as ModuleScienceConverter, ec, elapsed_s); break; case Module_type.Light: ProcessLight(v, p, m, module_prefab as ModuleLight, ec, elapsed_s); break; case Module_type.Scanner: ProcessScanner(v, p, m, module_prefab, part_prefab, vd, ec, elapsed_s); break; case Module_type.CurvedPanel: ProcessCurvedPanel(v, p, m, module_prefab, part_prefab, vi, ec, elapsed_s); break; case Module_type.FissionGenerator: ProcessFissionGenerator(v, p, m, module_prefab, ec, elapsed_s); break; case Module_type.RadioisotopeGenerator: ProcessRadioisotopeGenerator(v, p, m, module_prefab, ec, elapsed_s); break; case Module_type.CryoTank: ProcessCryoTank(v, p, m, module_prefab, resources, ec, elapsed_s); break; case Module_type.FNGenerator: ProcessFNGenerator(v, p, m, module_prefab, ec, elapsed_s); break; } } } }
void FixedUpdate() { // remove control locks in any case Misc.ClearLocks(); // do nothing if paused if (Lib.IsPaused()) { return; } // maintain elapsed_s, converting to double only once // and detect warp blending double fixedDeltaTime = TimeWarp.fixedDeltaTime; if (Math.Abs(fixedDeltaTime - elapsed_s) > double.Epsilon) { warp_blending = 0; } else { ++warp_blending; } elapsed_s = fixedDeltaTime; // evict oldest entry from vessel cache Cache.Update(); // store info for oldest unloaded vessel double last_time = 0.0; Vessel last_v = null; Vessel_info last_vi = null; VesselData last_vd = null; Vessel_resources last_resources = null; // for each vessel foreach (Vessel v in FlightGlobals.Vessels) { // get vessel info from the cache Vessel_info vi = Cache.VesselInfo(v); // set locks for active vessel if (v.isActiveVessel) { Misc.SetLocks(v, vi); } // 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 && vi.is_vessel) { // manage rescue mission mechanics Misc.ManageRescueMission(v); } // do nothing else for invalid vessels if (!vi.is_valid) { continue; } // get vessel data from db VesselData vd = DB.Vessel(v); // get resource cache Vessel_resources resources = ResourceCache.Get(v); // if loaded if (v.loaded) { // get most used resource Resource_info ec = resources.Info(v, "ElectricCharge"); // show belt warnings Radiation.BeltWarnings(v, vi, vd); // update storm data Storm.Update(v, vi, vd, elapsed_s); Communications.Update(v, vi, vd, ec, elapsed_s); // Habitat equalization ResourceBalance.Equalizer(v); // transmit science data Science.Update(v, vi, vd, resources, elapsed_s); // apply rules Profile.Execute(v, vi, vd, resources, elapsed_s); // apply deferred requests resources.Sync(v, elapsed_s); // call automation scripts vd.computer.Automate(v, vi, resources); // remove from unloaded data container unloaded.Remove(vi.id); } // if unloaded else { // get unloaded data, or create an empty one Unloaded_data ud; if (!unloaded.TryGetValue(vi.id, out ud)) { ud = new Unloaded_data(); unloaded.Add(vi.id, ud); } // accumulate time ud.time += elapsed_s; // maintain oldest entry if (ud.time > last_time) { last_time = ud.time; last_v = v; last_vi = vi; 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) { // get most used resource Resource_info last_ec = last_resources.Info(last_v, "ElectricCharge"); // show belt warnings Radiation.BeltWarnings(last_v, last_vi, last_vd); // update storm data Storm.Update(last_v, last_vi, last_vd, last_time); Communications.Update(last_v, last_vi, last_vd, last_ec, last_time); // transmit science data Science.Update(last_v, last_vi, last_vd, last_resources, last_time); // apply rules Profile.Execute(last_v, last_vi, last_vd, last_resources, last_time); // simulate modules in background Background.Update(last_v, last_vi, last_vd, last_resources, last_time); // apply deferred requests last_resources.Sync(last_v, last_time); // call automation scripts last_vd.computer.Automate(last_v, last_vi, last_resources); // remove from unloaded data container unloaded.Remove(last_vi.id); } // 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 static double Evaluate(Vessel v, Vessel_info vi, Vessel_resources resources, List<string> modifiers) { double k = 1.0; foreach (string mod in modifiers) { switch (mod) { case "breathable": k *= vi.breathable ? 0.0 : 1.0; break; case "temperature": k *= vi.temp_diff; break; case "radiation": k *= vi.radiation; break; case "shielding": k *= 1.0 - vi.shielding; break; case "volume": k *= vi.volume; break; case "surface": k *= vi.surface; break; case "living_space": k /= vi.living_space; break; case "comfort": k /= vi.comforts.factor; break; case "pressure": k *= vi.pressure > Settings.PressureThreshold ? 1.0 : Settings.PressureFactor; break; case "poisoning": k *= vi.poisoning > Settings.PoisoningThreshold ? 1.0 : Settings.PoisoningFactor; break; case "humidity": k *= vi.humidity > Settings.HumidityThreshold ? 1.0 : Settings.HumidityFactor; break; case "per_capita": k /= (double)Math.Max(vi.crew_count, 1); break; default: k *= resources.Info(v, mod).amount; break; } } return k; }
private static string TestForResources(Vessel v, List <KeyValuePair <string, double> > defs, double elapsed_s, Vessel_resources res) { if (defs.Count < 1) { return(string.Empty); } // test if there are enough resources on the vessel foreach (var p in defs) { var ri = res.Info(v, p.Key); if (ri.amount < p.Value * elapsed_s) { return("missing " + ri.resource_name); } } return(string.Empty); }
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, "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 Resource_recipe recipe = new Resource_recipe(part, "greenhouse"); foreach (ModuleResource input in resHandler.inputResources) { // WasteAtmosphere is primary combined input if (WACO2 && input.name == "WasteAtmosphere") { recipe.Input(input.name, vi.breathable ? 0.0 : input.rate * Kerbalism.elapsed_s, "CarbonDioxide"); } // CarbonDioxide is secondary combined input else if (WACO2 && input.name == "CarbonDioxide") { recipe.Input(input.name, vi.breathable ? 0.0 : input.rate * Kerbalism.elapsed_s, ""); } // if atmosphere is breathable disable WasteAtmosphere / CO2 else if (!WACO2 && (input.name == "CarbonDioxide" || input.name == "WasteAtmosphere")) { recipe.Input(input.name, vi.breathable ? 0.0 : input.rate, ""); } else { recipe.Input(input.name, input.rate * Kerbalism.elapsed_s); } } foreach (ModuleResource output in resHandler.outputResources) { // if atmosphere is breathable disable Oxygen if (output.name == "Oxygen") { recipe.Output(output.name, vi.breathable ? 0.0 : output.rate * Kerbalism.elapsed_s, true); } else { 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; 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 || Cache.VesselInfo(vessel).breathable) { continue; // skip if already checked or atmosphere is breathable } if (WACO2) { if (resources.Info(vessel, "WasteAtmosphere").amount <= double.Epsilon && resources.Info(vessel, "CarbonDioxide").amount <= double.Epsilon) { inputs = false; missing_res = "CarbonDioxide"; break; } dis_WACO2 = true; continue; } } 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; } }
void ToEVA(GameEvents.FromToAction <Part, Part> data) { Cache.PurgeObjects(data.from.vessel); Cache.PurgeObjects(data.to.vessel); // get total crew in the origin vessel double tot_crew = Lib.CrewCount(data.from.vessel) + 1.0; // get vessel resources handler Vessel_resources resources = ResourceCache.Get(data.from.vessel); // setup supply resources capacity in the eva kerbal Profile.SetupEva(data.to); 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.Info(data.from.vessel, res.resourceName).amount / tot_crew, res.maxAmount); // remove resource from vessel quantity = data.from.RequestResource(res.resourceName, quantity); // add resource to eva kerbal data.to.RequestResource(res.resourceName, -quantity); } // 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", PreferencesLifeSupport.Instance.evaAtmoLoss, "airlock"); // 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, Lib.BuildString("There isn't any <b>", prop_name, "</b> in the EVA suit"), "Don't let the ladder go!"); } // turn off headlamp light, to avoid stock bug that show them for a split second when going on eva KerbalEVA kerbal = data.to.FindModuleImplementing <KerbalEVA>(); EVA.HeadLamps(kerbal, false); // execute script DB.Vessel(data.from.vessel).computer.Execute(data.from.vessel, ScriptType.eva_out); }
// consume EC for transmission, and transmit science data public static void Update(Vessel v, Vessel_info vi, VesselData vd, Vessel_resources resources, double elapsed_s) { // do nothing if science system is disabled if (!Features.Science) { return; } // avoid corner-case when RnD isn't live during scene changes // - this avoid losing science if the buffer reach threshold during a scene change if (HighLogic.CurrentGame.Mode != Game.Modes.SANDBOX && ResearchAndDevelopment.Instance == null) { return; } // get connection info ConnectionInfo conn = vi.connection; if (conn == null) { return; } if (String.IsNullOrEmpty(vi.transmitting)) { return; } Drive warpCache = Cache.WarpCache(v); bool isWarpCache = false; double transmitSize = conn.rate * elapsed_s; while (warpCache.files.Count > 0 || // transmit EVERYTHING in the cache, regardless of transmitSize. (transmitSize > double.Epsilon && !String.IsNullOrEmpty(vi.transmitting))) { // get filename of data being downloaded var exp_filename = vi.transmitting; if (string.IsNullOrEmpty(exp_filename)) { break; } Drive drive = null; if (warpCache.files.ContainsKey(exp_filename)) { drive = warpCache; isWarpCache = true; } else { drive = FindDrive(v, exp_filename); isWarpCache = false; } if (drive == null) { break; } File file = drive.files[exp_filename]; if (isWarpCache) { file.buff = file.size; file.size = 0; transmitSize -= file.size; } else { if (transmitSize < double.Epsilon) { break; } // determine how much data is transmitted double transmitted = Math.Min(file.size, transmitSize); transmitSize -= transmitted; // consume data in the file file.size -= transmitted; // accumulate in the buffer file.buff += transmitted; } // special case: file size on drive = 0 -> buffer is 0, so no need to do anyhting. just delete. if (file.buff > double.Epsilon) { // this is the science value remaining for this experiment var remainingValue = Value(exp_filename, 0); // this is the science value of this sample double dataValue = Value(exp_filename, file.buff); bool doCredit = file.size <= double.Epsilon || dataValue > buffer_science_value;; // if buffer science value is high enough or file was transmitted completely if (doCredit) { var totalValue = TotalValue(exp_filename); // collect the science data Credit(exp_filename, file.buff, true, v.protoVessel); // reset the buffer file.buff = 0.0; // this was the last useful bit, there is no more value in the experiment if (remainingValue >= 0.1 && remainingValue - dataValue < 0.1) { Message.Post( Lib.BuildString(Lib.HumanReadableScience(totalValue), " ", Experiment(exp_filename).FullName(exp_filename), " completed"), Lib.TextVariant( "Our researchers will jump on it right now", "This cause some excitement", "These results are causing a brouhaha in R&D", "Our scientists look very confused", "The scientists won't believe these readings" )); } } } // if file was transmitted completely if (file.size <= double.Epsilon) { // remove the file drive.files.Remove(exp_filename); vi.transmitting = Science.Transmitting(v, true); } } }
private static bool DoRecord(Experiment experiment, string subject_id, Vessel vessel, Resource_info ec, uint hdId, Vessel_resources resources, List <KeyValuePair <string, double> > resourceDefs, double remainingSampleMass, double dataSampled, out double sampledOut, out double remainingSampleMassOut) { var exp = Science.Experiment(subject_id); if (Done(exp, dataSampled)) { sampledOut = dataSampled; remainingSampleMassOut = remainingSampleMass; return(true); } double elapsed = Kerbalism.elapsed_s; double chunkSize = Math.Min(experiment.data_rate * elapsed, exp.max_amount); double massDelta = experiment.sample_mass * chunkSize / exp.max_amount; Drive drive = GetDrive(experiment, vessel, hdId, chunkSize, subject_id); // on high time warp this chunk size could be too big, but we could store a sizable amount if we process less bool isFile = experiment.sample_mass < float.Epsilon; double maxCapacity = isFile ? drive.FileCapacityAvailable() : drive.SampleCapacityAvailable(subject_id); if (maxCapacity < chunkSize) { double factor = maxCapacity / chunkSize; chunkSize *= factor; massDelta *= factor; elapsed *= factor; } foreach (var p in resourceDefs) { resources.Consume(vessel, p.Key, p.Value * elapsed, "experiment"); } bool stored = false; if (isFile) { stored = drive.Record_file(subject_id, chunkSize, true); } else { stored = drive.Record_sample(subject_id, chunkSize, massDelta); } if (stored) { // consume ec ec.Consume(experiment.ec_rate * elapsed, "experiment"); dataSampled += chunkSize; dataSampled = Math.Min(dataSampled, exp.max_amount); sampledOut = dataSampled; if (!experiment.sample_collecting) { remainingSampleMass -= massDelta; remainingSampleMass = Math.Max(remainingSampleMass, 0); } remainingSampleMassOut = remainingSampleMass; return(true); } sampledOut = dataSampled; remainingSampleMassOut = remainingSampleMass; return(false); }