Example #1
0
 // return proportion of radiation blocked by shielding
 public static double Shielding(Vessel v)
 {
     return(Cache.VesselInfo(v).shielding);
 }
Example #2
0
        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);
        }
Example #3
0
        public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, KerbalismScansat kerbalismScansat,
                                            Part part_prefab, VesselData vd, ResourceInfo ec, double elapsed_s)
        {
            List <ProtoPartModuleSnapshot> scanners = Cache.VesselObjectsCache <List <ProtoPartModuleSnapshot> >(vessel, "scansat_" + p.flightID);

            if (scanners == null)
            {
                scanners = Lib.FindModules(p, "SCANsat");
                if (scanners.Count == 0)
                {
                    scanners = Lib.FindModules(p, "ModuleSCANresourceScanner");
                }
                Cache.SetVesselObjectsCache(vessel, "scansat_" + p.flightID, scanners);
            }

            if (scanners.Count == 0)
            {
                return;
            }
            var scanner = scanners[0];

            bool is_scanning = Lib.Proto.GetBool(scanner, "scanning");

            if (is_scanning && kerbalismScansat.ec_rate > double.Epsilon)
            {
                ec.Consume(kerbalismScansat.ec_rate * elapsed_s, "scanner");
            }

            if (!Features.Science)
            {
                if (is_scanning && ec.Amount < double.Epsilon)
                {
                    SCANsat.StopScanner(vessel, scanner, 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>", vessel.vesselName, "</b>"));
                    }
                }
                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(vessel, 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>", vessel.vesselName, "</b>"));
                        }
                    }
                }

                // forget active scanners
                if (is_scanning)
                {
                    vd.scansat_id.Remove(p.flightID);
                }

                return;
            }             // if(!Feature.Science)

            string body_name     = Lib.Proto.GetString(m, "body_name");
            int    sensorType    = (int)Lib.Proto.GetUInt(m, "sensorType");
            double body_coverage = Lib.Proto.GetDouble(m, "body_coverage");
            double warp_buffer   = Lib.Proto.GetDouble(m, "warp_buffer");

            double new_coverage = SCANsat.Coverage(sensorType, vessel.mainBody);

            if (body_name == vessel.mainBody.name && new_coverage < body_coverage)
            {
                // SCANsat sometimes reports a coverage of 0, which is wrong
                new_coverage = body_coverage;
            }

            if (vessel.mainBody.name != body_name)
            {
                body_name     = vessel.mainBody.name;
                body_coverage = new_coverage;
            }
            else
            {
                double coverage_delta = new_coverage - body_coverage;
                body_coverage = new_coverage;

                if (is_scanning)
                {
                    ExperimentInfo expInfo = ScienceDB.GetExperimentInfo(kerbalismScansat.experimentType);
                    SubjectData    subject = ScienceDB.GetSubjectData(expInfo, vd.VesselSituations.GetExperimentSituation(expInfo));
                    if (subject == null)
                    {
                        return;
                    }

                    double size = expInfo.DataSize * coverage_delta / 100.0;                     // coverage is 0-100%
                    size += warp_buffer;

                    if (size > double.Epsilon)
                    {
                        // store what we can
                        foreach (var d in Drive.GetDrives(vd))
                        {
                            var available = d.FileCapacityAvailable();
                            var chunk     = Math.Min(size, available);
                            if (!d.Record_file(subject, chunk, true))
                            {
                                break;
                            }
                            size -= chunk;

                            if (size < double.Epsilon)
                            {
                                break;
                            }
                        }
                    }

                    if (size > double.Epsilon)
                    {
                        // we filled all drives up to the brim but were unable to store everything
                        if (warp_buffer < double.Epsilon)
                        {
                            // warp buffer is empty, so lets store the rest there
                            warp_buffer = size;
                            size        = 0;
                        }
                        else
                        {
                            // warp buffer not empty. that's ok if we didn't get new data
                            if (coverage_delta < double.Epsilon)
                            {
                                size = 0;
                            }
                            // else we're scanning too fast. stop.
                        }
                    }

                    // we filled all drives up to the brim but were unable to store everything
                    // cancel scanning and annoy the user
                    if (size > double.Epsilon || ec.Amount < double.Epsilon)
                    {
                        warp_buffer = 0;
                        SCANsat.StopScanner(vessel, scanner, part_prefab);
                        vd.scansat_id.Add(p.flightID);
                        if (vd.cfg_ec)
                        {
                            Message.Post(Lib.BuildString("SCANsat sensor was disabled on <b>", vessel.vesselName, "</b>"));
                        }
                    }
                }
                else if (vd.scansat_id.Contains(p.flightID))
                {
                    if (ec.Level >= 0.25 && (vd.DrivesFreeSpace / vd.DrivesCapacity > 0.9))
                    {
                        SCANsat.ResumeScanner(vessel, scanner, part_prefab);
                        vd.scansat_id.Remove(p.flightID);
                        if (vd.cfg_ec)
                        {
                            Message.Post(Lib.BuildString("SCANsat sensor resumed operations on <b>", vessel.vesselName, "</b>"));
                        }
                    }
                }
            }

            Lib.Proto.Set(m, "warp_buffer", warp_buffer);
            Lib.Proto.Set(m, "body_coverage", body_coverage);
            Lib.Proto.Set(m, "body_name", body_name);
        }
