// constructor public Kerbalism() { // enable global access Fetch = this; Communications.NetworkInitialized = false; RemoteTech.NetworkInitialized = false; }
// constructor public Kerbalism() { // enable global access Fetch = this; Communications.NetworkInitialized = false; Communications.NetworkInitializing = false; SerenityEnabled = Expansions.ExpansionsLoader.IsExpansionInstalled("Serenity"); }
private void OnDestroy() { Fetch = null; }
// implement radiation mechanics public void FixedUpdate() { // avoid case when DB isn't ready for whatever reason if (!DB.Ready()) { return; } // do nothing in the editors and the menus if (!Lib.SceneIsGame()) { return; } // do nothing if paused if (Lib.IsPaused()) { return; } // get time elapsed from last update double elapsed_s = TimeWarp.fixedDeltaTime; // for each vessel foreach (Vessel v in FlightGlobals.Vessels) { // skip invalid vessels if (!Lib.IsVessel(v)) { continue; } // skip dead eva kerbals if (EVA.IsDead(v)) { continue; } // get crew List <ProtoCrewMember> crew = v.loaded ? v.GetVesselCrew() : v.protoVessel.GetVesselCrew(); // get crew count int crew_count = Lib.CrewCount(v); // get vessel info from the cache vessel_info info = Cache.VesselInfo(v); // get vessel data vessel_data vd = DB.VesselData(v.id); // belt warnings // note: we only show it for manned vesssels, but the first time we also show it for probes if (crew_count > 0 || DB.NotificationData().first_belt_crossing == 0) { if (InsideBelt(v) && vd.msg_belt < 1) { Message.Post("<b>" + v.vesselName + "</b> is crossing <i>" + v.mainBody.bodyName + " radiation belt</i>", "Exposed to extreme radiation"); vd.msg_belt = 1; DB.NotificationData().first_belt_crossing = 1; //< record first belt crossing } else if (!InsideBelt(v) && vd.msg_belt > 0) { // no message after crossing the belt vd.msg_belt = 0; } } // for each crew foreach (ProtoCrewMember c in crew) { // get kerbal data kerbal_data kd = DB.KerbalData(c.name); // skip resque kerbals if (kd.resque == 1) { continue; } // skip disabled kerbals if (kd.disabled == 1) { continue; } // accumulate radiation kd.radiation += info.radiation * elapsed_s; // kill kerbal if necessary if (kd.radiation >= Settings.RadiationFatalThreshold) { Message.Post(Severity.fatality, KerbalEvent.radiation, v, c); Kerbalism.Kill(v, c); } // show warnings else if (kd.radiation >= Settings.RadiationDangerThreshold && kd.msg_radiation < 2) { Message.Post(Severity.danger, KerbalEvent.radiation, v, c); kd.msg_radiation = 2; } else if (kd.radiation >= Settings.RadiationWarningThreshold && kd.msg_radiation < 1) { Message.Post(Severity.danger, KerbalEvent.radiation, v, c); kd.msg_radiation = 1; } // note: no recovery from radiations } } }
public static void applyRules(Vessel v, vessel_info vi, vessel_data vd, vessel_resources resources, double elapsed_s) { // get crew List<ProtoCrewMember> crew = v.loaded ? v.GetVesselCrew() : v.protoVessel.GetVesselCrew(); // get breathable modifier double breathable = vi.breathable ? 0.0 : 1.0; // get temp diff modifier double temp_diff = v.altitude < 2000.0 && v.mainBody == FlightGlobals.GetHomeBody() ? 0.0 : Sim.TempDiff(vi.temperature); // for each rule foreach(Rule r in Kerbalism.rules) { // get resource handler resource_info res = r.resource_name.Length > 0 ? resources.Info(v, r.resource_name) : null; // if a resource is specified if (res != null) { // get data from db vmon_data vmon = DB.VmonData(v.id, r.name); // message obey user config bool show_msg = (r.resource_name == "ElectricCharge" ? vd.cfg_ec > 0 : vd.cfg_supply > 0); // no messages with no capacity if (res.capacity > double.Epsilon) { // manned/probe message variant uint variant = crew.Count > 0 ? 0 : 1u; // manage messages if (res.level <= double.Epsilon && vmon.message < 2) { if (r.empty_message.Length > 0 && show_msg) Message.Post(Severity.danger, Lib.ExpandMsg(r.empty_message, v, null, variant)); vmon.message = 2; } else if (res.level < r.low_threshold && vmon.message < 1) { if (r.low_message.Length > 0 && show_msg) Message.Post(Severity.warning, Lib.ExpandMsg(r.low_message, v, null, variant)); vmon.message = 1; } else if (res.level > r.low_threshold && vmon.message > 0) { if (r.refill_message.Length > 0 && show_msg) Message.Post(Severity.relax, Lib.ExpandMsg(r.refill_message, v, null, variant)); vmon.message = 0; } } } // for each crew foreach(ProtoCrewMember c in crew) { // get kerbal data kerbal_data kd = DB.KerbalData(c.name); // skip resque kerbals if (kd.resque == 1) continue; // skip disabled kerbals if (kd.disabled == 1) continue; // get supply data from db kmon_data kmon = DB.KmonData(c.name, r.name); // get product of all environment modifiers double k = 1.0; foreach(string modifier in r.modifier) { switch(modifier) { case "breathable": k *= breathable; break; case "temperature": k *= temp_diff; break; case "radiation": k *= vi.radiation * (1.0 - kd.shielding); break; case "qol": k /= QualityOfLife.Bonus(kd.living_space, kd.entertainment, vi.landed, vi.link.linked, vi.crew_count == 1); break; } } // if continuous double step; if (r.interval <= double.Epsilon) { // influence consumption by elapsed time step = elapsed_s; } // if interval-based else { // accumulate time kmon.time_since += elapsed_s; // determine number of steps step = Math.Floor(kmon.time_since / r.interval); // consume time kmon.time_since -= step * r.interval; // remember if a meal is consumed in this simulation step res.meal_consumed |= step > 0.99; } // if continuous, or if one or more intervals elapsed if (step > double.Epsilon) { // indicate if we must degenerate bool must_degenerate = true; // if there is a resource specified, and this isn't just a monitoring rule if (res != null && r.rate > double.Epsilon) { // determine amount of resource to consume double required = r.rate // rate per-second or per interval * k // product of environment modifiers * step; // seconds elapsed or number of steps // if there is no waste if (r.waste_name.Length == 0) { // simply consume (that is faster) res.Consume(required); } // if there is waste else { // transform resource into waste resource_recipe recipe = new resource_recipe(resource_recipe.rule_priority); recipe.Input(r.resource_name, required); recipe.Output(r.waste_name, required * r.waste_ratio); resources.Transform(recipe); } // reset degeneration when consumed, or when not required at all // note: evaluating amount from previous simulation step if (required <= double.Epsilon || res.amount > double.Epsilon) { // slowly recover instead of instant reset kmon.problem *= 1.0 / (1.0 + Math.Max(r.interval, 1.0) * step * 0.002); kmon.problem = Math.Max(kmon.problem, 0.0); // do not degenerate must_degenerate = false; } } // degenerate if this rule is resource-less, or if there was not enough resource in the vessel if (must_degenerate) { kmon.problem += r.degeneration // degeneration rate per-second or per-interval * k // product of environment modifiers * step // seconds elapsed or by number of steps * Variance(c, r.variance); // kerbal-specific variance } // determine message variant uint variant = vi.temperature < Settings.SurvivalTemperature ? 0 : 1u; // kill kerbal if necessary if (kmon.problem >= r.fatal_threshold) { if (r.fatal_message.Length > 0) Message.Post(r.breakdown ? Severity.breakdown : Severity.fatality, Lib.ExpandMsg(r.fatal_message, v, c, variant)); if (r.breakdown) { Kerbalism.Breakdown(v, c); kmon.problem = r.danger_threshold * 1.01; //< move back to danger threshold } else { Kerbalism.Kill(v, c); } } // show messages else if (kmon.problem >= r.danger_threshold && kmon.message < 2) { if (r.danger_message.Length > 0) Message.Post(Severity.danger, Lib.ExpandMsg(r.danger_message, v, c, variant)); kmon.message = 2; } else if (kmon.problem >= r.warning_threshold && kmon.message < 1) { if (r.warning_message.Length > 0) Message.Post(Severity.warning, Lib.ExpandMsg(r.warning_message, v, c, variant)); kmon.message = 1; } else if (kmon.problem < r.warning_threshold && kmon.message > 0) { if (r.relax_message.Length > 0) Message.Post(Severity.relax, Lib.ExpandMsg(r.relax_message, v, c, variant)); kmon.message = 0; } } } } }
// called every simulation step void FixedUpdate() { // do nothing if paused if (Lib.IsPaused()) return; // do nothing in the editors and the menus if (!Lib.SceneIsGame()) return; // do nothing if db isn't ready if (!DB.Ready()) return; // get elapsed time double elapsed_s = Kerbalism.elapsed_s; // 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; vessel_data 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); // skip invalid vessels if (!vi.is_valid) continue; // get vessel data from db vessel_data vd = DB.VesselData(v.id); // get resource cache vessel_resources resources = ResourceCache.Get(v); // if loaded if (v.loaded) { // show belt warnings Radiation.beltWarnings(v, vi, vd); // update storm data storm.update(v, vi, vd, elapsed_s); // consume relay EC and show signal warnings signal.update(v, vi, vd, resources, elapsed_s * vi.time_dilation); // apply rules Rule.applyRules(v, vi, vd, resources, elapsed_s * vi.time_dilation); // apply deferred requests resources.Sync(v, elapsed_s); // update computer vd.computer.update(v, elapsed_s); // 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; } } } // if the oldest unloaded vessel was selected if (last_v != null) { // show belt warnings Radiation.beltWarnings(last_v, last_vi, last_vd); // decay unloaded vessels inside atmosphere Kerbalism.atmosphereDecay(last_v, last_vi, last_time); // update storm data storm.update(last_v, last_vi, last_vd, last_time); // consume relay EC and show signal warnings signal.update(last_v, last_vi, last_vd, last_resources, last_time * last_vi.time_dilation); // apply rules Rule.applyRules(last_v, last_vi, last_vd, last_resources, last_time * last_vi.time_dilation); // simulate modules in background Background.update(last_v, last_vi, last_vd, last_resources, last_time * last_vi.time_dilation); // apply deferred requests last_resources.Sync(last_v, last_time); // update computer last_vd.computer.update(last_v, last_time); // remove from unloaded data container unloaded.Remove(last_vi.id); } // update storm data for one body per-step 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; }
// implement life support mechanics public void FixedUpdate() { // avoid case when DB isn't ready for whatever reason if (!DB.Ready()) return; // do nothing in the editors and the menus if (!Lib.SceneIsGame()) return; // do nothing if paused if (Lib.IsPaused()) return; // get time elapsed from last update double elapsed_s = TimeWarp.fixedDeltaTime; // for each vessel foreach(Vessel v in FlightGlobals.Vessels) { // skip invalid vessels if (!Lib.IsVessel(v)) continue; // skip dead eva kerbals if (EVA.IsDead(v)) continue; // get crew List<ProtoCrewMember> crew = v.loaded ? v.GetVesselCrew() : v.protoVessel.GetVesselCrew(); // get vessel info from the cache vessel_info info = Cache.VesselInfo(v); // get temperature difference // note: for gameplay reasons, climatization doesn't consume anything landed at home bool landed_home = Lib.Landed(v) && v.mainBody == FlightGlobals.GetHomeBody(); double temp_diff = landed_home ? 0.0 : Math.Abs(info.temperature - Settings.SurvivalTemperature); double temp_sign = landed_home ? 1.0 : info.temperature > Settings.SurvivalTemperature ? 1.0 : -1.0; // determine if inside breathable atmosphere bool breathable = BreathableAtmosphere(v); // for each crew foreach(ProtoCrewMember c in crew) { // get kerbal data kerbal_data kd = DB.KerbalData(c.name); // skip resque kerbals if (kd.resque == 1) continue; // skip disabled kerbals if (kd.disabled == 1) continue; // consume ec for climate control double ec_required = temp_diff * Settings.ElectricChargePerSecond * elapsed_s; double ec_consumed = Lib.RequestResource(v, "ElectricCharge", ec_required); double ec_perc = ec_required > 0.0 ? ec_consumed / ec_required : 0.0; // reset kerbal temperature, if necessary if (ec_required <= double.Epsilon || ec_perc >= 1.0 - double.Epsilon) { kd.temperature = 0.0; } else { // degenerate kerbal temperature kd.temperature += Settings.TemperatureDegradationRate * elapsed_s * (1.0 - ec_perc) * temp_diff * temp_sign; // kill kerbal if necessary if (kd.temperature <= -Settings.TemperatureFatalThreshold) { Message.Post(Severity.fatality, KerbalEvent.climate_low, v, c); Kerbalism.Kill(v, c); } else if (kd.temperature >= Settings.TemperatureFatalThreshold) { Message.Post(Severity.fatality, KerbalEvent.climate_high, v, c); Kerbalism.Kill(v, c); } // show warnings else if (kd.temperature <= -Settings.TemperatureDangerThreshold && kd.msg_freezing < 2) { Message.Post(Severity.danger, KerbalEvent.climate_low, v, c); kd.msg_freezing = 2; } else if (kd.temperature <= -Settings.TemperatureWarningThreshold && kd.msg_freezing < 1) { Message.Post(Severity.warning, KerbalEvent.climate_low, v, c); kd.msg_freezing = 1; } else if (kd.temperature > -Settings.TemperatureWarningThreshold && kd.msg_freezing > 0) { Message.Post(Severity.relax, KerbalEvent.climate_low, v, c); kd.msg_freezing = 0; } else if (kd.temperature >= Settings.TemperatureDangerThreshold && kd.msg_burning < 2) { Message.Post(Severity.danger, KerbalEvent.climate_high, v, c); kd.msg_burning = 2; } else if (kd.temperature >= Settings.TemperatureWarningThreshold && kd.msg_burning < 1) { Message.Post(Severity.warning, KerbalEvent.climate_high, v, c); kd.msg_burning = 1; } else if (kd.temperature < Settings.TemperatureWarningThreshold && kd.msg_burning > 0) { Message.Post(Severity.relax, KerbalEvent.climate_high, v, c); kd.msg_burning = 0; } } // if its meal time for this kerbal kd.time_since_food += elapsed_s; if (kd.time_since_food >= Settings.MealFrequency) { // consume food const double food_required = Settings.FoodPerMeal; double food_consumed = Lib.RequestResource(v, "Food", food_required); double food_perc = food_consumed / food_required; // reset kerbal starvation, if necessary if (food_perc >= 1.0 - double.Epsilon) { kd.starved = 0.0; kd.time_since_food = 0.0; } else { // assure piecewise consumption Lib.RequestResource(v, "Food", -food_consumed); food_consumed = 0.0; // degenerate kerbal starvation kd.starved += Settings.StarvedDegradationRate * elapsed_s; // kill kerbal if necessary if (kd.starved >= Settings.StarvedFatalThreshold) { Message.Post(Severity.fatality, KerbalEvent.food, v, c); Kerbalism.Kill(v, c); } // show warnings else if (kd.starved >= Settings.StarvedDangerThreshold && kd.msg_starved < 2) { Message.Post(Severity.danger, KerbalEvent.food, v, c); kd.msg_starved = 2; } else if (kd.starved >= Settings.StarvedWarningThreshold && kd.msg_starved < 1) { Message.Post(Severity.warning, KerbalEvent.food, v, c); kd.msg_starved = 1; } else if (kd.starved < Settings.StarvedWarningThreshold && kd.msg_starved > 0) { Message.Post(Severity.relax, KerbalEvent.food, v, c); kd.msg_starved = 0; } } // produce waste Lib.RequestResource(v, "Waste", -food_consumed); } // if not inside a breathable atmosphere if (!breathable) { // consume oxygen double oxygen_required = Settings.OxygenPerSecond * elapsed_s; double oxygen_consumed = Lib.RequestResource(v, "Oxygen", oxygen_required); double oxygen_perc = oxygen_consumed / oxygen_required; // reset kerbal deprivation, if necessary if (oxygen_perc >= 1.0 - double.Epsilon) { kd.deprived = 0.0; } else { // degenerate kerbal deprivation kd.deprived += Settings.DeprivedDegradationRate * elapsed_s * (1.0 - oxygen_perc); // kill kerbal if necessary if (kd.deprived >= Settings.DeprivedFatalThreshold) { Message.Post(Severity.fatality, KerbalEvent.oxygen, v, c); Kerbalism.Kill(v, c); } // show warnings else if (kd.deprived >= Settings.DeprivedDangerThreshold && kd.msg_deprived < 2) { Message.Post(Severity.danger, KerbalEvent.oxygen, v, c); kd.msg_deprived = 2; } else if (kd.deprived >= Settings.DeprivedWarningThreshold && kd.msg_deprived < 1) { Message.Post(Severity.warning, KerbalEvent.oxygen, v, c); kd.msg_deprived = 1; } else if (kd.deprived < Settings.DeprivedWarningThreshold && kd.msg_deprived > 0) { Message.Post(Severity.relax, KerbalEvent.oxygen, v, c); kd.msg_deprived = 0; } } // produce CO2 Lib.RequestResource(v, "CO2", -oxygen_consumed); } } } }