Пример #1
0
        /// <summary>
        /// Call ResourceUpdate on all part modules that have that method
        /// </summary>
        public void ResourceUpdate(VesselResources resources, double elapsed_s)
        {
            // only do this for loaded vessels. unloaded vessels will be handled in Background.cs
            if (!Vessel.loaded)
            {
                return;
            }

            if (resourceUpdateDelegates == null)
            {
                resourceUpdateDelegates = new List <ResourceUpdateDelegate>();
                foreach (var part in Vessel.parts)
                {
                    foreach (var module in part.Modules)
                    {
                        if (!module.isEnabled)
                        {
                            continue;
                        }
                        var resourceUpdateDelegate = ResourceUpdateDelegate.Instance(module);
                        if (resourceUpdateDelegate != null)
                        {
                            resourceUpdateDelegates.Add(resourceUpdateDelegate);
                        }
                    }
                }
            }

            if (resourceUpdateDelegates.Count == 0)
            {
                return;
            }

            List <ResourceInfo> allResources = resources.GetAllResources(Vessel);            // there might be some performance to be gained by caching the list of all resource

            Dictionary <string, double> availableResources = new Dictionary <string, double>();

            foreach (var ri in allResources)
            {
                availableResources[ri.ResourceName] = ri.Amount;
            }
            List <KeyValuePair <string, double> > resourceChangeRequests = new List <KeyValuePair <string, double> >();

            foreach (var resourceUpdateDelegate in resourceUpdateDelegates)
            {
                resourceChangeRequests.Clear();
                string title = resourceUpdateDelegate.invoke(availableResources, resourceChangeRequests);
                foreach (var rc in resourceChangeRequests)
                {
                    if (rc.Value > 0)
                    {
                        resources.Produce(Vessel, rc.Key, rc.Value * elapsed_s, title);
                    }
                    if (rc.Value < 0)
                    {
                        resources.Consume(Vessel, rc.Key, -rc.Value * elapsed_s, title);
                    }
                }
            }
        }
Пример #2
0
        private void ExecuteRecipe(double k, VesselResources resources, double elapsed_s, ResourceRecipe recipe)
        {
            // only execute processes if necessary
            if (Math.Abs(k) < double.Epsilon)
            {
                return;
            }

            foreach (var p in inputs)
            {
                recipe.AddInput(p.Key, p.Value * k * elapsed_s);
            }
            foreach (var p in outputs)
            {
                recipe.AddOutput(p.Key, p.Value * k * elapsed_s, dump.Check(p.Key));
            }
            foreach (var p in cures)
            {
                // TODO this assumes that the cure modifies always put the resource first
                // works: modifier = _SickbayRDU,zerog works
                // fails: modifier = zerog,_SickbayRDU
                recipe.AddCure(p.Key, p.Value * k * elapsed_s, modifiers[0]);
            }
            resources.AddRecipe(recipe);
        }
Пример #3
0
        public static void TelemetryPanel(this Panel p, Vessel v)
        {
            // avoid corner-case when this is called in a lambda after scene changes
            v = FlightGlobals.FindVessel(v.id);

            // if vessel doesn't exist anymore, leave the panel empty
            if (v == null)
            {
                return;
            }

            // get vessel data
            VesselData vd = v.KerbalismData();

            // if not a valid vessel, leave the panel empty
            if (!vd.IsSimulated)
            {
                return;
            }

            // set metadata
            p.Title(Lib.BuildString(Lib.Ellipsis(v.vesselName, Styles.ScaleStringLength(20)), " ", Lib.Color(Local.TELEMETRY_title, Lib.Kolor.LightGrey)));            //"TELEMETRY"
            p.Width(Styles.ScaleWidthFloat(355.0f));
            p.paneltype = Panel.PanelType.telemetry;

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

            // get resources
            VesselResources resources = ResourceCache.Get(v);

            // get crew
            var crew = Lib.CrewList(v);

            // draw the content
            Render_crew(p, crew);
            if (Features.Science)
            {
                Render_science(p, v, vd);
            }
            Render_greenhouse(p, vd);
            Render_supplies(p, v, vd, resources);
            Render_habitat(p, v, vd);
            Render_environment(p, v, vd);

            // collapse eva kerbal sections into one
            if (v.isEVA)
            {
                p.Collapse(Local.TELEMETRY_EVASUIT);                     //"EVA SUIT"
            }
        }
Пример #4
0
        public void Execute(Vessel v, VesselData vd, VesselResources resources, double elapsed_s)
        {
            // evaluate modifiers
            // if a given PartModule has a larger than 1 capacity for a process, then the multiplication happens here
            // remember that when a process is enabled the units of process are stored in the PartModule as a pseudo-resource
            double k = Modifiers.Evaluate(v, vd, resources, modifiers);

            ResourceRecipe recipe = new ResourceRecipe(name);

            ExecuteRecipe(k, resources, elapsed_s, recipe);
        }
Пример #5
0
        void ToEVA(GameEvents.FromToAction <Part, Part> data)
        {
            OnVesselModified(data.from.vessel);
            OnVesselModified(data.to.vessel);

            // get total crew in the origin vessel
            double tot_crew = Lib.CrewCount(data.from.vessel) + 1.0;

            // get vessel resources handler
            VesselResources resources = ResourceCache.Get(data.from.vessel);

            // setup supply resources capacity in the eva kerbal
            Profile.SetupEva(data.to);

            string evaPropName = Lib.EvaPropellantName();

            // for each resource in the kerbal
            for (int i = 0; i < data.to.Resources.Count; ++i)
            {
                // get the resource
                PartResource res = data.to.Resources[i];

                // eva prop is handled differently
                if (res.resourceName == evaPropName)
                {
                    continue;
                }

                double quantity = Math.Min(resources.GetResource(data.from.vessel, res.resourceName).Amount / tot_crew, res.maxAmount);
                // remove resource from vessel
                quantity = data.from.RequestResource(res.resourceName, quantity);

                // add resource to eva kerbal
                data.to.RequestResource(res.resourceName, -quantity);
            }

            // Airlock loss
            resources.Consume(data.from.vessel, "Nitrogen", Settings.LifeSupportAtmoLoss, ResourceBroker.Generic);

            KerbalEVA kerbal = data.to.FindModuleImplementing <KerbalEVA>();


            // turn off headlamp light, to avoid stock bug that show them for a split second when going on eva
            EVA.HeadLamps(kerbal, false);

            // execute script
            data.from.vessel.KerbalismData().computer.Execute(data.from.vessel, ScriptType.eva_out);

            // Start a coroutine for doing eva propellant resource transfers once the kerbal EVA is started (this is too early here)
            data.to.StartCoroutine(PostEVATweaks(data.from, data.to, evaPropName));
        }
