// 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); }
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); }
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)); }
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); }
// 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); }
// 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); }
// 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); } } }
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 }
// set analyze flag for a sample public void Analyze(SubjectData subjectData, bool b) { Sample sample; if (samples.TryGetValue(subjectData, out sample)) { sample.analyze = b; } }
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); }
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); } }
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; } }
// 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)); }
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); }
// 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); }
// 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); } }
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); } } } } } }
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; } }
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; } } } }
/// <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); }
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 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); }
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; } }
// 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); }
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); } }
/// <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 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); }