/// <summary> return the subject information for the given experiment and situation, or null if the situation isn't available. </summary> public static SubjectData GetSubjectData(ExperimentInfo expInfo, Situation situation) { int situationId; if (!situation.ScienceSituation.IsBiomesRelevantForExperiment(expInfo)) { situationId = situation.GetBiomeAgnosticId(); } else { situationId = situation.Id; } SubjectData subjectData; if (!subjectByExpThenSituationId[expInfo].TryGetValue(situationId, out subjectData)) { return(null); } return(subjectData); }
/// <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); }
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); } } }
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")}"); }
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"); }
public SubjectData(ExperimentInfo expInfo, Situation situation) { ExpInfo = expInfo; Situation = situation; Id = Lib.BuildString(ExpInfo.ExperimentId, "@", Situation.Id.ToString()); }
public void FixedUpdate() { if (scanner == null) { return; } if (!Features.Science) { return; } IsScanning = SCANsat.IsScanning(scanner); double new_coverage = SCANsat.Coverage(sensorType, vessel.mainBody); if (body_name == vessel.mainBody.name && new_coverage < body_coverage) { // SCANsat sometimes reports a coverage of 0, which is wrong new_coverage = body_coverage; } if (vessel.mainBody.name != body_name) { body_name = vessel.mainBody.name; body_coverage = new_coverage; } else { double coverage_delta = new_coverage - body_coverage; body_coverage = new_coverage; VesselData vd = vessel.KerbalismData(); if (IsScanning) { Situation scanSatSituation = new Situation(vessel.mainBody.flightGlobalsIndex, ScienceSituation.InSpaceHigh); SubjectData subject = ScienceDB.GetSubjectData(expInfo, scanSatSituation); if (subject == null) { return; } double size = expInfo.DataSize * coverage_delta / 100.0; // coverage is 0-100% size += warp_buffer; size = Drive.StoreFile(vessel, subject, size); if (size > double.Epsilon) { // we filled all drives up to the brim but were unable to store everything if (warp_buffer < double.Epsilon) { // warp buffer is empty, so lets store the rest there warp_buffer = size; size = 0; } else { // warp buffer not empty. that's ok if we didn't get new data if (coverage_delta < double.Epsilon) { size = 0; } // else we're scanning too fast. stop. } // cancel scanning and annoy the user if (size > double.Epsilon) { warp_buffer = 0; StopScan(); vd.scansat_id.Add(part.flightID); Message.Post(Lib.Color(Local.Scansat_Scannerhalted, Lib.Kolor.Red, true), Local.Scansat_Scannerhalted_text.Format("<b>" + vessel.vesselName + "</b>")); //"Scanner halted""Scanner halted on <<1>>. No storage left on vessel." } } } else if (vd.scansat_id.Contains(part.flightID)) { if (vd.DrivesFreeSpace / vd.DrivesCapacity > 0.9) // restart when 90% of capacity is available { StartScan(); vd.scansat_id.Remove(part.flightID); if (vd.cfg_ec) { Message.Post(Local.Scansat_sensorresumed.Format("<b>" + vessel.vesselName + "</b>")); //Lib.BuildString("SCANsat sensor resumed operations on <<1>>) } } } } }
public static void Load(ConfigNode node) { // RnD subjects don't exists in sandbox if (!Science.GameHasRnD) { return; } // 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); bodiesSituationsBiomesSubjects.RemoveSubject(bodyIndex, (ScienceSituation)scienceSituation, biomeIndex, subjectData); } // clear the list unknownSubjectDatas.Clear(); // find them again foreach (ScienceSubject stockSubject in ResearchAndDevelopment.GetSubjects()) { if (!knownStockSubjectsId.Contains(stockSubject.id)) { GetSubjectDataFromStockId(stockSubject.id, stockSubject); } } }