Пример #6
0
        public void Execute(Vessel v, VesselData vd, VesselResources resources)
        {
            // get crew
            List <ProtoCrewMember> crew = Lib.CrewList(v);

            // get resource handler
            ResourceInfo res = resources.GetResource(v, resource);

            // get data from db
            SupplyData sd = v.KerbalismData().Supply(resource);

            // message obey user config
            bool show_msg = resource == "ElectricCharge" ? vd.cfg_ec : vd.cfg_supply;

            // messages are shown only if there is some capacity and the vessel is manned
            // special case: ElectricCharge related messages are shown for unmanned vessels too
            if (res.Capacity > double.Epsilon && (crew.Count > 0 || resource == "ElectricCharge"))
            {
                // manned/probe message variant
                uint variant = crew.Count > 0 ? 0 : 1u;

                // manage messages
                if (res.Level <= double.Epsilon && sd.message < 2)
                {
                    if (empty_message.Length > 0 && show_msg)
                    {
                        Message.Post(Severity.danger, Lib.ExpandMsg(empty_message, v, null, variant));
                    }
                    sd.message = 2;
                }
                else if (res.Level < low_threshold && sd.message < 1)
                {
                    if (low_message.Length > 0 && show_msg)
                    {
                        Message.Post(Severity.warning, Lib.ExpandMsg(low_message, v, null, variant));
                    }
                    sd.message = 1;
                }
                else if (res.Level > low_threshold && sd.message > 0)
                {
                    if (refill_message.Length > 0 && show_msg)
                    {
                        Message.Post(Severity.relax, Lib.ExpandMsg(refill_message, v, null, variant));
                    }
                    sd.message = 0;
                }
            }
        }
Пример #7
0
        /// <summary>Execute all recipes and record deferred consumption/production for inputs/ouputs</summary>
        public static void ExecuteRecipes(Vessel v, VesselResources resources, List <ResourceRecipe> recipes)
        {
            bool executing = true;

            while (executing)
            {
                executing = false;
                for (int i = 0; i < recipes.Count; ++i)
                {
                    ResourceRecipe recipe = recipes[i];
                    if (recipe.left > double.Epsilon)
                    {
                        executing |= recipe.ExecuteRecipeStep(v, resources);
                    }
                }
            }
        }
Пример #8
0
        /// <summary> Return the VesselResources handler for this vessel </summary>
        public static VesselResources Get(Vessel v)
        {
            // try to get existing entry if any
            VesselResources entry;

            if (entries.TryGetValue(v.id, out entry))
            {
                return(entry);
            }

            // create new entry
            entry = new VesselResources();

            // remember new entry
            entries.Add(v.id, entry);

            // return new entry
            return(entry);
        }
Пример #9
0
        public static void Execute(Vessel v, VesselData vd, VesselResources resources, double elapsed_s)
        {
            // execute all supplies
            foreach (Supply supply in supplies)
            {
                // this will just show warning messages if resources get low
                supply.Execute(v, vd, resources);
            }

            // execute all rules
            foreach (Rule rule in rules)
            {
                rule.Execute(v, vd, resources, elapsed_s);
            }

            // execute all processes
            foreach (Process process in processes)
            {
                process.Execute(v, vd, resources, elapsed_s);
            }
        }
Пример #10
0
        void ToEVA(GameEvents.FromToAction <Part, Part> data)
        {
            OnVesselModified(data.from.vessel);
            OnVesselModified(data.to.vessel);

            // get total crew in the origin vessel
            double tot_crew = Lib.CrewCount(data.from.vessel) + 1.0;

            // get vessel resources handler
            VesselResources resources = ResourceCache.Get(data.from.vessel);

            // setup supply resources capacity in the eva kerbal
            Profile.SetupEva(data.to);

            String prop_name = Lib.EvaPropellantName();

            // for each resource in the kerbal
            for (int i = 0; i < data.to.Resources.Count; ++i)
            {
                // get the resource
                PartResource res = data.to.Resources[i];

                // eva prop is handled differently
                if (res.resourceName == prop_name)
                {
                    continue;
                }

                double quantity = Math.Min(resources.GetResource(data.from.vessel, res.resourceName).Amount / tot_crew, res.maxAmount);
                // remove resource from vessel
                quantity = data.from.RequestResource(res.resourceName, quantity);

                // add resource to eva kerbal
                data.to.RequestResource(res.resourceName, -quantity);
            }

            // take as much of the propellant as possible. just imagine: there are 1.3 units left, and 12 occupants
            // in the ship. you want to send out an engineer to fix the chemical plant that produces monoprop,
            // and have to get from one end of the station to the other with just 0.1 units in the tank...
            // nope.
            double evaPropQuantity = data.from.RequestResource(prop_name, Lib.EvaPropellantCapacity());

            // We can't just add the monoprop here, because that doesn't always work. It might be related
            // to the fact that stock KSP wants to add 5 units of monoprop to new EVAs. Instead of fighting KSP here,
            // we just let it do it's thing and set our amount later in EVA.cs - which seems to work just fine.
            // don't put that into Cache.VesselInfo because that can be deleted before we get there
            Cache.SetVesselObjectsCache(data.to.vessel, "eva_prop", evaPropQuantity);

            // Airlock loss
            resources.Consume(data.from.vessel, "Nitrogen", Settings.LifeSupportAtmoLoss, ResourceBroker.Generic);

            // show warning if there is little or no EVA propellant in the suit
            if (evaPropQuantity <= 0.05 && !Lib.Landed(data.from.vessel))
            {
                Message.Post(Severity.danger,
                             Local.CallBackMsg_EvaNoMP.Format("<b>" + prop_name + "</b>"), Local.CallBackMsg_EvaNoMP2);       //Lib.BuildString("There isn't any <<1>> in the EVA suit")"Don't let the ladder go!"
            }

            // turn off headlamp light, to avoid stock bug that show them for a split second when going on eva
            KerbalEVA kerbal = data.to.FindModuleImplementing <KerbalEVA>();

            EVA.HeadLamps(kerbal, false);

            // execute script
            data.from.vessel.KerbalismData().computer.Execute(data.from.vessel, ScriptType.eva_out);
        }