Example #4
0
        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);

                // 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);

                // transmit science	data
                Science.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;
            }
        }
Example #5
0
        public override void OnLoad(ConfigNode node)
        {
            // deserialize data
            DB.Load(node);

            Communications.NetworkInitialized  = false;
            Communications.NetworkInitializing = false;

            // initialize everything just once
            if (!initialized)
            {
                // add supply resources to pods
                Profile.SetupPods();

                // initialize subsystems
                Cache.Init();
                ResourceCache.Init();
                Radiation.Init();
                Science.Init();
                LineRenderer.Init();
                ParticleRenderer.Init();
                Highlighter.Init();
                UI.Init();

#if !KSP170 && !KSP16 && !KSP15 && !KSP14
                Serenity.Init();
#endif

                // prepare storm data
                foreach (CelestialBody body in FlightGlobals.Bodies)
                {
                    if (Storm.Skip_body(body))
                    {
                        continue;
                    }
                    Storm_data sd = new Storm_data
                    {
                        body = body
                    };
                    storm_bodies.Add(sd);
                }

                // various tweaks to the part icons in the editor
                Misc.TweakPartIcons();

                // setup callbacks
                callbacks = new Callbacks();

                // everything was initialized
                initialized = true;
            }

            // detect if this is a different savegame
            if (DB.uid != savegame_uid)
            {
                // clear caches
                Cache.Clear();
                ResourceCache.Clear();
                Message.all_logs.Clear();

                // sync main window pos from db
                UI.Sync();

                // remember savegame id
                savegame_uid = DB.uid;
            }

            Science.ClearDeferred();
        }
Example #6
0
        // --- ENVIRONMENT ----------------------------------------------------------

        // return true if the vessel specified is in sunlight
        public static bool InSunlight(Vessel v)
        {
            return(Cache.VesselInfo(v).sunlight > double.Epsilon);
        }
