static void ProcessRadioisotopeGenerator(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule radioisotope_generator, ResourceInfo ec, double elapsed_s) { // note: doesn't support easy mode double power = Lib.ReflectionValue <float>(radioisotope_generator, "BasePower"); double half_life = Lib.ReflectionValue <float>(radioisotope_generator, "HalfLife"); double mission_time = v.missionTime / (3600.0 * Lib.HoursInDay * Lib.DaysInYear); double remaining = Math.Pow(2.0, (-mission_time) / half_life); ec.Produce(power * remaining * elapsed_s, ResourceBroker.RTG); }
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); }
static void ProcessLight(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleLight light, ResourceInfo ec, double elapsed_s) { if (light.useResources && Lib.Proto.GetBool(m, "isOn")) { ec.Consume(light.resourceAmount * elapsed_s, ResourceBroker.Light); } }
/* * static void ProcessScanner(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule scanner, Part part_prefab, VesselData vd, ResourceInfo ec, double elapsed_s) * { * // get ec consumption rate * double power = SCANsat.EcConsumption(scanner); * * // if the scanner doesn't require power to operate, we aren't interested in simulating it * if (power <= double.Epsilon) return; * * // get scanner state * bool is_scanning = Lib.Proto.GetBool(m, "scanning"); * * // if its scanning * if (is_scanning) * { * // consume ec * ec.Consume(power * elapsed_s, "scanner"); * * // if there isn't ec * // - comparing against amount in previous simulation step * if (ec.amount <= double.Epsilon) * { * // unregister scanner * SCANsat.StopScanner(v, m, part_prefab); * is_scanning = false; * * // remember disabled scanner * vd.scansat_id.Add(p.flightID); * * // give the user some feedback * if (vd.cfg_ec) Message.Post(Lib.BuildString("SCANsat sensor was disabled on <b>", v.vesselName, "</b>")); * } * } * // if it was disabled in background * else if (vd.scansat_id.Contains(p.flightID)) * { * // if there is enough ec * // note: comparing against amount in previous simulation step * // re-enable at 25% EC * if (ec.level > 0.25) * { * // re-enable the scanner * SCANsat.ResumeScanner(v, m, part_prefab); * is_scanning = true; * * // give the user some feedback * if (vd.cfg_ec) Message.Post(Lib.BuildString("SCANsat sensor resumed operations on <b>", v.vesselName, "</b>")); * } * } * * // forget active scanners * if (is_scanning) vd.scansat_id.Remove(p.flightID); * } */ static void ProcessFissionGenerator(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule fission_generator, ResourceInfo ec, double elapsed_s) { // note: ignore heat double power = Lib.ReflectionValue <float>(fission_generator, "PowerGeneration"); var reactor = p.modules.Find(k => k.moduleName == "FissionReactor"); double tweakable = reactor == null ? 1.0 : Lib.ConfigValue(reactor.moduleValues, "CurrentPowerPercent", 100.0) * 0.01; ec.Produce(power * tweakable * elapsed_s, ResourceBroker.FissionReactor); }
static void ProcessFNGenerator(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, PartModule fission_generator, ResourceInfo ec, double elapsed_s) { string maxPowerStr = Lib.Proto.GetString(m, "MaxPowerStr"); double maxPower = 0; if (maxPowerStr.Contains("GW")) { maxPower = double.Parse(maxPowerStr.Replace(" GW", "")) * 1000000; } else if (maxPowerStr.Contains("MW")) { maxPower = double.Parse(maxPowerStr.Replace(" MW", "")) * 1000; } else { maxPower = double.Parse(maxPowerStr.Replace(" KW", "")); } ec.Produce(maxPower * elapsed_s, ResourceBroker.KSPIEGenerator); }
static void ProcessStockLab(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleScienceConverter lab, ResourceInfo ec, double elapsed_s) { // note: we are only simulating the EC consumption // note: there is no easy way to 'stop' the lab when there isn't enough EC // if active if (Lib.Proto.GetBool(m, "IsActivated")) { // consume ec ec.Consume(lab.powerRequirement * elapsed_s, ResourceBroker.ScienceLab); } }
// 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>")); } } }
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.APIModule: ProcessApiModule(v, e.p, e.m, e.part_prefab, e.module_prefab, resources, availableResources, resourceChangeRequests, elapsed_s); break; } } }
// trigger a random breakdown event public static void Breakdown(Vessel v, ProtoCrewMember c) { // constants const double res_penalty = 0.1; // proportion of food lost on 'depressed' and 'wrong_valve' // get a supply resource at random ResourceInfo res = null; if (Profile.supplies.Count > 0) { Supply supply = Profile.supplies[Lib.RandomInt(Profile.supplies.Count)]; res = ResourceCache.GetResource(v, supply.resource); } // compile list of events with condition satisfied List <KerbalBreakdown> events = new List <KerbalBreakdown> { KerbalBreakdown.mumbling //< do nothing, here so there is always something that can happen }; if (Lib.HasData(v)) { events.Add(KerbalBreakdown.fat_finger); } if (Reliability.CanMalfunction(v)) { events.Add(KerbalBreakdown.rage); } if (res != null && res.Amount > double.Epsilon) { events.Add(KerbalBreakdown.wrong_valve); } // choose a breakdown event KerbalBreakdown breakdown = events[Lib.RandomInt(events.Count)]; // generate message string text = ""; string subtext = ""; switch (breakdown) { case KerbalBreakdown.mumbling: text = Local.Kerbalmumbling; //"$ON_VESSEL$KERBAL has been in space for too long" subtext = Local.Kerbalmumbling_subtext; //"Mumbling incoherently" break; case KerbalBreakdown.fat_finger: text = Local.Kerbalfatfinger_subtext; //"$ON_VESSEL$KERBAL is pressing buttons at random on the control panel" subtext = Local.Kerbalfatfinger_subtext; //"Science data has been lost" break; case KerbalBreakdown.rage: text = Local.Kerbalrage; //"$ON_VESSEL$KERBAL is possessed by a blind rage" subtext = Local.Kerbalrage_subtext; //"A component has been damaged" break; case KerbalBreakdown.wrong_valve: text = Local.Kerbalwrongvalve; //"$ON_VESSEL$KERBAL opened the wrong valve" subtext = res.ResourceName + " " + Local.Kerbalwrongvalve_subtext; //has been lost" break; } // post message first so this one is shown before malfunction message Message.Post(Severity.breakdown, Lib.ExpandMsg(text, v, c), subtext); // trigger the event switch (breakdown) { case KerbalBreakdown.mumbling: break; // do nothing case KerbalBreakdown.fat_finger: Lib.RemoveData(v); break; case KerbalBreakdown.rage: Reliability.CauseMalfunction(v); break; case KerbalBreakdown.wrong_valve: res.Consume(res.Amount * res_penalty, ResourceBroker.Generic); break; } // remove reputation if (HighLogic.CurrentGame.Mode == Game.Modes.CAREER) { Reputation.Instance.AddReputation(-Settings.KerbalBreakdownReputationPenalty, TransactionReasons.Any); } }
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 static void Update(Vessel v, VesselData vd, ResourceInfo ec, double elapsed_s) { if (!Lib.IsVessel(v)) { return; } // EC consumption is handled in Science update Cache.WarpCache(v).dataCapacity = vd.deviceTransmit ? vd.Connection.rate * elapsed_s : 0.0; // do nothing if network is not ready if (!NetworkInitialized) { return; } // maintain and send messages // - do not send messages during/after solar storms // - do not send messages for EVA kerbals if (!v.isEVA && v.situation != Vessel.Situations.PRELAUNCH) { if (!vd.msg_signal && !vd.Connection.linked) { vd.msg_signal = true; if (vd.cfg_signal) { string subtext = Localizer.Format("#KERBALISM_UI_transmissiondisabled"); switch (vd.Connection.status) { case LinkStatus.plasma: subtext = Localizer.Format("#KERBALISM_UI_Plasmablackout"); break; case LinkStatus.storm: subtext = Localizer.Format("#KERBALISM_UI_Stormblackout"); break; default: if (vd.CrewCount == 0) { switch (Settings.UnlinkedControl) { case UnlinkedCtrl.none: subtext = Localizer.Format("#KERBALISM_UI_noctrl"); break; case UnlinkedCtrl.limited: subtext = Localizer.Format("#KERBALISM_UI_limitedcontrol"); break; } } break; } Message.Post(Severity.warning, Lib.BuildString(Localizer.Format("#KERBALISM_UI_signallost"), " <b>", v.vesselName, "</b>"), subtext); } } else if (vd.msg_signal && vd.Connection.linked) { vd.msg_signal = false; if (vd.cfg_signal) { Message.Post(Severity.relax, Lib.BuildString("<b>", v.vesselName, "</b> ", Localizer.Format("#KERBALISM_UI_signalback")), vd.Connection.status == LinkStatus.direct_link ? Localizer.Format("#KERBALISM_UI_directlink") : Lib.BuildString(Localizer.Format("#KERBALISM_UI_relayby"), " <b>", vd.Connection.target_name, "</b>")); } } } }
public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, GravityRing ring, ResourceInfo ec, double elapsed_s) { // if the module is either non-deployable or deployed if (ring.deploy.Length == 0 || Lib.Proto.GetBool(m, "deployed")) { // consume ec ec.Consume(ring.ec_rate * elapsed_s, ResourceBroker.GravityRing); } }
// This Method has a lot of "For\Foreach" because it was design for multi resources // Method don't count disabled habitats public static void Equalizer(Vessel v) { // get resource level in habitats double[] res_level = new double[resourceName.Length]; // Don't count Manned or Depressiong habitats // Total resource in parts not disabled double[] totalAmount = new double[resourceName.Length]; double[] maxAmount = new double[resourceName.Length]; // Total resource in Enabled parts (No crew) double[] totalE = new double[resourceName.Length]; double[] maxE = new double[resourceName.Length]; // Total resource in Manned parts (Priority!) double[] totalP = new double[resourceName.Length]; double[] maxP = new double[resourceName.Length]; // Total resource in Depressurizing double[] totalD = new double[resourceName.Length]; double[] maxD = new double[resourceName.Length]; // amount to equalize speed double[] amount = new double[resourceName.Length]; // Can be positive or negative, controlling the resource flow double flowController; bool[] mannedisPriority = new bool[resourceName.Length]; // The resource is priority bool equalize = false; // Has any resource that needs to be equalized // intial value for (int i = 0; i < resourceName.Length; i++) { totalAmount[i] = new ResourceInfo(v, resourceName[i]).Rate; // Get generate rate for each resource maxAmount[i] = 0; totalE[i] = 0; maxE[i] = 0; totalP[i] = 0; maxP[i] = 0; totalD[i] = 0; maxD[i] = 0; mannedisPriority[i] = false; } foreach (Habitat partHabitat in v.FindPartModulesImplementing <Habitat>()) { // Skip disabled habitats if (partHabitat.state != Habitat.State.disabled) { // Has flag to be Equalized? equalize |= partHabitat.needEqualize; PartResource[] resources = new PartResource[resourceName.Length]; for (int i = 0; i < resourceName.Length; i++) { if (partHabitat.part.Resources.Contains(resourceName[i])) { PartResource t = partHabitat.part.Resources[resourceName[i]]; // Manned Amounts if (Lib.IsCrewed(partHabitat.part)) { totalP[i] += t.amount; maxP[i] += t.maxAmount; } // Amount for Depressurizing else if (partHabitat.state == Habitat.State.depressurizing) { totalD[i] += t.amount; maxD[i] += t.maxAmount; } else { totalE[i] += t.amount; maxE[i] += t.maxAmount; } totalAmount[i] += t.amount; maxAmount[i] += t.maxAmount; } } } } if (!equalize) { return; } for (int i = 0; i < resourceName.Length; i++) { // resource level for Enabled habitats no Manned res_level[i] = totalE[i] / (maxAmount[i] - maxP[i]); // Manned is priority? // If resource amount is less then maxAmount in manned habitat and it's flagged to equalize, define as priority // Using Atmosphere, N2, O2 as Priority trigger (we don't want to use CO2 as a trigger) if (resourceName[i] != "WasteAtmosphere" && equalize) { mannedisPriority[i] = maxP[i] - totalP[i] > 0; } // determine generic equalization speed per resource if (mannedisPriority[i]) { amount[i] = maxAmount[i] * equalize_speed * Kerbalism.elapsed_s; } else { amount[i] = (maxE[i] + maxD[i]) * equalize_speed * Kerbalism.elapsed_s; } } if (equalize) { foreach (Habitat partHabitat in v.FindPartModulesImplementing <Habitat>()) { bool stillNeed = false; if (partHabitat.state != Habitat.State.disabled) { for (int i = 0; i < resourceName.Length; i++) { if (partHabitat.part.Resources.Contains(resourceName[i])) { PartResource t = partHabitat.part.Resources[resourceName[i]]; flowController = 0; // Conditions in order // If perctToMax = 0 (means Habitat will have 0% of amount: // 1 case: modules still needs to be equalized // 2 case: has depressurizing habitat // 3 case: dropping everything into the priority habitats if ((Math.Abs(res_level[i] - (t.amount / t.maxAmount)) > precision && !Lib.IsCrewed(partHabitat.part)) || ((partHabitat.state == Habitat.State.depressurizing || mannedisPriority[i]) && t.amount > double.Epsilon)) { double perctToAll; // Percent of resource for this habitat related double perctRest; // Percent to fill priority perctToAll = t.amount / maxAmount[i]; double perctToType; double perctToMaxType; // Percts per Types if (Lib.IsCrewed(partHabitat.part)) { perctToType = t.amount / totalP[i]; perctToMaxType = t.maxAmount / maxP[i]; } else if (partHabitat.state == Habitat.State.depressurizing) { perctToType = t.amount / totalD[i]; perctToMaxType = t.maxAmount / maxD[i]; } else { perctToType = t.amount / totalE[i]; perctToMaxType = t.maxAmount / maxE[i]; } // Perct from the left resource if (totalAmount[i] - maxP[i] <= 0 || partHabitat.state == Habitat.State.depressurizing) { perctRest = 0; } else { perctRest = (((totalAmount[i] - maxP[i]) * perctToMaxType) - t.amount) / totalE[i]; } // perctToMax < perctToAll ? habitat will send resource : otherwise will receive, flowController == 0 means no flow if ((partHabitat.state == Habitat.State.depressurizing || totalAmount[i] - maxP[i] <= 0) && !Lib.IsCrewed(partHabitat.part)) { flowController = 0 - perctToType; } else if (mannedisPriority[i] && !Lib.IsCrewed(partHabitat.part)) { flowController = Math.Min(perctToMaxType - perctToAll, (t.maxAmount - t.amount) / totalAmount[i]); } else { flowController = perctRest; } // clamp amount to what's available in the hab and what can fit in the part double amountAffected; if (partHabitat.state == Habitat.State.depressurizing) { amountAffected = flowController * totalD[i]; } else if (!mannedisPriority[i] || !Lib.IsCrewed(partHabitat.part)) { amountAffected = flowController * totalE[i]; } else { amountAffected = flowController * totalP[i]; } amountAffected *= equalize_speed; amountAffected = Math.Sign(amountAffected) >= 0 ? Math.Max(Math.Sign(amountAffected) * precision, amountAffected) : Math.Min(Math.Sign(amountAffected) * precision, amountAffected); double va = amountAffected <0.0 ? Math.Abs(amountAffected)> t.amount // If negative, habitat can't send more than it has ? t.amount * (-1) : amountAffected : Math.Min(amountAffected, t.maxAmount - t.amount); // if positive, habitat can't receive more than max va = Double.IsNaN(va) ? 0.0 : va; // consume relative percent of this part t.amount += va; if (va < double.Epsilon) { stillNeed = false; } else { stillNeed = true; } } } } } partHabitat.needEqualize = stillNeed; } } }
/// <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); }
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("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>no change</b>"); } if (res.AverageRate < 0.0 && res.Level < 0.0001) { sb.Append(" <i>(empty)</i>"); } else if (res.AverageRate > 0.0 && res.Level > 0.9999) { sb.Append(" <i>(full)</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.ResourceBroker> brokers = vd.Supply(supply.resource).ResourceBrokers; if (brokers.Count > 0) { sb.Append("\n<b>------------ \t------------</b>"); foreach (SupplyData.ResourceBroker 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.name); } } string rate_tooltip = sb.ToString(); // finally, render resource supply p.AddContent(label, Lib.HumanReadableDuration(res.DepletionTime()), rate_tooltip); ++supplies; } }