Пример #11
0
        public static void BackgroundUpdate(Vessel v, ProtoPartModuleSnapshot m, Greenhouse g,
                                            VesselData vd, VesselResources resources, double elapsed_s)
        {
            // get protomodule data
            bool   active = Lib.Proto.GetBool(m, "active");
            double growth = Lib.Proto.GetDouble(m, "growth");

            // if enabled and not ready for harvest
            if (active && growth < 0.99)
            {
                // get resource handler
                ResourceInfo ec = resources.GetResource(v, "ElectricCharge");

                // calculate natural and artificial lighting
                double natural    = vd.EnvSolarFluxTotal;
                double artificial = Math.Max(g.light_tolerance - natural, 0.0);

                // consume EC for the lamps, scaled by artificial light intensity
                if (artificial > double.Epsilon)
                {
                    ec.Consume(g.ec_rate * (artificial / g.light_tolerance) * elapsed_s, ResourceBroker.Greenhouse);
                }

                // reset artificial lighting if there is no ec left
                // note: comparing against amount in previous simulation step
                if (ec.Amount <= double.Epsilon)
                {
                    artificial = 0.0;
                }

                // execute recipe
                ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.Greenhouse);
                foreach (ModuleResource input in g.resHandler.inputResources)                 //recipe.Input(input.name, input.rate * elapsed_s);
                {
                    // WasteAtmosphere is primary combined input
                    if (g.WACO2 && input.name == "WasteAtmosphere")
                    {
                        recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * elapsed_s, "CarbonDioxide");
                    }
                    // CarbonDioxide is secondary combined input
                    else if (g.WACO2 && input.name == "CarbonDioxide")
                    {
                        recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * elapsed_s, "");
                    }
                    // if atmosphere is breathable disable WasteAtmosphere / CO2
                    else if (!g.WACO2 && (input.name == "CarbonDioxide" || input.name == "WasteAtmosphere"))
                    {
                        recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate, "");
                    }
                    else
                    {
                        recipe.AddInput(input.name, input.rate * elapsed_s);
                    }
                }
                foreach (ModuleResource output in g.resHandler.outputResources)
                {
                    // if atmosphere is breathable disable Oxygen
                    if (output.name == "Oxygen")
                    {
                        recipe.AddOutput(output.name, vd.EnvBreathable ? 0.0 : output.rate * elapsed_s, true);
                    }
                    else
                    {
                        recipe.AddOutput(output.name, output.rate * elapsed_s, true);
                    }
                }
                resources.AddRecipe(recipe);

                // determine environment conditions
                bool lighting  = natural + artificial >= g.light_tolerance;
                bool pressure  = g.pressure_tolerance <= 0 || vd.Pressure >= g.pressure_tolerance;
                bool radiation = g.radiation_tolerance <= 0 || vd.EnvRadiation * (1.0 - vd.Shielding) < g.radiation_tolerance;

                // determine inputs conditions
                // note: comparing against amounts in previous simulation step
                bool   inputs      = true;
                string missing_res = string.Empty;
                bool   dis_WACO2   = false;
                foreach (ModuleResource input in g.resHandler.inputResources)
                {
                    // combine WasteAtmosphere and CO2 if both exist
                    if (input.name == "WasteAtmosphere" || input.name == "CarbonDioxide")
                    {
                        if (dis_WACO2 || vd.EnvBreathable)
                        {
                            continue;                                                           // skip if already checked or atmosphere is breathable
                        }
                        if (g.WACO2)
                        {
                            if (resources.GetResource(v, "WasteAtmosphere").Amount <= double.Epsilon && resources.GetResource(v, "CarbonDioxide").Amount <= double.Epsilon)
                            {
                                inputs      = false;
                                missing_res = "CarbonDioxide";
                                break;
                            }
                            dis_WACO2 = true;
                            continue;
                        }
                    }
                    if (resources.GetResource(v, input.name).Amount <= double.Epsilon)
                    {
                        inputs      = false;
                        missing_res = input.name;
                        break;
                    }
                }

                // if growing
                if (lighting && pressure && radiation && inputs)
                {
                    // increase growth
                    growth += g.crop_rate * elapsed_s;
                    growth  = Math.Min(growth, 1.0);

                    // notify the user when crop can be harvested
                    if (growth >= 0.99)
                    {
                        Message.Post(Local.harvestedready_msg.Format("<b>" + v.vesselName + "</b>"));                        //Lib.BuildString("On <<1>> the crop is ready to be harvested")
                        growth = 1.0;
                    }
                }

                // update time-to-harvest
                double tta = (1.0 - growth) / g.crop_rate;

                // update issues
                string issue =
                    !inputs?Lib.BuildString(Local.Greenhouse_resoucesmissing.Format(missing_res)) //"missing ", missing_res
                        : !lighting ? Local.Greenhouse_issue1                                     //"insufficient lighting"
                                : !pressure ? Local.Greenhouse_issue2                             //"insufficient pressure"
                                : !radiation ? Local.Greenhouse_issue3                            //"excessive radiation"
                                : string.Empty;

                // update protomodule data
                Lib.Proto.Set(m, "natural", natural);
                Lib.Proto.Set(m, "artificial", artificial);
                Lib.Proto.Set(m, "tta", tta);
                Lib.Proto.Set(m, "issue", issue);
                Lib.Proto.Set(m, "growth", growth);
            }
        }