Example #7
0
        public void FixedUpdate()
        {
            // do nothing in the editor
            if (Lib.IsEditor())
            {
                return;
            }

            // do nothing if vessel is invalid
            if (!Cache.VesselInfo(vessel).is_valid)
            {
                return;
            }

            // get ec handler
            Resource_info ec = ResourceCache.Info(vessel, "ElectricCharge");

            // if experiment is active
            if (recording)
            {
                // detect conditions
                // - comparing against amount in previous step
                bool   has_ec       = ec.amount > double.Epsilon;
                bool   has_operator = operator_cs.Check(vessel);
                string sit          = Science.Situation(vessel, situations);

                // deduce issues
                issue = string.Empty;
                if (sit.Length == 0)
                {
                    issue = "invalid situation";
                }
                else if (!has_operator)
                {
                    issue = "no operator";
                }
                else if (!has_ec)
                {
                    issue = "missing <b>EC</b>";
                }

                // if there are no issues
                if (issue.Length == 0)
                {
                    // generate subject id
                    string subject_id = Science.Generate_subject(experiment, vessel.mainBody, sit, Science.Biome(vessel, sit), Science.Multiplier(vessel, sit));

                    // record in drive
                    if (transmissible)
                    {
                        DB.Vessel(vessel).drive.Record_file(subject_id, data_rate * Kerbalism.elapsed_s);
                    }
                    else
                    {
                        DB.Vessel(vessel).drive.Record_sample(subject_id, data_rate * Kerbalism.elapsed_s);
                    }

                    // consume ec
                    ec.Consume(ec_rate * Kerbalism.elapsed_s);
                }
            }
        }
        public static void NetMan(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, 20), " <color=#cccccc>NETWORK INFO</color>"));

            // time-out simulation
#if !DEBUG
            if (p.Timeout(vi))
            {
                return;
            }
#endif

            p.SetSection("ADAPTORS");
            p.Set_IsFreqSelector(true);

            // store all devices
            var devices = new Dictionary <uint, NetDevice>();

            // store device being added
            NetDevice adap;

            // loaded vessel
            if (v.loaded)
            {
                foreach (NetworkAdaptor m in Lib.FindModules <NetworkAdaptor>(v))
                {
                    adap = new NetAdaptorDevice(m);

                    // add the device
                    // - multiple same-type components in the same part will have the same id, and are ignored
                    if (!devices.ContainsKey(adap.Id()))
                    {
                        devices.Add(adap.Id(), adap);
                    }
                }
            }
            else
            {
                // store data required to support multiple modules of same type in a part
                var PD = new Dictionary <string, Lib.module_prefab_data>();

                // for each part
                foreach (ProtoPartSnapshot proto in v.protoVessel.protoPartSnapshots)
                {
                    // get part prefab (required for module properties)
                    Part part_prefab = PartLoader.getPartInfoByName(proto.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 proto.modules)
                    {
                        // 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;
                        }

                        if (m.moduleName == "NetworkAdaptor")
                        {
                            adap = new ProtoNetAdaptorDevice(m, proto.flightID, v);

                            // add the device
                            // - multiple same-type components in the same part will have the same id, and are ignored
                            if (!devices.ContainsKey(adap.Id()))
                            {
                                devices.Add(adap.Id(), adap);
                            }
                        }
                    }
                }
            }

            // dict order by device name
            // for each device
            foreach (var pair in devices.OrderBy(x => x.Value.Name()))
            {
                // render device entry
                NetDevice dev = pair.Value;
                // Get how many antennas share the same freq
                AntennasByFrequency x = null;
                if (vi.antenna.antennasByFreq.ContainsKey(dev.InfoFreq()))
                {
                    x = vi.antenna.antennasByFreq[dev.InfoFreq()];
                }

                p.SetContent(dev.Name(), dev.InfoRate(), string.Empty, null, () => Highlighter.Set(dev.Part(), Color.cyan), dev.InfoFreq());
                p.SetIcon(Icons.left_freq, "Decrease", () =>
                {
                    if (dev.InfoFreq() > 0) // && x != null
                    {
                        //if (x.antCount == 1 && x.countConnections > 0)
                        //{
                        //  Lib.Popup(
                        //    "Warning!",
                        //    Lib.BuildString("This is the last antenna on '", dev.InfoFreq().ToString(),
                        //                    "' frequency.\nYou will lost connection in this frequency.\nDo you really want to remove this frequency from this vessel?"),
                        //    new DialogGUIButton("Remove", () => dev.ChangeFreq(-1)),
                        //    new DialogGUIButton("Keep it", () => { }));
                        //}
                        //else
                        dev.ChangeFreq(-1);
                    }
                });
                p.SetIcon(Icons.right_freq, "Increase", () =>
                {
                    if (dev.InfoFreq() < 99) // && x != null
                    {
                        //if (x.antCount == 1 && x.countConnections > 0)
                        //{
                        //  Lib.Popup(
                        //    "Warning!",
                        //    Lib.BuildString("This is the last antenna on '", dev.InfoFreq().ToString(),
                        //                    "' frequency.\nYou will lost connection in this frequency.\nDo you really want to remove this frequency from this vessel?"),
                        //    new DialogGUIButton("Remove", () => dev.ChangeFreq(+1)),
                        //    new DialogGUIButton("Keep it", () => { }));
                        //}
                        //else
                        dev.ChangeFreq(+1);
                    }
                });
            }

            p.SetSection("FREQUENCY(S) DETAIL");
            foreach (short key in vi.antenna.antennasByFreq.Keys)
            {
                double range = vi.antenna.antennasByFreq[key].antennaPower;
                double rate  = vi.antenna.antennasByFreq[key].antennaRate;

                Render_ConnectionDetail(p, range, rate, key);
            }
        }
