Esempio n. 1
0
        // remove science sample, deleting the sample when it is empty
        public double Delete_sample(SubjectData subjectData, double amount = 0.0)
        {
            // get data
            Sample sample;

            if (samples.TryGetValue(subjectData, out sample))
            {
                // decrease amount of data stored in the sample
                if (amount == 0.0)
                {
                    amount = sample.size;
                }
                else
                {
                    amount = Math.Min(amount, sample.size);
                }

                double massDelta = sample.mass * amount / sample.size;
                sample.size -= amount;
                sample.mass -= massDelta;

                // keep track of data collected
                subjectData.RemoveDataCollectedInFlight(amount);

                // remove sample if empty
                if (sample.size <= 0.0)
                {
                    samples.Remove(subjectData);
                }

                return(massDelta);
            }
            return(0.0);
        }
Esempio n. 2
0
 public override void OnUpdate()
 {
     issue        = Lib.Proto.GetString(protoModule, "issue");
     status       = Lib.Proto.GetEnum(protoModule, "status", Experiment.ExpStatus.Stopped);
     subject      = ScienceDB.GetSubjectData(expInfo, Lib.Proto.GetInt(protoModule, "situationId"));
     scienceValue = Experiment.ScienceValue(subject);
 }
Esempio n. 3
0
        public static File Load(string integerSubjectId, ConfigNode node)
        {
            SubjectData subjectData;
            string      stockSubjectId = Lib.ConfigValue(node, "stockSubjectId", string.Empty);

            // the stock subject id is stored only if this is an asteroid sample, or a non-standard subject id
            if (stockSubjectId != string.Empty)
            {
                subjectData = ScienceDB.GetSubjectDataFromStockId(stockSubjectId);
            }
            else
            {
                subjectData = ScienceDB.GetSubjectData(integerSubjectId);
            }

            if (subjectData == null)
            {
                return(null);
            }

            double size = Lib.ConfigValue(node, "size", 0.0);

            if (double.IsNaN(size))
            {
                Lib.LogStack($"File has a NaN size on load : {subjectData.DebugStateInfo}", Lib.LogLevel.Error);
                return(null);
            }

            string resultText        = Lib.ConfigValue(node, "resultText", "");
            bool   useStockCrediting = Lib.ConfigValue(node, "useStockCrediting", false);

            return(new File(subjectData, size, useStockCrediting, resultText));
        }
Esempio n. 4
0
        public static Sample Load(string integerSubjectId, ConfigNode node)
        {
            SubjectData subjectData;
            string      stockSubjectId = Lib.ConfigValue(node, "stockSubjectId", string.Empty);

            // the stock subject id is stored only if this is an asteroid sample, or a non-standard subject id
            if (stockSubjectId != string.Empty)
            {
                subjectData = ScienceDB.GetSubjectDataFromStockId(stockSubjectId);
            }
            else
            {
                subjectData = ScienceDB.GetSubjectData(integerSubjectId);
            }

            if (subjectData == null)
            {
                return(null);
            }

            double size              = Lib.ConfigValue(node, "size", 0.0);
            string resultText        = Lib.ConfigValue(node, "resultText", "");
            bool   useStockCrediting = Lib.ConfigValue(node, "useStockCrediting", false);

            Sample sample = new Sample(subjectData, size, useStockCrediting, resultText);

            sample.analyze = Lib.ConfigValue(node, "analyze", false);
            sample.mass    = Lib.ConfigValue(node, "mass", 0.0);

            return(sample);
        }
Esempio n. 5
0
        // this is a fallback loading method for pre 3.1 / pre build 7212 files saved used the stock subject id
        public static Sample LoadOldFormat(string stockSubjectId, ConfigNode node)
        {
            SubjectData subjectData = ScienceDB.GetSubjectDataFromStockId(stockSubjectId);

            if (subjectData == null)
            {
                return(null);
            }

            double size = Lib.ConfigValue(node, "size", 0.0);

            if (double.IsNaN(size))
            {
                Lib.LogStack($"Sample has a NaN size on load : {subjectData.DebugStateInfo}", Lib.LogLevel.Error);
                return(null);
            }

            string resultText        = Lib.ConfigValue(node, "resultText", "");
            bool   useStockCrediting = Lib.ConfigValue(node, "useStockCrediting", false);

            Sample sample = new Sample(subjectData, size, useStockCrediting, resultText);

            sample.analyze = Lib.ConfigValue(node, "analyze", false);
            sample.mass    = Lib.ConfigValue(node, "mass", 0.0);

            return(sample);
        }
Esempio n. 6
0
        // add science data, creating new file or incrementing existing one
        public bool Record_file(SubjectData subjectData, double amount, bool allowImmediateTransmission = true, bool useStockCrediting = false)
        {
            if (dataCapacity >= 0 && FilesSize() + amount > dataCapacity)
            {
                return(false);
            }

            // create new data or get existing one
            File file;

            if (!files.TryGetValue(subjectData, out file))
            {
                file = new File(subjectData, 0.0, useStockCrediting);
                files.Add(subjectData, file);

                if (!allowImmediateTransmission)
                {
                    Send(subjectData.Id, false);
                }
            }

            // increase amount of data stored in the file
            file.size += amount;

            // keep track of data collected
            subjectData.AddDataCollectedInFlight(amount);

            return(true);
        }
Esempio n. 7
0
        // remove science data, deleting the file when it is empty
        public void Delete_file(SubjectData subjectData, double amount = 0.0)
        {
            // get data
            File file;

            if (files.TryGetValue(subjectData, out file))
            {
                // decrease amount of data stored in the file
                if (amount == 0.0)
                {
                    amount = file.size;
                }
                else
                {
                    amount = Math.Min(amount, file.size);
                }

                file.size -= amount;

                // keep track of data collected
                subjectData.RemoveDataCollectedInFlight(amount);

                // remove file if empty
                if (file.size <= 0.0)
                {
                    files.Remove(subjectData);
                }
            }
        }