Пример #12
0
        public void FixedUpdate()
        {
            // do nothing in the editor
            if (Lib.IsEditor())
            {
                return;
            }

            // if enabled and not ready for harvest
            if (active && growth < 0.99)
            {
                // get vessel info from the cache
                // - if the vessel is not valid (eg: flagged as debris) then solar flux will be 0 and landed false (but that's okay)
                VesselData vd = vessel.KerbalismData();

                // get resource cache
                VesselResources resources = ResourceCache.Get(vessel);
                ResourceInfo    ec        = resources.GetResource(vessel, "ElectricCharge");

                // deal with corner cases when greenhouse is assembled using KIS
                if (double.IsNaN(growth) || double.IsInfinity(growth))
                {
                    growth = 0.0;
                }

                // calculate natural and artificial lighting
                natural    = vd.EnvSolarFluxTotal;
                artificial = Math.Max(light_tolerance - natural, 0.0);

                // consume EC for the lamps, scaled by artificial light intensity
                if (artificial > double.Epsilon)
                {
                    ec.Consume(ec_rate * (artificial / light_tolerance) * Kerbalism.elapsed_s, ResourceBroker.Greenhouse);
                }

                // reset artificial lighting if there is no ec left
                // - comparing against amount in previous simulation step
                if (ec.Amount <= double.Epsilon)
                {
                    artificial = 0.0;
                }

                // execute recipe
                ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.Greenhouse);
                foreach (ModuleResource input in resHandler.inputResources)
                {
                    // WasteAtmosphere is primary combined input
                    if (WACO2 && input.name == "WasteAtmosphere")
                    {
                        recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * Kerbalism.elapsed_s, "CarbonDioxide");
                    }
                    // CarbonDioxide is secondary combined input
                    else if (WACO2 && input.name == "CarbonDioxide")
                    {
                        recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate * Kerbalism.elapsed_s, "");
                    }
                    // if atmosphere is breathable disable WasteAtmosphere / CO2
                    else if (!WACO2 && (input.name == "CarbonDioxide" || input.name == "WasteAtmosphere"))
                    {
                        recipe.AddInput(input.name, vd.EnvBreathable ? 0.0 : input.rate, "");
                    }
                    else
                    {
                        recipe.AddInput(input.name, input.rate * Kerbalism.elapsed_s);
                    }
                }
                foreach (ModuleResource output in resHandler.outputResources)
                {
                    // if atmosphere is breathable disable Oxygen
                    if (output.name == "Oxygen")
                    {
                        recipe.AddOutput(output.name, vd.EnvBreathable ? 0.0 : output.rate * Kerbalism.elapsed_s, true);
                    }
                    else
                    {
                        recipe.AddOutput(output.name, output.rate * Kerbalism.elapsed_s, true);
                    }
                }
                resources.AddRecipe(recipe);

                // determine environment conditions
                bool lighting  = natural + artificial >= light_tolerance;
                bool pressure  = pressure_tolerance <= double.Epsilon || vd.Pressure >= pressure_tolerance;
                bool radiation = radiation_tolerance <= double.Epsilon || (1.0 - vd.Shielding) * vd.EnvHabitatRadiation < radiation_tolerance;

                // determine input resources conditions
                // - comparing against amounts in previous simulation step
                bool   inputs      = true;
                string missing_res = string.Empty;
                bool   dis_WACO2   = false;
                foreach (ModuleResource input in resHandler.inputResources)
                {
                    // combine WasteAtmosphere and CO2 if both exist
                    if (input.name == "WasteAtmosphere" || input.name == "CarbonDioxide")
                    {
                        if (dis_WACO2 || vd.EnvBreathable)
                        {
                            continue;                                                           // skip if already checked or atmosphere is breathable
                        }
                        if (WACO2)
                        {
                            if (resources.GetResource(vessel, "WasteAtmosphere").Amount <= double.Epsilon && resources.GetResource(vessel, "CarbonDioxide").Amount <= double.Epsilon)
                            {
                                inputs      = false;
                                missing_res = "CarbonDioxide";
                                break;
                            }
                            dis_WACO2 = true;
                            continue;
                        }
                    }
                    if (resources.GetResource(vessel, input.name).Amount <= double.Epsilon)
                    {
                        inputs      = false;
                        missing_res = input.name;
                        break;
                    }
                }

                // if growing
                if (lighting && pressure && radiation && inputs)
                {
                    // increase growth
                    growth += crop_rate * Kerbalism.elapsed_s;
                    growth  = Math.Min(growth, 1.0);

                    // notify the user when crop can be harvested
                    if (growth >= 0.99)
                    {
                        Message.Post(Local.harvestedready_msg.Format("<b>" + vessel.vesselName + "</b>"));                        //Lib.BuildString("On <<1>> the crop is ready to be harvested")
                        growth = 1.0;
                    }
                }

                // update time-to-harvest
                tta = (1.0 - growth) / crop_rate;

                // update issues
                issue =
                    !inputs?Lib.BuildString(Local.Greenhouse_resoucesmissing.Format(missing_res)) //"missing <<1>>"
                        : !lighting ? Local.Greenhouse_issue1                                     //"insufficient lighting"
                                : !pressure ? Local.Greenhouse_issue2                             //"insufficient pressure"
                                : !radiation ? Local.Greenhouse_issue3                            //"excessive radiation"
                                : string.Empty;
            }
        }
Пример #13
0
        static void Render_supplies(Panel p, Vessel v, VesselData vd, VesselResources resources)
        {
            int supplies = 0;

            // for each supply
            foreach (Supply supply in Profile.supplies)
            {
                // get resource info
                ResourceInfo res = resources.GetResource(v, supply.resource);

                // only show estimate if the resource is present
                if (res.Capacity <= 1e-10)
                {
                    continue;
                }

                // render panel title, if not done already
                if (supplies == 0)
                {
                    p.AddSection(Local.TELEMETRY_SUPPLIES);                               //"SUPPLIES"
                }
                // determine label
                var    resource = PartResourceLibrary.Instance.resourceDefinitions[supply.resource];
                string label    = Lib.SpacesOnCaps(resource.displayName).ToLower();

                StringBuilder sb = new StringBuilder();

                sb.Append("<align=left />");
                if (res.AverageRate != 0.0)
                {
                    sb.Append(Lib.Color(res.AverageRate > 0.0,
                                        Lib.BuildString("+", Lib.HumanReadableRate(Math.Abs(res.AverageRate))), Lib.Kolor.PosRate,
                                        Lib.BuildString("-", Lib.HumanReadableRate(Math.Abs(res.AverageRate))), Lib.Kolor.NegRate,
                                        true));
                }
                else
                {
                    sb.Append("<b>");
                    sb.Append(Local.TELEMETRY_nochange);                    //no change
                    sb.Append("</b>");
                }

                if (res.AverageRate < 0.0 && res.Level < 0.0001)
                {
                    sb.Append(" <i>");
                    sb.Append(Local.TELEMETRY_empty);                    //(empty)
                    sb.Append("</i>");
                }
                else if (res.AverageRate > 0.0 && res.Level > 0.9999)
                {
                    sb.Append(" <i>");
                    sb.Append(Local.TELEMETRY_full);                    //(full)
                    sb.Append("</i>");
                }
                else
                {
                    sb.Append("   ");                  // spaces to prevent alignement issues
                }
                sb.Append("\t");
                sb.Append(res.Amount.ToString("F1"));
                sb.Append("/");
                sb.Append(res.Capacity.ToString("F1"));
                sb.Append(" (");
                sb.Append(res.Level.ToString("P0"));
                sb.Append(")");

                List <SupplyData.ResourceBrokerRate> brokers = vd.Supply(supply.resource).ResourceBrokers;
                if (brokers.Count > 0)
                {
                    sb.Append("\n<b>------------    \t------------</b>");
                    foreach (SupplyData.ResourceBrokerRate rb in brokers)
                    {
                        sb.Append("\n");
                        sb.Append(Lib.Color(rb.rate > 0.0,
                                            Lib.BuildString("+", Lib.HumanReadableRate(Math.Abs(rb.rate)), "   "), Lib.Kolor.PosRate,             // spaces to mitigate alignement issues
                                            Lib.BuildString("-", Lib.HumanReadableRate(Math.Abs(rb.rate)), "   "), Lib.Kolor.NegRate,             // spaces to mitigate alignement issues
                                            true));
                        sb.Append("\t");
                        sb.Append(rb.broker.Title);
                    }
                }

                string rate_tooltip = sb.ToString();

                // finally, render resource supply
                p.AddContent(label, Lib.HumanReadableDuration(res.DepletionTime()), rate_tooltip);
                ++supplies;
            }
        }