Example #9
0
        public static void ProtoBreak(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m)
        {
            // get reliability module prefab
            string      type        = Lib.Proto.GetString(m, "type", string.Empty);
            Reliability reliability = p.partPrefab.FindModulesImplementing <Reliability>().Find(k => k.type == type);

            if (reliability == null)
            {
                return;
            }

            // if manned, or if safemode didn't trigger
            if (Cache.VesselInfo(v).crew_capacity > 0 || Lib.RandomDouble() > Settings.SafeModeChance)
            {
                // flag as broken
                Lib.Proto.Set(m, "broken", true);

                // determine if this is a critical failure
                bool critical = Lib.RandomDouble() < Settings.CriticalChance;
                Lib.Proto.Set(m, "critical", critical);

                // for each associated module
                foreach (var proto_module in p.modules.FindAll(k => k.moduleName == reliability.type))
                {
                    // disable the module
                    Lib.Proto.Set(proto_module, "isEnabled", false);
                }

                // type-specific hacks
                switch (reliability.type)
                {
                case "ProcessController":
                    foreach (ProcessController pc in p.partPrefab.FindModulesImplementing <ProcessController>())
                    {
                        ProtoPartResourceSnapshot res = p.resources.Find(k => k.resourceName == pc.resource);
                        if (res != null)
                        {
                            res.flowState = false;
                        }
                    }
                    break;
                }

                // show message
                broken_msg(v, reliability.title, critical);
            }
            // safe mode
            else
            {
                // reset age
                Lib.Proto.Set(m, "last", 0.0);
                Lib.Proto.Set(m, "next", 0.0);

                // notify user
                safemode_msg(v, reliability.title);
            }

            // in any case, incentive redundancy
            if (Settings.IncentiveRedundancy)
            {
                incentive_redundancy(v, reliability.redundancy);
            }
        }
Example #10
0
        public static void Update(Vessel v, Vessel_info vi, VesselData vd, Resource_info ec, double elapsed_s)
        {
            if (!Lib.IsVessel(v))
            {
                return;
            }

            // consume ec for transmitters
            ec.Consume(vi.connection.ec * elapsed_s, "comms");

            Cache.WarpCache(v).dataCapacity = vi.connection.rate * elapsed_s;

            // 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 && !vi.connection.linked)
                {
                    vd.msg_signal = true;
                    if (vd.cfg_signal)
                    {
                        string subtext = Localizer.Format("#KERBALISM_UI_transmissiondisabled");

                        switch (vi.connection.status)
                        {
                        case LinkStatus.plasma:
                            subtext = Localizer.Format("#KERBALISM_UI_Plasmablackout");
                            break;

                        case LinkStatus.storm:
                            subtext = Localizer.Format("#KERBALISM_UI_Stormblackout");
                            break;

                        default:
                            if (vi.crew_count == 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 && vi.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")),
                                     vi.connection.status == LinkStatus.direct_link ? Localizer.Format("#KERBALISM_UI_directlink") :
                                     Lib.BuildString(Localizer.Format("#KERBALISM_UI_relayby"), " <b>", vi.connection.target_name, "</b>"));
                    }
                }
            }
        }