Esempio n. 8
0
        public static double StoreFile(Vessel vessel, SubjectData subjectData, double size, bool include_private = false)
        {
            if (size < double.Epsilon)
            {
                return(0);
            }

            // store what we can

            var drives = GetDrives(vessel, include_private);

            drives.Insert(0, Cache.WarpCache(vessel));

            foreach (var d in drives)
            {
                var available = d.FileCapacityAvailable();
                var chunk     = Math.Min(size, available);
                if (!d.Record_file(subjectData, chunk, true))
                {
                    break;
                }
                size -= chunk;

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

            return(size);
        }
        static bool Prefix(DeployedScienceExperiment __instance, ref bool __result)
        {
            // get private vars
            ScienceSubject subject                = Lib.ReflectionValue <ScienceSubject>(__instance, "subject");
            float          storedScienceData      = Lib.ReflectionValue <float>(__instance, "storedScienceData");
            float          transmittedScienceData = Lib.ReflectionValue <float>(__instance, "transmittedScienceData");
            Vessel         ControllerVessel       = Lib.ReflectionValue <Vessel>(__instance, "ControllerVessel");

            //Lib.Log("SendDataToComms!: " + subject.title);
            if (__instance.Experiment != null && !(__instance.ExperimentVessel == null) && subject != null && !(__instance.Cluster == null) && __instance.sciencePart.Enabled && !(storedScienceData <= 0f) && __instance.ExperimentSituationValid)
            {
                /*	if (!__instance.TimeToSendStoredData())
                 *      {
                 *              __result = true;
                 *              Lib.Log(Lib.BuildString("BREAKING GROUND bailout 1"));
                 *              return false;
                 *      } */

                if (ControllerVessel == null && __instance.Cluster != null)
                {
                    Lib.ReflectionCall(__instance, "SetControllerVessel");
                    ControllerVessel = Lib.ReflectionValue <Vessel>(__instance, "ControllerVessel");
                }

                /*
                 * Part control;
                 * FlightGlobals.FindLoadedPart(__instance.Cluster.ControlModulePartId, out control);
                 * if(control == null) {
                 *      //Lib.Log("DeployedScienceExperiment: couldn't find control module");
                 *      __result = true;
                 *      Lib.Log(Lib.BuildString("BREAKING GROUND bailout 2"));
                 *      return false;
                 * }
                 */

                List <Drive> drives      = Drive.GetDrives(ControllerVessel, false);
                SubjectData  subjectData = ScienceDB.GetSubjectDataFromStockId(subject.id);
                foreach (Drive drive in drives)
                {
                    //Lib.Log(Lib.BuildString("BREAKING GROUND -- ", subject.id, " | ", storedScienceData.ToString()));
                    if (drive.Record_file(subjectData, storedScienceData, true))
                    {
                        //Lib.Log("BREAKING GROUND -- file recorded!");
                        Lib.ReflectionValue <float>(__instance, "transmittedScienceData", transmittedScienceData + storedScienceData);
                        Lib.ReflectionValue <float>(__instance, "storedScienceData", 0f);
                        break;
                    }
                    else
                    {
                        //Lib.Log("BREAKING GROUND -- file NOT recorded!");
                        __result = true;
                        return(false);
                    }
                }
                __result = false;
            }
            return(false);            // always return false so we don't continue to the original code
        }
Esempio n. 10
0
        // set analyze flag for a sample
        public void Analyze(SubjectData subjectData, bool b)
        {
            Sample sample;

            if (samples.TryGetValue(subjectData, out sample))
            {
                sample.analyze = b;
            }
        }
Esempio n. 11
0
        static bool Prefix(ModuleComet __instance, ref ScienceExperiment ___experiment)
        {
            // Patch only if science is enabled
            if (!Features.Science)
            {
                return(true);
            }

            // stock ModuleAsteroid.performSampleExperiment code : get situation and check availablility
            ExperimentSituations experimentSituation = ScienceUtil.GetExperimentSituation(__instance.vessel);
            string message = string.Empty;

            if (!ScienceUtil.RequiredUsageExternalAvailable(__instance.vessel, FlightGlobals.ActiveVessel, (ExperimentUsageReqs)__instance.experimentUsageMask, ___experiment, ref message))
            {
                ScreenMessages.PostScreenMessage("<b><color=orange>" + message + "</color></b>", 6f, ScreenMessageStyle.UPPER_LEFT);
                return(false);
            }

            if (!___experiment.IsAvailableWhile(experimentSituation, __instance.vessel.mainBody))
            {
                ScreenMessages.PostScreenMessage(Localizer.Format("#autoLOC_230133", ___experiment.experimentTitle), 5f, ScreenMessageStyle.UPPER_CENTER);
                return(false);
            }

            // stock ModuleAsteroid.performSampleExperiment code : create subject
            ScienceSubject subject = ResearchAndDevelopment.GetExperimentSubject(___experiment, experimentSituation, __instance.part.partInfo.name + __instance.part.flightID, __instance.part.partInfo.title, __instance.vessel.mainBody, string.Empty, string.Empty);

            // put the data on the EVA kerbal drive.
            if (FlightGlobals.ActiveVessel == null)
            {
                return(false);
            }
            double size  = ___experiment.baseValue * ___experiment.dataScale;
            Drive  drive = Drive.SampleDrive(FlightGlobals.ActiveVessel.KerbalismData(), size);

            if (drive != null)
            {
                double      mass        = size * Settings.AsteroidSampleMassPerMB;
                SubjectData subjectData = ScienceDB.GetSubjectDataFromStockId(subject.id, null, __instance.part.partInfo.title);
                drive.Record_sample(subjectData, size, mass, true);
                Message.Post(Lib.BuildString("<b><color=ffffff>", subject.title, "</color></b>\n", (mass * 1000.0).ToString("F1"), "<b><i> Kg of sample stored</i></b>"));
            }
            else
            {
                Message.Post("Not enough sample storage available");
            }

            // don't call TakeSampleEVAEvent() (this will also prevent the call to ModuleAsteroid.performSampleExperiment)
            return(false);
        }
Esempio n. 12
0
        public void DumpData(ScienceData data)
        {
            SubjectData subjectData = ScienceDB.GetSubjectDataFromStockId(data.subjectID);

            // remove the data
            if (data.baseTransmitValue > float.Epsilon || data.transmitBonus > float.Epsilon)
            {
                drive.Delete_file(subjectData, data.dataAmount);
            }
            else
            {
                drive.Delete_sample(subjectData, data.dataAmount);
            }
        }
Esempio n. 13
0
 public File(SubjectData subjectData, double size = 0.0, bool useStockCrediting = false, string resultText = "")
 {
     this.subjectData       = subjectData;
     this.size              = size;
     this.useStockCrediting = useStockCrediting;
     if (string.IsNullOrEmpty(resultText))
     {
         this.resultText = ResearchAndDevelopment.GetResults(subjectData.StockSubjectId);
     }
     else
     {
         this.resultText = resultText;
     }
 }
Esempio n. 14
0
        // this is a fallback loading method for pre 3.1 / pre build 7212 files saved used the stock subject id
        public static File LoadOldFormat(string stockSubjectId, ConfigNode node)
        {
            SubjectData subjectData = ScienceDB.GetSubjectDataFromStockId(stockSubjectId);

            if (subjectData == null)
            {
                return(null);
            }

            double size              = Lib.ConfigValue(node, "size", 0.0);
            string resultText        = Lib.ConfigValue(node, "resultText", "");
            bool   useStockCrediting = Lib.ConfigValue(node, "useStockCrediting", false);

            return(new File(subjectData, size, useStockCrediting, resultText));
        }
Esempio n. 15
0
        public double SampleCapacityAvailable(SubjectData subject = null)
        {
            if (sampleCapacity < 0)
            {
                return(double.MaxValue);
            }

            double result = Lib.SlotsToSampleSize(sampleCapacity - SamplesSize());

            if (subject != null && samples.ContainsKey(subject))
            {
                int    slotsForMyFile       = Lib.SampleSizeToSlots(samples[subject].size);
                double amountLostToSlotting = Lib.SlotsToSampleSize(slotsForMyFile) - samples[subject].size;
                result += amountLostToSlotting;
            }
            return(result);
        }
Esempio n. 16
0
        // add science sample, creating new sample or incrementing existing one
        public bool Record_sample(SubjectData subjectData, double amount, double mass, bool useStockCrediting = false)
        {
            int currentSampleSlots = SamplesSize();

            if (sampleCapacity >= 0)
            {
                if (!samples.ContainsKey(subjectData) && currentSampleSlots >= sampleCapacity)
                {
                    // can't take a new sample if we're already at capacity
                    return(false);
                }
            }

            Sample sample;

            if (samples.ContainsKey(subjectData) && sampleCapacity >= 0)
            {
                // test if adding the amount to the sample would exceed our capacity
                sample = samples[subjectData];

                int existingSampleSlots = Lib.SampleSizeToSlots(sample.size);
                int newSampleSlots      = Lib.SampleSizeToSlots(sample.size + amount);
                if (currentSampleSlots - existingSampleSlots + newSampleSlots > sampleCapacity)
                {
                    return(false);
                }
            }

            // create new data or get existing one
            if (!samples.TryGetValue(subjectData, out sample))
            {
                sample         = new Sample(subjectData, 0.0, useStockCrediting);
                sample.analyze = PreferencesScience.Instance.analyzeSamples;
                samples.Add(subjectData, sample);
            }

            // increase amount of data stored in the sample
            sample.size += amount;
            sample.mass += mass;

            // keep track of data collected
            subjectData.AddDataCollectedInFlight(amount);

            return(true);
        }
Esempio n. 17
0
        // TODO do something about limited capacity...
        // EVAs returning should get a warning if needed
        // TODO : this should not be used for EVA boarding, too much information is lost in the conversion
        public void ReturnData(ScienceData data)
        {
            SubjectData subjectData = ScienceDB.GetSubjectDataFromStockId(data.subjectID);

            if (subjectData == null)
            {
                return;
            }

            if (data.baseTransmitValue > Science.maxXmitDataScalarForSample || data.transmitBonus > Science.maxXmitDataScalarForSample)
            {
                drive.Record_file(subjectData, data.dataAmount);
            }
            else
            {
                drive.Record_sample(subjectData, data.dataAmount, subjectData.ExpInfo.MassPerMB * data.dataAmount);
            }
        }
Esempio n. 18
0
        public static void BackgroundUpdate(Vessel v, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, Laboratory lab, ResourceInfo ec, double elapsed_s)
        {
            // if enabled
            if (Lib.Proto.GetBool(m, "running"))
            {
                // if a researcher is not required, or the researcher is present
                background_researcher_cs = new CrewSpecs(lab.researcher);
                if (!background_researcher_cs || background_researcher_cs.Check(p.protoModuleCrew))
                {
                    double rate = lab.analysis_rate;
                    if (background_researcher_cs)
                    {
                        int    bonus     = background_researcher_cs.Bonus(p.protoModuleCrew);
                        double crew_gain = 1 + bonus * Settings.LaboratoryCrewLevelBonus;
                        crew_gain = Lib.Clamp(crew_gain, 1, Settings.MaxLaborartoryBonus);
                        rate     *= crew_gain;
                    }

                    // get sample to analyze
                    background_sample = NextSample(v);

                    // if there is a sample to analyze
                    if (background_sample != null)
                    {
                        // consume EC
                        ec.Consume(lab.ec_rate * elapsed_s, "laboratory");

                        // if there was ec
                        // - comparing against amount in previous simulation step
                        if (ec.Amount > double.Epsilon)
                        {
                            // analyze the sample
                            var status = Analyze(v, background_sample, rate * elapsed_s);
                            if (status != Status.RUNNING)
                            {
                                Lib.Proto.Set(m, "running", false);
                            }
                        }
                    }
                }
            }
        }
Esempio n. 19
0
        public File(SubjectData subjectData, double size = 0.0, bool useStockCrediting = false, string resultText = "")
        {
            this.subjectData = subjectData;
            this.size        = size;
            if (double.IsNaN(size))
            {
                Lib.LogStack($"File has a NaN size on creation : {subjectData.DebugStateInfo}", Lib.LogLevel.Error);
                this.size = 0.0;
            }

            this.useStockCrediting = useStockCrediting;
            if (string.IsNullOrEmpty(resultText))
            {
                this.resultText = ResearchAndDevelopment.GetResults(subjectData.StockSubjectId);
            }
            else
            {
                this.resultText = resultText;
            }
        }
Esempio n. 20
0
 private static void RestoreSampleMass(Vessel v, SubjectData filename, double restoredAmount)
 {
     if (v.loaded)            // loaded vessel
     {
         foreach (var experiment in v.FindPartModulesImplementing <Experiment>())
         {
             restoredAmount -= experiment.RestoreSampleMass(restoredAmount, filename.ExpInfo.ExperimentId);
         }
     }
     else             // unloaded vessel
     {
         foreach (ProtoPartModuleSnapshot m in Lib.FindModules(v.protoVessel, "Experiment"))
         {
             restoredAmount -= Experiment.RestoreSampleMass(restoredAmount, m, filename.ExpInfo.ExperimentId);
             if (restoredAmount < double.Epsilon)
             {
                 return;
             }
         }
     }
 }
Esempio n. 21
0
        /// <summary> Get a drive for storing samples. Will return null if there are no drives on the vessel </summary>
        public static Drive SampleDrive(VesselData vesselData, double size = 0, SubjectData subject = null)
        {
            Drive result = null;

            foreach (var drive in GetDrives(vesselData))
            {
                if (result == null)
                {
                    result = drive;
                    continue;
                }

                double available = drive.SampleCapacityAvailable(subject);
                if (size > double.Epsilon && available < size)
                {
                    continue;
                }
                if (available > result.SampleCapacityAvailable(subject))
                {
                    result = drive;
                }
            }
            return(result);
        }
Esempio n. 22
0
        public static void Init()
        {
            Lib.Log("ScienceDB init started");
            int subjectCount = 0;

            // get our extra defintions
            ConfigNode[] expDefNodes = GameDatabase.Instance.GetConfigNodes("EXPERIMENT_DEFINITION");

            // create our subject database
            // Note : GetExperimentIDs will force the creation of all ScienceExperiment objects,
            // no matter if the RnD instance is null or not because the ScienceExperiment dictionary is static.
            foreach (string experimentId in ResearchAndDevelopment.GetExperimentIDs())
            {
                if (experimentId == "recovery")
                {
                    continue;
                }

                ConfigNode kerbalismExpNode = null;
                foreach (ConfigNode expDefNode in expDefNodes)
                {
                    string id = string.Empty;
                    if (expDefNode.TryGetValue("id", ref id) && id == experimentId)
                    {
                        kerbalismExpNode = expDefNode.GetNode("KERBALISM_EXPERIMENT");                         // return null if not found
                        break;
                    }
                }

                ScienceExperiment stockDef = ResearchAndDevelopment.GetExperiment(experimentId);
                if (stockDef == null)
                {
                    Lib.Log("ScienceExperiment is null for experiment Id=" + experimentId + ", skipping...", Lib.LogLevel.Warning);
                    continue;
                }

                ExperimentInfo expInfo = new ExperimentInfo(stockDef, kerbalismExpNode);
                if (!experiments.ContainsKey(experimentId))
                {
                    experiments.Add(experimentId, expInfo);
                }
                if (!subjectByExpThenSituationId.ContainsKey(expInfo))
                {
                    subjectByExpThenSituationId.Add(expInfo, new Dictionary <int, SubjectData>());
                }

                for (int bodyIndex = 0; bodyIndex < FlightGlobals.Bodies.Count; bodyIndex++)
                {
                    CelestialBody body = FlightGlobals.Bodies[bodyIndex];

                    if (!expInfo.IgnoreBodyRestrictions && !expInfo.ExpBodyConditions.IsBodyAllowed(body))
                    {
                        continue;
                    }

                    // ScienceSituationUtils.validSituations is all situations in the enum, apart from the "None" value
                    foreach (ScienceSituation scienceSituation in ScienceSituationUtils.validSituations)
                    {
                        // test the ScienceExperiment situation mask
                        if (!scienceSituation.IsAvailableForExperiment(expInfo))
                        {
                            continue;
                        }

                        // don't add impossible body / situation combinations
                        if (!expInfo.IgnoreBodyRestrictions && !scienceSituation.IsAvailableOnBody(body))
                        {
                            continue;
                        }

                        // virtual biomes always have priority over normal biomes :
                        if (scienceSituation.IsVirtualBiomesRelevantForExperiment(expInfo))
                        {
                            foreach (VirtualBiome virtualBiome in expInfo.VirtualBiomes)
                            {
                                if (!virtualBiome.IsAvailableOnBody(body))
                                {
                                    continue;
                                }

                                SubjectData subjectData = null;
                                if (expInfo.HasDBSubjects)
                                {
                                    Situation situation = new Situation(bodyIndex, scienceSituation, (int)virtualBiome);
                                    subjectData = new SubjectData(expInfo, situation);
                                    subjectByExpThenSituationId[expInfo].Add(situation.Id, subjectData);
                                    knownStockSubjectsId.Add(subjectData.StockSubjectId);
                                    subjectCount++;
                                }

                                expBodiesSituationsBiomesSubject.AddSubject(expInfo, bodyIndex, scienceSituation, (int)virtualBiome, subjectData);
                                bodiesSituationsBiomesSubjects.AddSubject(bodyIndex, scienceSituation, (int)virtualBiome, subjectData);
                            }
                        }
                        // if the biome mask says the situation is biome dependant :
                        else if (scienceSituation.IsBodyBiomesRelevantForExperiment(expInfo) && body.BiomeMap != null && body.BiomeMap.Attributes.Length > 1)
                        {
                            for (int biomeIndex = 0; biomeIndex < body.BiomeMap.Attributes.Length; biomeIndex++)
                            {
                                SubjectData subjectData = null;
                                if (expInfo.HasDBSubjects)
                                {
                                    Situation situation = new Situation(bodyIndex, scienceSituation, biomeIndex);
                                    subjectData = new SubjectData(expInfo, situation);
                                    subjectByExpThenSituationId[expInfo].Add(situation.Id, subjectData);
                                    knownStockSubjectsId.Add(subjectData.StockSubjectId);
                                    subjectCount++;
                                }

                                expBodiesSituationsBiomesSubject.AddSubject(expInfo, bodyIndex, scienceSituation, biomeIndex, subjectData);
                                bodiesSituationsBiomesSubjects.AddSubject(bodyIndex, scienceSituation, biomeIndex, subjectData);
                            }
                        }
                        // else generate the global, biome agnostic situation
                        else
                        {
                            SubjectData subjectData = null;
                            if (expInfo.HasDBSubjects)
                            {
                                Situation situation = new Situation(bodyIndex, scienceSituation);
                                subjectData = new SubjectData(expInfo, situation);
                                subjectByExpThenSituationId[expInfo].Add(situation.Id, subjectData);
                                knownStockSubjectsId.Add(subjectData.StockSubjectId);
                                subjectCount++;
                            }

                            expBodiesSituationsBiomesSubject.AddSubject(expInfo, bodyIndex, scienceSituation, -1, subjectData);
                            bodiesSituationsBiomesSubjects.AddSubject(bodyIndex, scienceSituation, -1, subjectData);
                        }
                    }
                }
            }

            Lib.Log("ScienceDB init done : " + subjectCount + " subjects found");
        }
Esempio n. 23
0
            public void RemoveSubject(int bodyIndex, ScienceSituation scienceSituation, int biomeIndex, SubjectData subjectData)
            {
                SituationsBiomesSubjects situationsBiomesSubjects;

                if (!TryGetValue(bodyIndex, out situationsBiomesSubjects))
                {
                    return;
                }

                BiomesSubjects biomesSubjects;

                if (!situationsBiomesSubjects.TryGetValue(scienceSituation, out biomesSubjects))
                {
                    return;
                }

                List <SubjectData> subjects;

                if (!biomesSubjects.TryGetValue(biomeIndex, out subjects))
                {
                    return;
                }

                subjects.Remove(subjectData);
            }
Esempio n. 24
0
        public void FixedUpdate()
        {
            // do nothing in the editor
            if (Lib.IsEditor())
            {
                return;
            }

            // if enabled
            if (running)
            {
                // if a researcher is not required, or the researcher is present
                if (!researcher_cs || researcher_cs.Check(part.protoModuleCrew))
                {
                    // get next sample to analyze
                    current_sample = NextSample(vessel);

                    double rate = analysis_rate;
                    if (researcher_cs)
                    {
                        int    bonus     = researcher_cs.Bonus(part.protoModuleCrew);
                        double crew_gain = 1 + bonus * Settings.LaboratoryCrewLevelBonus;
                        crew_gain = Lib.Clamp(crew_gain, 1, Settings.MaxLaborartoryBonus);
                        rate     *= crew_gain;
                    }

                    // if there is a sample to analyze
                    if (current_sample != null)
                    {
                        // consume EC
                        ec = ResourceCache.GetResource(vessel, "ElectricCharge");
                        ec.Consume(ec_rate * Kerbalism.elapsed_s, "laboratory");

                        // if there was ec
                        // - comparing against amount in previous simulation step
                        if (ec.Amount > double.Epsilon)
                        {
                            // analyze the sample
                            status  = Analyze(vessel, current_sample, rate * Kerbalism.elapsed_s);
                            running = status == Status.RUNNING;
                        }
                        // if there was no ec
                        else
                        {
                            status = Status.NO_EC;
                        }
                    }
                    // if there is no sample to analyze
                    else
                    {
                        status = Status.NO_SAMPLE;
                    }
                }
                // if a researcher is required, but missing
                else
                {
                    status = Status.NO_RESEARCHER;
                }
            }
            // if disabled
            else
            {
                status = Status.DISABLED;
            }
        }
Esempio n. 25
0
        // analyze a sample
        private static Status Analyze(Vessel v, SubjectData subject, double amount)
        {
            Sample sample      = null;
            Drive  sampleDrive = null;

            foreach (var d in Drive.GetDrives(v, true))
            {
                if (d.samples.ContainsKey(subject) && d.samples[subject].analyze)
                {
                    sample      = d.samples[subject];
                    sampleDrive = d;
                    break;
                }
            }

            bool completed = false;

            if (sample != null)
            {
                completed = amount > sample.size;
                amount    = Math.Min(amount, sample.size);
            }

            Drive fileDrive = Drive.FileDrive(v.KerbalismData(), amount);

            if (fileDrive == null)
            {
                return(Status.NO_STORAGE);
            }

            if (sample != null)
            {
                bool recorded = fileDrive.Record_file(subject, amount, false);

                double massRemoved = 0.0;
                if (recorded)
                {
                    massRemoved = sampleDrive.Delete_sample(subject, amount);
                }
                else
                {
                    Message.Post(
                        Lib.Color(Lib.BuildString(Localizer.Format("#KERBALISM_Laboratory_Analysis"), " stopped"), Lib.Kolor.Red),
                        "Not enough space on hard drive"
                        );

                    return(Status.NO_STORAGE);
                }

                // return sample mass to experiment if needed
                if (massRemoved > 0.0)
                {
                    RestoreSampleMass(v, subject, massRemoved);
                }
            }

            // if the analysis is completed
            if (completed)
            {
                if (!PreferencesScience.Instance.analyzeSamples)
                {
                    // only inform the user if auto-analyze is turned off
                    // otherwise we could be spamming "Analysis complete" messages
                    Message.Post(Lib.BuildString(Lib.Color(Localizer.Format("#KERBALISM_Laboratory_Analysis"), Lib.Kolor.Science, true), "\n",
                                                 Localizer.Format("#KERBALISM_Laboratory_Analyzed", Lib.Bold(v.vesselName), Lib.Bold(subject.FullTitle))), localized_results);
                }

                if (PreferencesScience.Instance.transmitScience)
                {
                    fileDrive.Send(subject.Id, true);
                }

                // record landmark event
                if (!Lib.Landed(v))
                {
                    DB.landmarks.space_analysis = true;
                }
            }

            return(Status.RUNNING);
        }
Esempio n. 26
0
            public void AddSubject(ExperimentInfo expInfo, int bodyIndex, ScienceSituation scienceSituation, int biomeIndex, SubjectData subjectData)
            {
                BodiesSituationsBiomesSubject bodiesSituationsBiomesSubject;

                if (!TryGetValue(expInfo, out bodiesSituationsBiomesSubject))
                {
                    bodiesSituationsBiomesSubject = new BodiesSituationsBiomesSubject();
                    Add(expInfo, bodiesSituationsBiomesSubject);
                }

                SituationsBiomesSubject situationsBiomesSubject;

                if (!bodiesSituationsBiomesSubject.TryGetValue(bodyIndex, out situationsBiomesSubject))
                {
                    situationsBiomesSubject = new SituationsBiomesSubject();
                    bodiesSituationsBiomesSubject.Add(bodyIndex, situationsBiomesSubject);
                }

                BiomesSubject biomesSubject;

                if (!situationsBiomesSubject.TryGetValue(scienceSituation, out biomesSubject))
                {
                    biomesSubject = new BiomesSubject();
                    situationsBiomesSubject.Add(scienceSituation, biomesSubject);
                }

                List <SubjectData> subjectDataList;

                if (!biomesSubject.TryGetValue(biomeIndex, out subjectDataList))
                {
                    subjectDataList = new List <SubjectData>();
                    biomesSubject.Add(biomeIndex, subjectDataList);
                }

                if (subjectData != null)
                {
                    subjectDataList.Add(subjectData);
                }
            }
Esempio n. 27
0
        /// <summary>
        /// Create our SubjectData by parsing the stock "experiment@situation" subject id string.
        /// Used for asteroid samples, for compatibility with RnD archive data of removed mods and for converting stock ScienceData into SubjectData
        /// </summary>
        public static SubjectData GetSubjectDataFromStockId(string stockSubjectId, ScienceSubject RnDSubject = null)
        {
            SubjectData subjectData = null;

            if (unknownSubjectDatas.TryGetValue(stockSubjectId, out subjectData))
            {
                return(subjectData);
            }

            string[] expAndSit = stockSubjectId.Split(new char[] { '@' }, StringSplitOptions.RemoveEmptyEntries);

            if (expAndSit.Length != 2)
            {
                Lib.Log("Could not parse the SubjectData from subjectId '" + stockSubjectId + "' : bad format");
                return(null);
            }

            // the recovery experiment subject are created in ResearchAndDevelopment.reverseEngineerRecoveredVessel, called on the vessel recovery event
            // it use a non-standard situation system ("body" + a situation enum "RecoverySituations"). We ignore those.
            if (expAndSit[0] == "recovery")
            {
                return(null);
            }

            ExperimentInfo expInfo = GetExperimentInfo(expAndSit[0]);

            if (expInfo == null)
            {
                Lib.Log("Could not parse the SubjectData from subjectId '" + stockSubjectId + "' : the experiment id '" + expAndSit[0] + "' doesn't exists");
                return(null);
            }

            // for subject ids created with the ResearchAndDevelopment.GetExperimentSubject overload that take a "sourceUId" string,
            // the sourceUId is added to the situation after a "_"
            // in stock this seems to be used only for asteroids, and I don't think any mod use it.
            string extraSituationInfo = string.Empty;

            if (expAndSit[1].Contains("_"))
            {
                string[] sitAndAsteroid = expAndSit[1].Split('_');
                // remove
                expAndSit[1] = sitAndAsteroid[0];
                // asteroid are saved as "part.partInfo.name + part.flightID", and the part name always end with a randomly generated "AAA-000" string
                extraSituationInfo = Regex.Match(sitAndAsteroid[1], ".*?-[0-9][0-9][0-9]").Value;
                // if no match, just use the unmodified string
                if (extraSituationInfo == string.Empty)
                {
                    extraSituationInfo = sitAndAsteroid[1];
                }
            }

            string[] bodyAndBiome = expAndSit[1].Split(ScienceSituationUtils.validSituationsStrings, StringSplitOptions.RemoveEmptyEntries);
            string   situation;

            if (bodyAndBiome.Length == 1)
            {
                situation = expAndSit[1].Substring(bodyAndBiome[0].Length);
            }
            else if (bodyAndBiome.Length == 2)
            {
                situation = expAndSit[1].Substring(bodyAndBiome[0].Length, expAndSit[1].Length - bodyAndBiome[0].Length - bodyAndBiome[1].Length);
            }
            else
            {
                Lib.Log("Could not parse the SubjectData from subjectId '" + stockSubjectId + "' : the situation doesn't exists");
                return(null);
            }

            CelestialBody subjectBody = null;

            foreach (CelestialBody body in FlightGlobals.Bodies)
            {
                if (body.name == bodyAndBiome[0])
                {
                    subjectBody = body;
                    break;
                }
            }

            if (subjectBody == null)
            {
                // TODO : DMOS asteroid experiments are doing : "magScan@AsteroidInSpaceLowCarbonaceous7051371", those subjects will be discarded entirely here
                // because the body "Asteroid" doesn't exists, consequently it's impossible to create the Situation object.
                // To handle that, maybe we could implement a derived class "UnknownSituation" from Situation that can handle a completely random subject format
                Lib.Log("Could not parse the SubjectData from subjectId '" + stockSubjectId + "' : the body '" + bodyAndBiome[0] + "' doesn't exist");
                return(null);
            }

            ScienceSituation scienceSituation = ScienceSituationUtils.ScienceSituationDeserialize(situation);

            int biomeIndex = -1;

            if (bodyAndBiome.Length == 2 && ScienceSituationUtils.IsBodyBiomesRelevantForExperiment(scienceSituation, expInfo) && subjectBody.BiomeMap != null)
            {
                for (int i = 0; i < subjectBody.BiomeMap.Attributes.Length; i++)
                {
                    // Note : a stock subject has its spaces in the biome name removed but prior versions of kerbalism didn't do that,
                    // so we try to fix it, in order not to create duplicates in the RnD archives.

                    // TODO : also, we need to remove the "reentry" subjects, as stock is failing to parse them, altough this is in a try/catch block and handled gracefully.
                    string sanitizedBiome = bodyAndBiome[1].Replace(" ", string.Empty);
                    if (RnDSubject != null && extraSituationInfo == string.Empty && sanitizedBiome != bodyAndBiome[1])
                    {
                        string correctedSubjectId = expAndSit[0] + "@" + bodyAndBiome[0] + situation + sanitizedBiome;
                        RnDSubject.id = correctedSubjectId;

                        Dictionary <string, ScienceSubject> stockSubjects = Lib.ReflectionValue <Dictionary <string, ScienceSubject> >(ResearchAndDevelopment.Instance, "scienceSubjects");
                        if (stockSubjects.Remove(stockSubjectId) && !stockSubjects.ContainsKey(correctedSubjectId))
                        {
                            stockSubjects.Add(correctedSubjectId, RnDSubject);
                        }

                        Lib.Log("RnD subject load : misformatted subject '" + stockSubjectId + "' was corrected to '" + correctedSubjectId + "'");
                    }

                    if (subjectBody.BiomeMap.Attributes[i].name.Replace(" ", string.Empty).Equals(sanitizedBiome, StringComparison.OrdinalIgnoreCase))
                    {
                        biomeIndex = i;
                        break;
                    }
                }
            }

            int       bodyIndex       = subjectBody.flightGlobalsIndex;
            Situation vesselSituation = new Situation(bodyIndex, scienceSituation, biomeIndex);

            // if the subject is a "doable" subject, we should have it in the DB.
            if (extraSituationInfo == string.Empty)
            {
                subjectData = GetSubjectData(expInfo, vesselSituation);
            }

            // else create the subjectdata. this can happen either because :
            // - it's a subject using the stock "extra id" system (asteroid samples)
            // - the subject was created in RnD prior to an experiment definition config change
            // - it was created by a mod that does things in a non-stock way (ex : DMOS anomaly scans uses the anomaly name as biomes)
            if (subjectData == null)
            {
                if (bodyAndBiome.Length == 2 && bodyAndBiome[1] != string.Empty && string.IsNullOrEmpty(extraSituationInfo))
                {
                    extraSituationInfo = bodyAndBiome[1];
                }

                UnknownSubjectData unknownSubjectData = new UnknownSubjectData(expInfo, vesselSituation, stockSubjectId, RnDSubject, extraSituationInfo);
                subjectData = unknownSubjectData;
                unknownSubjectDatas.Add(stockSubjectId, unknownSubjectData);
                expBodiesSituationsBiomesSubject.AddSubject(subjectData.ExpInfo, bodyIndex, scienceSituation, biomeIndex, subjectData);
            }

            return(subjectData);
        }
Esempio n. 28
0
        public static void Load(ConfigNode node)
        {
            // RnD subjects don't exists in sandbox
            if (!Science.GameHasRnD)
            {
                // load sandbox science subjects
                sandboxSubjects.Clear();
                if (node.HasNode("sandboxScienceSubjects"))
                {
                    foreach (var subjectNode in node.GetNode("sandboxScienceSubjects").GetNodes())
                    {
                        ScienceSubject subject = new ScienceSubject(subjectNode);
                        sandboxSubjects.Add(subject.id, subject);
                    }
                }
            }
            else
            {
                // Load API subjects (require RnD)
                subjectsReceivedBuffer.Clear();
                subjectsReceivedValueBuffer.Clear();
                ConfigNode APISubjects = new ConfigNode();
                if (node.TryGetNode("APISubjects", ref APISubjects))
                {
                    foreach (ConfigNode subjectNode in APISubjects.GetNodes("Subject"))
                    {
                        string         subjectId = Lib.ConfigValue(subjectNode, "subjectId", string.Empty);
                        ScienceSubject subject   = ResearchAndDevelopment.GetSubjectByID(subjectId);
                        if (subject == null)
                        {
                            Lib.Log($"Warning : API subject '{subjectId}' not found in ResearchAndDevelopment");
                            continue;
                        }
                        subjectsReceivedBuffer.Add(subject);
                        subjectsReceivedValueBuffer.Add(Lib.ConfigValue(subjectNode, "science", 0.0));
                    }
                }
            }

            // load uncredited science (transmission buffer)
            uncreditedScience = Lib.ConfigValue(node, "uncreditedScience", 0.0);

            // Rebuild the list of persisted subjects
            persistedSubjects.Clear();
            foreach (ExperimentInfo expInfo in experiments.Values)
            {
                foreach (SubjectData subjectData in subjectByExpThenSituationId[expInfo].Values)
                {
                    subjectData.CheckRnD();
                    subjectData.ClearDataCollectedInFlight();
                }
            }

            // load science subjects persisted data
            if (node.HasNode("subjectData"))
            {
                foreach (var subjectNode in node.GetNode("subjectData").GetNodes())
                {
                    string      integerSubjectId = DB.From_safe_key(subjectNode.name);
                    SubjectData subjectData      = GetSubjectData(integerSubjectId);
                    if (subjectData != null)
                    {
                        subjectData.Load(subjectNode);
                    }
                }
            }

            //if (ResearchAndDevelopment.Instance == null)
            //	Lib.Log("ERROR : ResearchAndDevelopment.Instance is null on subjects load !");

            // remove unknown subjects from the database
            foreach (SubjectData subjectData in unknownSubjectDatas.Values)
            {
                int bodyIndex;
                int scienceSituation;
                int biomeIndex;

                Situation.IdToFields(subjectData.Situation.Id, out bodyIndex, out scienceSituation, out biomeIndex);

                expBodiesSituationsBiomesSubject.RemoveSubject(subjectData.ExpInfo, bodyIndex, (ScienceSituation)scienceSituation, biomeIndex, subjectData);
            }

            // clear the list
            unknownSubjectDatas.Clear();


            // find them again
            IEnumerable <ScienceSubject> stockSubjects;

            if (Science.GameHasRnD)
            {
                stockSubjects = ResearchAndDevelopment.GetSubjects();
            }
            else
            {
                stockSubjects = sandboxSubjects.Values;
            }

            foreach (ScienceSubject stockSubject in stockSubjects)
            {
                if (!knownStockSubjectsId.Contains(stockSubject.id))
                {
                    GetSubjectDataFromStockId(stockSubject.id, stockSubject);
                }
            }
        }
Esempio n. 29
0
        public static void Init()
        {
            Lib.Log("ScienceDB init started");
            int    subjectCount = 0;
            double totalScience = 0.0;

            // get our extra defintions
            ConfigNode[] expDefNodes = GameDatabase.Instance.GetConfigNodes("EXPERIMENT_DEFINITION");

            // create our subject database
            // Note : GetExperimentIDs will force the creation of all ScienceExperiment objects,
            // no matter if the RnD instance is null or not because the ScienceExperiment dictionary is static.
            foreach (string experimentId in ResearchAndDevelopment.GetExperimentIDs())
            {
                if (experimentId == "recovery")
                {
                    continue;
                }

                ConfigNode kerbalismExpNode = null;
                foreach (ConfigNode expDefNode in expDefNodes)
                {
                    string id = string.Empty;
                    if (expDefNode.TryGetValue("id", ref id) && id == experimentId)
                    {
                        kerbalismExpNode = expDefNode.GetNode("KERBALISM_EXPERIMENT");                         // return null if not found
                        break;
                    }
                }

                ScienceExperiment stockDef = ResearchAndDevelopment.GetExperiment(experimentId);
                if (stockDef == null)
                {
                    Lib.Log("ScienceExperiment is null for experiment Id=" + experimentId + ", skipping...", Lib.LogLevel.Warning);
                    continue;
                }

                ExperimentInfo expInfo = new ExperimentInfo(stockDef, kerbalismExpNode);
                if (!experiments.ContainsKey(experimentId))
                {
                    experiments.Add(experimentId, expInfo);
                }
                if (!subjectByExpThenSituationId.ContainsKey(expInfo))
                {
                    subjectByExpThenSituationId.Add(expInfo, new Dictionary <int, SubjectData>());
                }

                for (int bodyIndex = 0; bodyIndex < FlightGlobals.Bodies.Count; bodyIndex++)
                {
                    CelestialBody body = FlightGlobals.Bodies[bodyIndex];

                    if (!expInfo.IgnoreBodyRestrictions && !expInfo.ExpBodyConditions.IsBodyAllowed(body))
                    {
                        continue;
                    }

                    // ScienceSituationUtils.validSituations is all situations in the enum, apart from the "None" value
                    foreach (ScienceSituation scienceSituation in ScienceSituationUtils.validSituations)
                    {
                        // test the ScienceExperiment situation mask
                        if (!scienceSituation.IsAvailableForExperiment(expInfo))
                        {
                            continue;
                        }

                        // don't add impossible body / situation combinations
                        if (!expInfo.IgnoreBodyRestrictions && !scienceSituation.IsAvailableOnBody(body))
                        {
                            continue;
                        }

                        // virtual biomes always have priority over normal biomes :
                        if (scienceSituation.IsVirtualBiomesRelevantForExperiment(expInfo))
                        {
                            foreach (VirtualBiome virtualBiome in expInfo.VirtualBiomes)
                            {
                                if (!virtualBiome.IsAvailableOnBody(body))
                                {
                                    continue;
                                }

                                SubjectData subjectData = null;
                                if (expInfo.HasDBSubjects)
                                {
                                    Situation situation = new Situation(bodyIndex, scienceSituation, (int)virtualBiome);
                                    subjectData = new SubjectData(expInfo, situation);
                                    subjectByExpThenSituationId[expInfo].Add(situation.Id, subjectData);
                                    knownStockSubjectsId.Add(subjectData.StockSubjectId);
                                    subjectCount++;
                                    totalScience += subjectData.ScienceMaxValue;
                                }

                                expBodiesSituationsBiomesSubject.AddSubject(expInfo, bodyIndex, scienceSituation, (int)virtualBiome, subjectData);
                            }
                        }
                        // if the biome mask says the situation is biome dependant :
                        else if (scienceSituation.IsBodyBiomesRelevantForExperiment(expInfo) && body.BiomeMap != null && body.BiomeMap.Attributes.Length > 1)
                        {
                            for (int biomeIndex = 0; biomeIndex < body.BiomeMap.Attributes.Length; biomeIndex++)
                            {
                                SubjectData subjectData = null;
                                if (expInfo.HasDBSubjects)
                                {
                                    Situation situation = new Situation(bodyIndex, scienceSituation, biomeIndex);
                                    subjectData = new SubjectData(expInfo, situation);
                                    subjectByExpThenSituationId[expInfo].Add(situation.Id, subjectData);
                                    knownStockSubjectsId.Add(subjectData.StockSubjectId);
                                    subjectCount++;
                                    totalScience += subjectData.ScienceMaxValue;
                                }

                                expBodiesSituationsBiomesSubject.AddSubject(expInfo, bodyIndex, scienceSituation, biomeIndex, subjectData);
                            }
                        }
                        // else generate the global, biome agnostic situation
                        else
                        {
                            SubjectData subjectData = null;
                            if (expInfo.HasDBSubjects)
                            {
                                Situation situation = new Situation(bodyIndex, scienceSituation);
                                subjectData = new SubjectData(expInfo, situation);
                                subjectByExpThenSituationId[expInfo].Add(situation.Id, subjectData);
                                knownStockSubjectsId.Add(subjectData.StockSubjectId);
                                subjectCount++;
                                totalScience += subjectData.ScienceMaxValue;
                            }

                            expBodiesSituationsBiomesSubject.AddSubject(expInfo, bodyIndex, scienceSituation, -1, subjectData);
                        }
                    }
                }
            }

            // cache that call
            IEnumerable <ExperimentInfo> experimentInfosCache = ExperimentInfos;

            // first parse all the IncludeExperiment configs
            foreach (ExperimentInfo experimentInfo in experimentInfosCache)
            {
                experimentInfo.ParseIncludedExperiments();
            }

            // then check for infinite recursion from bad configs
            List <ExperimentInfo> chainedExperiments = new List <ExperimentInfo>();

            foreach (ExperimentInfo experimentInfo in experimentInfosCache)
            {
                chainedExperiments.Clear();
                ExperimentInfo.CheckIncludedExperimentsRecursion(experimentInfo, chainedExperiments);
            }

            // now we are sure all the include experiment chains are valid
            foreach (ExperimentInfo experimentInfo in experimentInfosCache)
            {
                // populate the included experiments chains at the subject level
                foreach (KeyValuePair <int, SubjectData> subjectInfo in subjectByExpThenSituationId[experimentInfo])
                {
                    foreach (ExperimentInfo includedInfo in experimentInfo.IncludedExperiments)
                    {
                        SubjectData subjectToInclude = GetSubjectData(includedInfo, subjectInfo.Key);

                        if (subjectToInclude != null)
                        {
                            subjectInfo.Value.IncludedSubjects.Add(subjectToInclude);
                        }
                    }
                }

                // Get the experiment description that will be shown in the science archive by calling GetInfo() on the first found partmodule using it
                // TODO: this isn't ideal, if there are several modules with different values (ex : data rate, ec rate...), the archive info will use the first found one.
                // Ideally we should revamp the whole handling of that (because it's a mess from the partmodule side too)
                experimentInfo.CompileModuleInfos();
            }

            Lib.Log($"ScienceDB init done : {subjectCount} subjects found, total science points : {totalScience.ToString("F1")}");
        }
Esempio n. 30
0
            public void RemoveSubject(ExperimentInfo expInfo, int bodyIndex, ScienceSituation scienceSituation, int biomeIndex, SubjectData subjectData)
            {
                BodiesSituationsBiomesSubject bodiesSituationsBiomesSubject;

                if (!TryGetValue(expInfo, out bodiesSituationsBiomesSubject))
                {
                    return;
                }

                SituationsBiomesSubject situationsBiomesSubject;

                if (!bodiesSituationsBiomesSubject.TryGetValue(bodyIndex, out situationsBiomesSubject))
                {
                    return;
                }

                BiomesSubject biomesSubject;

                if (!situationsBiomesSubject.TryGetValue(scienceSituation, out biomesSubject))
                {
                    return;
                }

                List <SubjectData> subjectDataList;

                if (!biomesSubject.TryGetValue(biomeIndex, out subjectDataList))
                {
                    return;
                }

                subjectDataList.Remove(subjectData);
            }