Пример #14
0
        // 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>"));
                }
            }
        }
Пример #15
0
        ///<summary> Modifiers Evaluate method used for the Monitors background and current vessel simulation </summary>
        public static double Evaluate(Vessel v, VesselData vd, VesselResources resources, List <string> modifiers)
        {
            double k = 1.0;

            foreach (string mod in modifiers)
            {
                switch (mod)
                {
                case "zerog":
                    k *= vd.EnvZeroG ? 1.0 : 0.0;
                    break;

                case "landed":
                    k *= vd.EnvLanded ? 1.0 : 0.0;
                    break;

                case "breathable":
                    k *= vd.EnvBreathable ? 1.0 : 0.0;
                    break;

                case "non_breathable":
                    k *= vd.EnvBreathable ? 0.0 : 1.0;
                    break;

                case "temperature":
                    k *= vd.EnvTempDiff;
                    break;

                case "radiation":
                    k *= vd.EnvHabitatRadiation;
                    break;

                case "shielding":
                    k *= 1.0 - vd.Shielding;
                    break;

                case "volume":
                    k *= vd.Volume;
                    break;

                case "surface":
                    k *= vd.Surface;
                    break;

                case "living_space":
                    k /= vd.LivingSpace;
                    break;

                case "comfort":
                    k /= vd.Comforts.factor;
                    break;

                case "pressure":
                    k *= vd.Pressure > Settings.PressureThreshold ? 1.0 : Settings.PressureFactor;
                    break;

                case "poisoning":
                    k *= vd.Poisoning > Settings.PoisoningThreshold ? 1.0 : Settings.PoisoningFactor;
                    break;

                case "per_capita":
                    k /= (double)Math.Max(vd.CrewCount, 1);
                    break;

                default:
                    k *= resources.GetResource(v, mod).Amount;
                    break;
                }
            }
            return(k);
        }
Пример #16
0
        static void ProcessConverter(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleResourceConverter converter, VesselResources resources, double elapsed_s)
        {
            // note: ignore stock temperature mechanic of converters
            // note: ignore auto shutdown
            // note: non-mandatory resources 'dynamically scale the ratios', that is exactly what mandatory resources do too (DERP ALERT)
            // note: 'undo' stock behavior by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation)

            // if active
            if (Lib.Proto.GetBool(m, "IsActivated"))
            {
                // determine if vessel is full of all output resources
                // note: comparing against previous amount
                bool full = true;
                foreach (var or in converter.outputList)
                {
                    ResourceInfo res = resources.GetResource(v, or.ResourceName);
                    full &= (res.Level >= converter.FillAmount - double.Epsilon);
                }

                // if not full
                if (!full)
                {
                    // deduce crew bonus
                    int exp_level = -1;
                    if (converter.UseSpecialistBonus)
                    {
                        foreach (ProtoCrewMember c in Lib.CrewList(v))
                        {
                            if (c.experienceTrait.Effects.Find(k => k.Name == converter.ExperienceEffect) != null)
                            {
                                exp_level = Math.Max(exp_level, c.experienceLevel);
                            }
                        }
                    }
                    double exp_bonus = exp_level < 0
                                          ? converter.EfficiencyBonus * converter.SpecialistBonusBase
                                          : converter.EfficiencyBonus * (converter.SpecialistBonusBase + (converter.SpecialistEfficiencyFactor * (exp_level + 1)));

                    // create and commit recipe
                    ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.StockConverter);
                    foreach (var ir in converter.inputList)
                    {
                        recipe.AddInput(ir.ResourceName, ir.Ratio * exp_bonus * elapsed_s);
                    }
                    foreach (var or in converter.outputList)
                    {
                        recipe.AddOutput(or.ResourceName, or.Ratio * exp_bonus * elapsed_s, or.DumpExcess);
                    }
                    resources.AddRecipe(recipe);
                }

                // undo stock behavior by forcing last_update_time to now
                Lib.Proto.Set(m, "lastUpdateTime", Planetarium.GetUniversalTime());
            }
        }