Example #11
0
        public void FixedUpdate()
        {
            // if part is manned (even in the editor), force enabled
            if (Lib.IsCrewed(part) && state != State.enabled)
            {
                Set_flow(true);
                state = State.pressurizing;

                // Equalize run only in Flight mode
                needEqualize = Lib.IsFlight();
            }

            perctDeployed = Lib.Level(part, "Atmosphere", true);

            // Only handle crewTransferred & Toggle, this way has less calls in FixedUpdate
            // CrewTransferred Event occur after FixedUpdate, this must be check in crewtransferred
            if (FixIVA)
            {
                if (Get_inflate_string().Length == 0)                 // it is not inflatable (We always going to show and cross those habitats)
                {
                    SetPassable(true);
                    UpdateIVA(true);
                }
                else
                {
                    // Inflatable modules shows IVA and are passable only in 99.9999% deployed
                    SetPassable(Lib.IsCrewed(part) || Math.Truncate(Math.Abs((perctDeployed + ResourceBalance.precision) - 1.0) * 100000) / 100000 <= ResourceBalance.precision);
                    UpdateIVA(Math.Truncate(Math.Abs((perctDeployed + ResourceBalance.precision) - 1.0) * 100000) / 100000 <= ResourceBalance.precision);
                }
                FixIVA = false;
            }

            if (max_pressure < Settings.PressureThreshold && Lib.IsFlight())
            {
                var vi = Cache.VesselInfo(vessel);
                vi.max_pressure = Math.Min(vi.max_pressure, max_pressure);
            }

            // state machine
            switch (state)
            {
            case State.enabled:
                // In case it is losting pressure
                if (perctDeployed < Settings.PressureThreshold)
                {
                    if (Get_inflate_string().Length != 0)                                     // it is inflatable
                    {
                        SetPassable(false || Lib.IsCrewed(part));                             // Prevent to not lock a Kerbal into a the part
                        UpdateIVA(false);
                    }
                    needEqualize = true;
                    state        = State.pressurizing;
                }
                break;

            case State.disabled:
                break;

            case State.pressurizing:
                state = Pressurizing();
                break;

            case State.depressurizing:
                // Just do Venting when has no gravityRing or when the gravity ring is not spinning.
                if (hasGravityRing && !gravityRing.Is_rotating())
                {
                    state = Depressurizing();
                }
                else if (!hasGravityRing)
                {
                    state = Depressurizing();
                }
                break;
            }
        }
Example #12
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;
  }