Пример #17
0
        public static void Update(Vessel v, VesselData vd, VesselResources resources, double elapsed_s)
        {
            if (!Lib.IsVessel(v))
            {
                return;
            }

            // get most used resource handlers
            ResourceInfo ec = resources.GetResource(v, "ElectricCharge");

            List <ResourceInfo>         allResources       = resources.GetAllResources(v);
            Dictionary <string, double> availableResources = new Dictionary <string, double>();

            foreach (var ri in allResources)
            {
                availableResources[ri.ResourceName] = ri.Amount;
            }
            List <KeyValuePair <string, double> > resourceChangeRequests = new List <KeyValuePair <string, double> >();

            foreach (var e in Background_PMs(v))
            {
                switch (e.type)
                {
                case Module_type.Reliability: Reliability.BackgroundUpdate(v, e.p, e.m, e.module_prefab as Reliability, elapsed_s); break;

                case Module_type.Experiment: (e.module_prefab as Experiment).BackgroundUpdate(v, vd, e.m, ec, resources, elapsed_s); break;                         // experiments use the prefab as a singleton instead of a static method

                case Module_type.Greenhouse: Greenhouse.BackgroundUpdate(v, e.m, e.module_prefab as Greenhouse, vd, resources, elapsed_s); break;

                case Module_type.GravityRing: GravityRing.BackgroundUpdate(v, e.p, e.m, e.module_prefab as GravityRing, ec, elapsed_s); break;

                case Module_type.Harvester: Harvester.BackgroundUpdate(v, e.m, e.module_prefab as Harvester, elapsed_s); break;                         // Kerbalism ground and air harvester module

                case Module_type.Laboratory: Laboratory.BackgroundUpdate(v, e.p, e.m, e.module_prefab as Laboratory, ec, elapsed_s); break;

                case Module_type.Command: ProcessCommand(v, e.p, e.m, e.module_prefab as ModuleCommand, resources, elapsed_s); break;

                case Module_type.Generator: ProcessGenerator(v, e.p, e.m, e.module_prefab as ModuleGenerator, resources, elapsed_s); break;

                case Module_type.Converter: ProcessConverter(v, e.p, e.m, e.module_prefab as ModuleResourceConverter, resources, elapsed_s); break;

                case Module_type.Drill: ProcessDrill(v, e.p, e.m, e.module_prefab as ModuleResourceHarvester, resources, elapsed_s); break;                         // Stock ground harvester module

                // case Module_type.AsteroidDrill: ProcessAsteroidDrill(v, e.p, e.m, e.module_prefab as ModuleAsteroidDrill, resources, elapsed_s); break; // Stock asteroid harvester module
                case Module_type.StockLab: ProcessStockLab(v, e.p, e.m, e.module_prefab as ModuleScienceConverter, ec, elapsed_s); break;

                case Module_type.Light: ProcessLight(v, e.p, e.m, e.module_prefab as ModuleLight, ec, elapsed_s); break;

                case Module_type.Scanner: KerbalismScansat.BackgroundUpdate(v, e.p, e.m, e.module_prefab as KerbalismScansat, e.part_prefab, vd, ec, elapsed_s); break;

                case Module_type.FissionGenerator: ProcessFissionGenerator(v, e.p, e.m, e.module_prefab, ec, elapsed_s); break;

                case Module_type.RadioisotopeGenerator: ProcessRadioisotopeGenerator(v, e.p, e.m, e.module_prefab, ec, elapsed_s); break;

                case Module_type.CryoTank: ProcessCryoTank(v, e.p, e.m, e.module_prefab, resources, ec, elapsed_s); break;

                case Module_type.FNGenerator: ProcessFNGenerator(v, e.p, e.m, e.module_prefab, ec, elapsed_s); break;

                case Module_type.SolarPanelFixer: SolarPanelFixer.BackgroundUpdate(v, e.m, e.module_prefab as SolarPanelFixer, vd, ec, elapsed_s); break;

                case Module_type.KerbalismSentinel: KerbalismSentinel.BackgroundUpdate(v, e.m, e.module_prefab as KerbalismSentinel, vd, ec, elapsed_s); break;

                case Module_type.APIModule: ProcessApiModule(v, e.p, e.m, e.part_prefab, e.module_prefab, resources, availableResources, resourceChangeRequests, elapsed_s); break;
                }
            }
        }
Пример #18
0
 static void ProcessCommand(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleCommand command, VesselResources resources, double elapsed_s)
 {
     // do not consume if this is a MCM with no crew
     // rationale: for consistency, the game doesn't consume resources for MCM without crew in loaded vessels
     //            this make some sense: you left a vessel with some battery and nobody on board, you expect it to not consume EC
     if (command.minimumCrew == 0 || p.protoModuleCrew.Count > 0)
     {
         // for each input resource
         foreach (ModuleResource ir in command.resHandler.inputResources)
         {
             // consume the resource
             resources.Consume(v, ir.name, ir.rate * elapsed_s, ResourceBroker.Command);
         }
     }
 }
Пример #19
0
        private static void ProcessApiModule(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m,
                                             Part part_prefab, PartModule module_prefab, VesselResources resources, Dictionary <string, double> availableResources, List <KeyValuePair <string, double> > resourceChangeRequests, double elapsed_s)
        {
            resourceChangeRequests.Clear();

            try
            {
                string title = BackgroundDelegate.Instance(module_prefab).invoke(v, p, m, module_prefab, part_prefab, availableResources, resourceChangeRequests, elapsed_s);

                foreach (var cr in resourceChangeRequests)
                {
                    if (cr.Value > 0)
                    {
                        resources.Produce(v, cr.Key, cr.Value * elapsed_s, ResourceBroker.GetOrCreate(title));
                    }
                    else if (cr.Value < 0)
                    {
                        resources.Consume(v, cr.Key, -cr.Value * elapsed_s, ResourceBroker.GetOrCreate(title));
                    }
                }
            }
            catch (Exception ex)
            {
                Lib.Log("BackgroundUpdate in PartModule " + module_prefab.moduleName + " excepted: " + ex.Message + "\n" + ex.ToString());
            }
        }
Пример #20
0
        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);
        }
Пример #21
0
        // Doesn't work since squad refactored the ModuleAsteroidInfo / ModuleAsteroidResource for Comets (in 1.10 ?), and was probably not working even before that.
        static void ProcessAsteroidDrill(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleAsteroidDrill asteroid_drill, VesselResources resources, double elapsed_s)
        {
            // note: untested
            // note: ignore stock temperature mechanic of asteroid drills
            // note: ignore auto shutdown
            // note: 'undo' stock behavior by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation)

            // if active
            if (Lib.Proto.GetBool(m, "IsActivated"))
            {
                // get asteroid data
                ProtoPartModuleSnapshot asteroid_info     = null;
                ProtoPartModuleSnapshot asteroid_resource = null;
                foreach (ProtoPartSnapshot pp in v.protoVessel.protoPartSnapshots)
                {
                    if (asteroid_info == null)
                    {
                        asteroid_info = pp.modules.Find(k => k.moduleName == "ModuleAsteroidInfo");
                    }
                    if (asteroid_resource == null)
                    {
                        asteroid_resource = pp.modules.Find(k => k.moduleName == "ModuleAsteroidResource");
                    }
                }

                // if there is actually an asteroid attached to this active asteroid drill (it should)
                if (asteroid_info != null && asteroid_resource != null)
                {
                    // get some data
                    double mass_threshold = Lib.Proto.GetDouble(asteroid_info, "massThresholdVal");
                    double mass           = Lib.Proto.GetDouble(asteroid_info, "currentMassVal");
                    double abundance      = Lib.Proto.GetDouble(asteroid_resource, "abundance");
                    string res_name       = Lib.Proto.GetString(asteroid_resource, "resourceName");
                    double res_density    = PartResourceLibrary.Instance.GetDefinition(res_name).density;

                    // if asteroid isn't depleted
                    if (mass > mass_threshold && abundance > double.Epsilon)
                    {
                        // deduce crew bonus
                        int exp_level = -1;
                        if (asteroid_drill.UseSpecialistBonus)
                        {
                            foreach (ProtoCrewMember c in Lib.CrewList(v))
                            {
                                if (c.experienceTrait.Effects.Find(k => k.Name == asteroid_drill.ExperienceEffect) != null)
                                {
                                    exp_level = Math.Max(exp_level, c.experienceLevel);
                                }
                            }
                        }
                        double exp_bonus = exp_level < 0
                                                ? asteroid_drill.EfficiencyBonus * asteroid_drill.SpecialistBonusBase
                                                : asteroid_drill.EfficiencyBonus * (asteroid_drill.SpecialistBonusBase + (asteroid_drill.SpecialistEfficiencyFactor * (exp_level + 1)));

                        // determine resource extracted
                        double res_amount = abundance * asteroid_drill.Efficiency * exp_bonus * elapsed_s;

                        // transform EC into mined resource
                        ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.StockDrill);
                        recipe.AddInput("ElectricCharge", asteroid_drill.PowerConsumption * elapsed_s);
                        recipe.AddOutput(res_name, res_amount, true);
                        resources.AddRecipe(recipe);

                        // if there was ec
                        // note: comparing against amount in previous simulation step
                        if (resources.GetResource(v, "ElectricCharge").Amount > double.Epsilon)
                        {
                            // consume asteroid mass
                            Lib.Proto.Set(asteroid_info, "currentMassVal", (mass - res_density * res_amount));
                        }
                    }
                }

                // undo stock behavior by forcing last_update_time to now
                Lib.Proto.Set(m, "lastUpdateTime", Planetarium.GetUniversalTime());
            }
        }
Пример #22
0
        static void ProcessDrill(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleResourceHarvester harvester, VesselResources resources, double elapsed_s)
        {
            // note: ignore stock temperature mechanic of harvesters
            // note: ignore auto shutdown
            // note: ignore depletion (stock seem to do the same)
            // note: 'undo' stock behavior by forcing lastUpdateTime to now (to minimize overlapping calculations from this and stock post-facto simulation)

            // if active
            if (Lib.Proto.GetBool(m, "IsActivated"))
            {
                // do nothing if full
                // note: comparing against previous amount
                if (resources.GetResource(v, harvester.ResourceName).Level < harvester.FillAmount - double.Epsilon)
                {
                    // deduce crew bonus
                    int exp_level = -1;
                    if (harvester.UseSpecialistBonus)
                    {
                        foreach (ProtoCrewMember c in Lib.CrewList(v))
                        {
                            if (c.experienceTrait.Effects.Find(k => k.Name == harvester.ExperienceEffect) != null)
                            {
                                exp_level = Math.Max(exp_level, c.experienceLevel);
                            }
                        }
                    }
                    double exp_bonus = exp_level < 0
                                          ? harvester.EfficiencyBonus * harvester.SpecialistBonusBase
                                          : harvester.EfficiencyBonus * (harvester.SpecialistBonusBase + (harvester.SpecialistEfficiencyFactor * (exp_level + 1)));

                    // detect amount of ore in the ground
                    AbundanceRequest request = new AbundanceRequest
                    {
                        Altitude     = v.altitude,
                        BodyId       = v.mainBody.flightGlobalsIndex,
                        CheckForLock = false,
                        Latitude     = v.latitude,
                        Longitude    = v.longitude,
                        ResourceType = (HarvestTypes)harvester.HarvesterType,
                        ResourceName = harvester.ResourceName
                    };
                    double abundance = ResourceMap.Instance.GetAbundance(request);

                    // if there is actually something (should be if active when unloaded)
                    if (abundance > harvester.HarvestThreshold)
                    {
                        // create and commit recipe
                        ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.StockDrill);
                        foreach (var ir in harvester.inputList)
                        {
                            recipe.AddInput(ir.ResourceName, ir.Ratio * elapsed_s);
                        }
                        recipe.AddOutput(harvester.ResourceName, abundance * harvester.Efficiency * exp_bonus * elapsed_s, true);
                        resources.AddRecipe(recipe);
                    }
                }

                // undo stock behavior by forcing last_update_time to now
                Lib.Proto.Set(m, "lastUpdateTime", Planetarium.GetUniversalTime());
            }
        }
Пример #23
0
        /// <summary>
        /// Execute the recipe and record deferred consumption/production for inputs/ouputs.
        /// This need to be called multiple times until left &lt;= 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);
        }
Пример #24
0
        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;
            }
        }