Example #13
0
        public override void OnLoad(ConfigNode node)
        {
            // everything in there will be called only one time : the first time a game is loaded from the main menu
            if (!IsCoreGameInitDone)
            {
                try
                {
                    // core game systems
                    Sim.Init();                             // find suns (Kopernicus support)
                    Radiation.Init();                       // create the radiation fields
                    ScienceDB.Init();                       // build the science database (needs Sim.Init() and Radiation.Init() first)
                    Science.Init();                         // register the science hijacker

                    // static graphic components
                    LineRenderer.Init();
                    ParticleRenderer.Init();
                    Highlighter.Init();

                    // UI
                    Textures.Init();                                          // set up the icon textures
                    UI.Init();                                                // message system, main gui, launcher
                    KsmGui.KsmGuiMasterController.Init();                     // setup the new gui framework

                    // part prefabs hacks
                    Profile.SetupPods();                     // add supply resources to pods
                    Misc.PartPrefabsTweaks();                // part prefabs tweaks, must be called after ScienceDB.Init()

                    // Create KsmGui windows
                    new ScienceArchiveWindow();

                    // GameEvents callbacks
                    Callbacks = new Callbacks();
                }
                catch (Exception e)
                {
                    string fatalError = "FATAL ERROR : Kerbalism core init has failed :" + "\n" + e.ToString();
                    Lib.Log(fatalError, Lib.LogLevel.Error);
                    LoadFailedPopup(fatalError);
                }

                IsCoreGameInitDone = true;
            }

            // everything in there will be called every time a savegame (or a new game) is loaded from the main menu
            if (!IsSaveGameInitDone)
            {
                try
                {
                    Cache.Init();
                    ResourceCache.Init();

                    // prepare storm data
                    foreach (CelestialBody body in FlightGlobals.Bodies)
                    {
                        if (Storm.Skip_body(body))
                        {
                            continue;
                        }
                        Storm_data sd = new Storm_data {
                            body = body
                        };
                        storm_bodies.Add(sd);
                    }
                }
                catch (Exception e)
                {
                    string fatalError = "FATAL ERROR : Kerbalism save game init has failed :" + "\n" + e.ToString();
                    Lib.Log(fatalError, Lib.LogLevel.Error);
                    LoadFailedPopup(fatalError);
                }

                IsSaveGameInitDone = true;
            }

            // eveything else will be called on every OnLoad() call :
            // - save/load
            // - every scene change
            // - in various semi-random situations (thanks KSP)

            // Fix for background IMGUI textures being dropped on scene changes since KSP 1.8
            Styles.ReloadBackgroundStyles();

            // always clear the caches
            Cache.Clear();
            ResourceCache.Clear();

            // deserialize our database
            try
            {
                UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.DB.Load");
                DB.Load(node);
                UnityEngine.Profiling.Profiler.EndSample();
            }
            catch (Exception e)
            {
                string fatalError = "FATAL ERROR : Kerbalism save game load has failed :" + "\n" + e.ToString();
                Lib.Log(fatalError, Lib.LogLevel.Error);
                LoadFailedPopup(fatalError);
            }

            // I'm smelling the hacky mess in here.
            Communications.NetworkInitialized  = false;
            Communications.NetworkInitializing = false;

            // detect if this is a different savegame
            if (DB.uid != savegame_uid)
            {
                // clear caches
                Message.all_logs.Clear();

                // sync main window pos from db
                UI.Sync();

                // remember savegame id
                savegame_uid = DB.uid;
            }

            Kerbalism.gameLoadTime = Time.time;
        }
        // ctor
        public Vessel_Info(Vessel v, uint vessel_id, UInt64 inc)
        {
            // NOTE: anything used here can't in turn use cache, unless you know what you are doing

            // NOTE: you can't cache vessel position
            //  at any point in time all vessel/body positions are relative to a different frame of reference
            //  so comparing the current position of a vessel, with the cached one of another make no sense

            // associate with an unique incremental id
            this.inc = inc;

            // determine if this is a valid vessel
            is_vessel = Lib.IsVessel(v);

            if (!is_vessel)
            {
                return;
            }

            // determine if this is a rescue mission vessel
            is_rescue = Misc.IsRescueMission(v);
            if (is_rescue)
            {
                return;
            }

            // dead EVA are not valid vessels
            if (EVA.IsDead(v))
            {
                return;
            }

            // shortcut for common tests
            is_valid = true;

            // generate id once
            id = vessel_id;

            // calculate crew info for the vessel
            crew_count    = Lib.CrewCount(v);
            crew_capacity = Lib.CrewCapacity(v);

            // get vessel position
            Vector3d position = Lib.VesselPosition(v);

            // this should never happen again
            if (Vector3d.Distance(position, v.mainBody.position) < 1.0)
            {
                throw new Exception("Shit hit the fan for vessel " + v.vesselName);
            }

            // determine if in sunlight, calculate sun direction and distance
            sunlight = Sim.RaytraceBody(v, position, FlightGlobals.Bodies[0], out sun_dir, out sun_dist) ? 1.0 : 0.0;

            // at the two highest timewarp speed, the number of sun visibility samples drop to the point that
            // the quantization error first became noticeable, and then exceed 100%
            // to solve this, we switch to an analytical estimation of the portion of orbit that was in sunlight
            // - we check against timewarp rate, instead of index, to avoid issues during timewarp blending
            if (v.mainBody.flightGlobalsIndex != 0 && TimeWarp.CurrentRate > 1000.0f)
            {
                sunlight = 1.0 - Sim.ShadowPeriod(v) / Sim.OrbitalPeriod(v);
            }

            // environment stuff
            atmo_factor        = Sim.AtmosphereFactor(v.mainBody, position, sun_dir);
            gamma_transparency = Sim.GammaTransparency(v.mainBody, v.altitude);
            underwater         = Sim.Underwater(v);
            breathable         = Sim.Breathable(v, underwater);
            landed             = Lib.Landed(v);

            // temperature at vessel position
            temperature = Sim.Temperature(v, position, sunlight, atmo_factor, out solar_flux, out albedo_flux, out body_flux, out total_flux);
            temp_diff   = Sim.TempDiff(temperature, v.mainBody, landed);

            // radiation
            radiation = Radiation.Compute(v, position, gamma_transparency, sunlight, out blackout, out magnetosphere, out inner_belt, out outer_belt, out interstellar);

            // extended atmosphere
            thermosphere = Sim.InsideThermosphere(v);
            exosphere    = Sim.InsideExosphere(v);

            // malfunction stuff
            malfunction = Reliability.HasMalfunction(v);
            critical    = Reliability.HasCriticalFailure(v);

            // signal info
            if (Features.KCommNet)
            {
                antenna = Cache.AntennaInfo(v);
                avoid_inf_recursion.Add(v.id);
            }
            else
            {
                kAntenna = new KAntennaInfo(v);
                avoid_inf_recursion.Add(v.id);
            }
            // TODO: Need to create a Signal integrated with CommNet
            connection   = Signal.Connection(v, position, kAntenna, blackout, avoid_inf_recursion);
            transmitting = Science.Transmitting(v, connection.linked);
            relaying     = Signal.Relaying(v, avoid_inf_recursion);
            avoid_inf_recursion.Remove(v.id);

            // habitat data
            volume       = Habitat.Total_Volume(v);
            surface      = Habitat.Total_Surface(v);
            pressure     = Habitat.Pressure(v);
            poisoning    = Habitat.Poisoning(v);
            shielding    = Habitat.Shielding(v);
            living_space = Habitat.Living_Space(v);
            comforts     = new Comforts(v, landed, crew_count > 1, true); // TODO: replace 'true' for connection.linked

            // data about greenhouses
            greenhouses = Greenhouse.Greenhouses(v);

            // other stuff
            gravioli = Sim.Graviolis(v);
        }