Пример #25
0
        public void Execute(Vessel v, VesselData vd, VesselResources resources, double elapsed_s)
        {
            // store list of crew to kill
            List <ProtoCrewMember> deferred_kills = new List <ProtoCrewMember>();

            // get input resource handler
            ResourceInfo res = input.Length > 0 ? resources.GetResource(v, input) : null;

            // determine message variant
            uint variant = vd.EnvTemperature < Settings.LifeSupportSurvivalTemperature ? 0 : 1u;

            // get product of all environment modifiers
            double k = Modifiers.Evaluate(v, vd, resources, modifiers);

            bool lifetime_enabled = PreferencesRadiation.Instance.lifetime;

            // for each crew
            foreach (ProtoCrewMember c in Lib.CrewList(v))
            {
                // get kerbal data
                KerbalData kd = DB.Kerbal(c.name);

                // skip rescue kerbals
                if (kd.rescue)
                {
                    continue;
                }

                // skip disabled kerbals
                if (kd.disabled)
                {
                    continue;
                }

                // get kerbal property data from db
                RuleData rd = kd.Rule(name);
                rd.lifetime = lifetime_enabled && lifetime;

                // influence consumption by elapsed time
                double step = elapsed_s;

                // if interval-based
                if (interval > 0.0)
                {
                    // accumulate time
                    rd.time_since += elapsed_s;

                    // determine number of intervals that has passed (can be 2 or more if elapsed_s > interval * 2)
                    step = Math.Floor(rd.time_since / interval);

                    // consume time
                    rd.time_since -= step * interval;
                }

                // if there is a resource specified
                if (res != null && rate > double.Epsilon)
                {
                    // get rate including per-kerbal variance
                    double resRate =
                        rate                                                        // consumption rate
                        * Variance(name, c, individuality)                          // kerbal-specific variance
                        * k;                                                        // product of environment modifiers

                    // determine amount of resource to consume

                    double required = resRate * step;                           // seconds elapsed or interval amount

                    // remember if a meal is consumed/produced in this simulation step
                    if (interval > 0.0)
                    {
                        double ratePerStep = resRate / interval;
                        res.UpdateIntervalRule(-required, -ratePerStep, name);
                        if (output.Length > 0)
                        {
                            ResourceCache.GetResource(v, output).UpdateIntervalRule(required * ratio, ratePerStep * ratio, name);
                        }
                    }

                    // if continuous, or if one or more intervals elapsed
                    if (step > 0.0)
                    {
                        // if there is no output
                        if (output.Length == 0)
                        {
                            // simply consume (that is faster)
                            res.Consume(required, name);
                        }
                        // if there is an output
                        else
                        {
                            // transform input into output resource
                            // - rules always dump excess overboard (because it is waste)
                            ResourceRecipe recipe = new ResourceRecipe(name);
                            recipe.AddInput(input, required);
                            recipe.AddOutput(output, required * ratio, true);
                            resources.AddRecipe(recipe);
                        }
                    }
                }

                // if continuous, or if one or more intervals elapsed
                if (step > 0.0)
                {
                    // degenerate:
                    // - if the environment modifier is not telling to reset (by being zero)
                    // - if this rule is resource-less, or if there was not enough resource in the vessel
                    if (k > 0.0 && (input.Length == 0 || res.Amount <= double.Epsilon))
                    {
                        rd.problem += degeneration                                   // degeneration rate per-second or per-interval
                                      * k                                            // product of environment modifiers
                                      * step                                         // seconds elapsed or by number of steps
                                      * Variance(name, c, variance);                 // kerbal-specific variance
                    }
                    // else slowly recover
                    else
                    {
                        rd.problem *= 1.0 / (1.0 + Math.Max(interval, 1.0) * step * 0.002);
                    }
                }

                bool do_breakdown = false;

                if (breakdown)
                {
                    // don't do breakdowns and don't show stress message if disabled
                    if (!PreferencesComfort.Instance.stressBreakdowns)
                    {
                        return;
                    }

                    // stress level
                    double breakdown_probability = rd.problem / warning_threshold;
                    breakdown_probability = Lib.Clamp(breakdown_probability, 0.0, 1.0);

                    // use the stupidity of a kerbal.
                    // however, nobody is perfect - not even a kerbal with a stupidity of 0.
                    breakdown_probability *= c.stupidity * 0.6 + 0.4;

                    // apply the weekly error rate
                    breakdown_probability *= PreferencesComfort.Instance.stressBreakdownRate;

                    // now we have the probability for one failure per week, based on the
                    // individual stupidity and stress level of the kerbal.

                    breakdown_probability = (breakdown_probability * elapsed_s) / (Lib.DaysInYear * Lib.HoursInDay * 3600);
                    if (breakdown_probability > Lib.RandomDouble())
                    {
                        do_breakdown = true;

                        // we're stressed out and just made a major mistake, this further increases the stress level...
                        rd.problem += warning_threshold * 0.05;                         // add 5% of the warning treshold to current stress level
                    }
                }

                // kill kerbal if necessary
                if (rd.problem >= fatal_threshold)
                {
#if DEBUG || DEVBUILD
                    Lib.Log("Rule " + name + " kills " + c.name + " at " + rd.problem + " " + degeneration + "/" + k + "/" + step + "/" + Variance(name, c, variance));
#endif
                    if (fatal_message.Length > 0)
                    {
                        Message.Post(breakdown ? Severity.breakdown : Severity.fatality, Lib.ExpandMsg(fatal_message, v, c, variant));
                    }

                    if (breakdown)
                    {
                        do_breakdown = true;

                        // move back between warning and danger level
                        rd.problem = (warning_threshold + danger_threshold) * 0.5;

                        // make sure next danger message is shown
                        rd.message = 1;
                    }
                    else
                    {
                        deferred_kills.Add(c);
                    }
                }
                // show messages
                else if (rd.problem >= danger_threshold && rd.message < 2)
                {
                    if (danger_message.Length > 0)
                    {
                        Message.Post(Severity.danger, Lib.ExpandMsg(danger_message, v, c, variant));
                    }
                    rd.message = 2;
                }
                else if (rd.problem >= warning_threshold && rd.message < 1)
                {
                    if (warning_message.Length > 0)
                    {
                        Message.Post(Severity.warning, Lib.ExpandMsg(warning_message, v, c, variant));
                    }
                    rd.message = 1;
                }
                else if (rd.problem < warning_threshold && rd.message > 0)
                {
                    if (relax_message.Length > 0)
                    {
                        Message.Post(Severity.relax, Lib.ExpandMsg(relax_message, v, c, variant));
                    }
                    rd.message = 0;
                }

                if (do_breakdown)
                {
                    // trigger breakdown event
                    Misc.Breakdown(v, c);
                }
            }

            // execute the deferred kills
            foreach (ProtoCrewMember c in deferred_kills)
            {
                Misc.Kill(v, c);
            }
        }
Пример #26
0
 static void ProcessGenerator(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, ModuleGenerator generator, VesselResources resources, double elapsed_s)
 {
     // if active
     if (Lib.Proto.GetBool(m, "generatorIsActive"))
     {
         // create and commit recipe
         ResourceRecipe recipe = new ResourceRecipe(ResourceBroker.StockConverter);
         foreach (ModuleResource ir in generator.resHandler.inputResources)
         {
             recipe.AddInput(ir.name, ir.rate * elapsed_s);
         }
         foreach (ModuleResource or in generator.resHandler.outputResources)
         {
             recipe.AddOutput(or.name, or.rate * elapsed_s, true);
         }
         resources.AddRecipe(recipe);
     }
 }