Example #15
0
 // return living space factor
 public static double LivingSpace(Vessel v)
 {
     return(Cache.VesselInfo(v).living_space);
 }
Example #16
0
        public static void Devman(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>" + Localizer.Format("#KERBALISM_UI_devman") + "</color>"));
            p.Width(Styles.ScaleWidthFloat(355.0f));
            p.paneltype = Panel.PanelType.scripts;

            // time-out simulation
            if (p.Timeout(vi))
            {
                return;
            }

            // get devices
            Dictionary <uint, Device> devices = Computer.Boot(v);
            int deviceCount = 0;

            // direct control
            if (script_index == 0)
            {
                // draw section title and desc
                p.AddSection
                (
                    Localizer.Format("#KERBALISM_UI_devices"),
                    Description(),
                    () => p.Prev(ref script_index, (int)ScriptType.last),
                    () => p.Next(ref script_index, (int)ScriptType.last),
                    true
                );

                // for each device
                foreach (var pair in devices)
                {
                    // render device entry
                    Device dev = pair.Value;
                    if (!dev.IsVisible())
                    {
                        continue;
                    }
                    p.AddContent(dev.Name(), dev.Info(), string.Empty, dev.Toggle, () => Highlighter.Set(dev.Part(), Color.cyan));
                    deviceCount++;
                }
            }
            // script editor
            else
            {
                // get script
                ScriptType script_type = (ScriptType)script_index;
                string     script_name = script_type.ToString().Replace('_', ' ').ToUpper();
                Script     script      = DB.Vessel(v).computer.Get(script_type);

                // draw section title and desc
                p.AddSection
                (
                    script_name,
                    Description(),
                    () => p.Prev(ref script_index, (int)ScriptType.last),
                    () => p.Next(ref script_index, (int)ScriptType.last),
                    true
                );

                // for each device
                foreach (var pair in devices)
                {
                    Device dev = pair.Value;
                    if (!dev.IsVisible())
                    {
                        continue;
                    }

                    // determine tribool state
                    int state = !script.states.ContainsKey(pair.Key)
                                          ? -1
                                          : !script.states[pair.Key]
                                          ? 0
                                          : 1;

                    // render device entry
                    p.AddContent
                    (
                        dev.Name(),
                        state == -1 ? "<color=#999999>" + Localizer.Format("#KERBALISM_UI_dontcare") + " </color>" : state == 0 ? "<color=red>" + Localizer.Format("#KERBALISM_Generic_OFF") + "</color>" : "<color=cyan>" + Localizer.Format("#KERBALISM_Generic_ON") + "</color>",
                        string.Empty,
                        () =>
                    {
                        switch (state)
                        {
                        case -1: script.Set(dev, true); break;

                        case 0: script.Set(dev, null); break;

                        case 1: script.Set(dev, false); break;
                        }
                    },
                        () => Highlighter.Set(dev.Part(), Color.cyan)
                    );
                    deviceCount++;
                }
            }

            // no devices case
            if (deviceCount == 0)
            {
                p.AddContent("<i>no devices</i>");
            }
        }
Example #17
0
 // return comfort factor
 public static double Comfort(Vessel v)
 {
     return(Cache.VesselInfo(v).comforts.factor);
 }
Example #18
0
        State equalize()
        {
            // in flight
            if (Lib.IsFlight())
            {
                // shortcuts
                resource_info vessel_atmo = ResourceCache.Info(vessel, "Atmosphere");
                PartResource  hab_atmo    = part.Resources["Atmosphere"];

                // get level of atmosphere in vessel and part
                double vessel_level = vessel_atmo.level;
                double hab_level    = Lib.Level(part, "Atmosphere", true);

                // equalization succeeded if the levels are the same
                // note: this behave correctly in the case the hab is the only enabled one or not
                if (Math.Abs(vessel_level - hab_level) < 0.01)
                {
                    return(State.enabled);
                }

                // in case vessel pressure is dropping during equalization, it mean that pressure
                // control is not enough so we just enable the hab while not fully equalized
                if (vessel_atmo.rate < 0.0)
                {
                    return(State.enabled);
                }

                // determine equalization speed
                // we deal with the case where a big hab is sucking all atmosphere from the rest of the vessel
                double amount = Math.Min(Cache.VesselInfo(vessel).volume, volume) * equalize_speed * Kerbalism.elapsed_s;

                // vessel pressure is higher
                if (vessel_level > hab_level)
                {
                    // clamp amount to what's available in the vessel and what can fit in the part
                    amount = Math.Min(amount, vessel_atmo.amount);
                    amount = Math.Min(amount, hab_atmo.maxAmount - hab_atmo.amount);

                    // consume from all enabled habs in the vessel
                    vessel_atmo.Consume(amount);

                    // produce in the part
                    hab_atmo.amount += amount;
                }
                // vessel pressure is lower
                else
                {
                    // consume from the part, clamp amount to what's available in the part
                    amount           = Math.Min(amount, hab_atmo.amount);
                    hab_atmo.amount -= amount;

                    // produce in all enabled habs in the vessel
                    // (attempt recovery, but dump overboard if there is no capacity left)
                    vessel_atmo.Produce(amount);
                }

                // equalization still in progress
                return(State.equalizing);
            }
            // in the editors
            else
            {
                // set amount to max capacity
                PartResource hab_atmo = part.Resources["Atmosphere"];
                hab_atmo.amount = hab_atmo.maxAmount;

                // return new state
                return(State.enabled);
            }
        }
Example #19
0
 // return true if the vessel specified is inside a breathable atmosphere
 public static bool Breathable(Vessel v)
 {
     return(Cache.VesselInfo(v).breathable);
 }
Example #20
0
  // 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);
        }
      }
    }
  }