[KSPEvent(guiActive = false, guiActiveUnfocused = true, guiActiveUncommand = true, guiName = "#KERBALISM_HardDrive_TakeData", active = true, groupName = "Science", groupDisplayName = "#KERBALISM_Group_Science")] //Science public void TakeData() { // disable for dead eva kerbals Vessel v = FlightGlobals.ActiveVessel; if (v == null || EVA.IsDead(v)) { return; } // transfer data if (!Drive.Transfer(drive, v, PreferencesScience.Instance.sampleTransfer || Lib.CrewCount(v) > 0)) { Message.Post ( Lib.Color(Lib.BuildString(Local.HardDrive_WARNING_title), Lib.Kolor.Red, true), //"WARNING: not evering copied" Lib.BuildString(Local.HardDrive_WARNING) //"Storage is at capacity" ); } }
void VesselDock(GameEvents.FromToAction <Part, Part> e) { // note: // we do not forget vessel data here, it just became inactive // and ready to be implicitly activated again on undocking // we do however tweak the data of the vessel being docked a bit, // to avoid states getting out of sync, leading to unintuitive behaviours VesselData vd = DB.Vessel(e.from.vessel); vd.msg_belt = false; vd.msg_signal = false; vd.storm_age = 0.0; vd.storm_time = 0.0; vd.storm_state = 0; vd.supplies.Clear(); vd.scansat_id.Clear(); // merge drives data Drive.Transfer(e.from.vessel, e.to.vessel); }
public void StoreData() { // disable for dead eva kerbals Vessel v = FlightGlobals.ActiveVessel; if (v == null || EVA.IsDead(v)) { return; } // transfer data if (!Drive.Transfer(v, drive, PreferencesScience.Instance.sampleTransfer || Lib.CrewCount(v) > 0)) { Message.Post ( Lib.Color("red", Lib.BuildString("WARNING: not evering copied"), true), Lib.BuildString("Storage is at capacity") ); } }
// return name of file being transmitted from vessel specified public static string Transmitting(Vessel v, bool linked) { // never transmitting if science system is disabled if (!Features.Science) { return(string.Empty); } // not transmitting if unlinked if (!linked) { return(string.Empty); } // not transmitting if there is no ec left if (!Lib.IsPowered(v)) { return(string.Empty); } foreach (var p in Cache.WarpCache(v).files) { return(p.Key); } // get first file flagged for transmission, AND has a ts at least 5 seconds old or is > 0.001Mb in size foreach (var drive in Drive.GetDrives(v, true)) { double now = Planetarium.GetUniversalTime(); foreach (var p in drive.files) { if (drive.GetFileSend(p.Key) && (p.Value.ts + 3 < now || p.Value.size > min_file_size)) { return(p.Key); } } } // no file flagged for transmission return(string.Empty); }
void FromEVA(GameEvents.FromToAction<Part, Part> data) { // for each resource in the eva kerbal for (int i = 0; i < data.from.Resources.Count; ++i) { // get the resource PartResource res = data.from.Resources[i]; // add leftovers to the vessel data.to.RequestResource(res.resourceName, -res.amount); } // merge drives data Drive.Transfer(data.from.vessel, data.to.vessel); // forget vessel data DB.vessels.Remove(Lib.RootID(data.from.vessel)); // execute script DB.Vessel(data.to.vessel).computer.Execute(data.to.vessel, ScriptType.eva_in); }
public void DumpData(ScienceData data) { // get drive Drive drive = DB.Vessel(vessel).drive; // if not the preferred drive if (drive.location != part.flightID) { return; } // remove the data if (data.baseTransmitValue > float.Epsilon || data.transmitBonus > double.Epsilon) { drive.delete_file(data.subjectID, data.dataAmount); } else { drive.delete_sample(data.subjectID, data.dataAmount); } }
public VesselData() { msg_signal = false; msg_belt = false; cfg_ec = true; cfg_supply = true; cfg_signal = true; cfg_malfunction = true; cfg_storm = true; cfg_script = true; cfg_highlights = true; cfg_showlink = true; storm_time = 0.0; storm_age = 0.0; storm_state = 0; group = "NONE"; computer = new Computer(); drive = new Drive(); supplies = new Dictionary <string, SupplyData>(); scansat_id = new List <uint>(); }
static void Render_file(Panel p, string filename, File file, Drive drive, bool short_strings, double rate) { // get experiment info ExperimentInfo exp = Science.Experiment(filename); // render experiment name string exp_label = Lib.BuildString ( "<b>", Lib.Ellipsis(exp.name, Styles.ScaleStringLength(short_strings ? 24 : 38)), "</b> <size=", Styles.ScaleInteger(10).ToString(), ">", Lib.Ellipsis(exp.situation, Styles.ScaleStringLength((short_strings ? 32 : 62) - Lib.Ellipsis(exp.name, Styles.ScaleStringLength(short_strings ? 24 : 38)).Length)), "</size>" ); string exp_tooltip = Lib.BuildString ( exp.name, "\n", "<color=#aaaaaa>", exp.situation, "</color>" ); double exp_value = Science.Value(filename, file.size); if (exp_value > double.Epsilon) { exp_tooltip = Lib.BuildString(exp_tooltip, "\n<b>", Lib.HumanReadableScience(exp_value), "</b>"); } if (rate > 0) { exp_tooltip = Lib.BuildString(exp_tooltip, "\n<i>" + Lib.HumanReadableDuration(file.size / rate) + "</i>"); } p.AddContent(exp_label, Lib.HumanReadableDataSize(file.size), exp_tooltip); p.AddIcon(file.send ? Icons.send_cyan : Icons.send_black, "Flag the file for transmission to <b>DSN</b>", () => { file.send = !file.send; }); p.AddIcon(Icons.toggle_red, "Delete the file", () => Lib.Popup ( "Warning!", Lib.BuildString("Do you really want to delete ", exp.fullname, "?"), new DialogGUIButton("Delete it", () => drive.files.Remove(filename)), new DialogGUIButton("Keep it", () => { }) )); }
static void Render_sample(Panel p, string filename, Sample sample, Drive drive, bool short_strings) { // get experiment info ExperimentInfo exp = Science.Experiment(filename); // render experiment name string exp_label = Lib.BuildString ( "<b>", Lib.Ellipsis(exp.name, Styles.ScaleStringLength(short_strings ? 24 : 38)), "</b> <size=", Styles.ScaleInteger(10).ToString(), ">", Lib.Ellipsis(exp.situation, Styles.ScaleStringLength((short_strings ? 32 : 62) - Lib.Ellipsis(exp.name, Styles.ScaleStringLength(short_strings ? 24 : 38)).Length)), "</size>" ); string exp_tooltip = Lib.BuildString ( exp.name, "\n", "<color=#aaaaaa>", exp.situation, "</color>" ); double exp_value = Science.Value(filename, sample.size); if (exp_value > double.Epsilon) { exp_tooltip = Lib.BuildString(exp_tooltip, "\n<b>", Lib.HumanReadableScience(exp_value), "</b>"); } if (sample.mass > Double.Epsilon) { exp_tooltip = Lib.BuildString(exp_tooltip, "\n<b>", Lib.HumanReadableMass(sample.mass), "</b>"); } p.AddContent(exp_label, Lib.HumanReadableSampleSize(sample.size), exp_tooltip); p.AddIcon(sample.analyze ? Icons.lab_cyan : Icons.lab_black, "Flag the file for analysis in a <b>laboratory</b>", () => { sample.analyze = !sample.analyze; }); p.AddIcon(Icons.toggle_red, "Dump the sample", () => Lib.Popup ( "Warning!", Lib.BuildString("Do you really want to dump ", exp.fullname, "?"), new DialogGUIButton("Dump it", () => drive.samples.Remove(filename)), new DialogGUIButton("Keep it", () => { }) )); }
public override void OnStart(StartState state) { // don't break tutorial scenarios if (Lib.DisableScenario(this)) { return; } if (drive == null) { if (Lib.IsEditor()) { drive = new Drive(title, dataCapacity, sampleCapacity); } else { drive = DB.Vessel(vessel).DriveForPart(title, part, dataCapacity, sampleCapacity); } } UpdateCapacity(); }
void VesselDestroyed(Vessel v) { // rescan the damn kerbals // - vessel crew is empty at destruction time // - we can't even use the flightglobal roster, because sometimes it isn't updated yet at this point HashSet <string> kerbals_alive = new HashSet <string>(); HashSet <string> kerbals_dead = new HashSet <string>(); foreach (Vessel ov in FlightGlobals.Vessels) { foreach (ProtoCrewMember c in Lib.CrewList(ov)) { kerbals_alive.Add(c.name); } } foreach (string key in DB.Kerbals().Keys) { if (!kerbals_alive.Contains(key)) { kerbals_dead.Add(key); } } foreach (string n in kerbals_dead) { // we don't know if the kerbal really is dead, or if it is just not currently assigned to a mission DB.KillKerbal(n, false); } // purge the caches ResourceCache.Purge(v); // works with loaded and unloaded vessels Cache.PurgeVesselCaches(v); // works with loaded and unloaded vessels // delete data on unloaded vessels only (this is handled trough OnPartWillDie for loaded vessels) if (!v.loaded) { Drive.DeleteDrivesData(v); } }
internal static double RecordData(ScienceData data, MetaData meta) { double remaining = data.dataAmount; foreach (var drive in Drive.GetDrives(meta.vessel.KerbalismData(), false)) { var size = Math.Min(remaining, drive.FileCapacityAvailable()); if (size > 0) { drive.Record_file(meta.subjectData, size, true, true); remaining -= size; } } if (remaining > 0) { Message.Post( Lib.Color(Lib.BuildString(meta.subjectData.FullTitle, " stored partially"), Lib.Kolor.Orange), "Not enough space on hard drive" ); } return(remaining); }
// transfer data between two vessels public static void Transfer(Vessel src, Vessel dst) { // get drives Drive a = DB.Vessel(src).drive; Drive b = DB.Vessel(dst).drive; // get size of data being transfered double amount = a.Size(); // if there is data if (amount > double.Epsilon) { // transfer the data a.Move(b); // inform the user Message.Post ( Lib.BuildString(Lib.HumanReadableDataSize(amount), " ", Localizer.Format("#KERBALISM_Science_ofdatatransfer")), Lib.BuildString(Localizer.Format("#KERBALISM_Generic_FROM"), " <b>", src.vesselName, "</b> ", Localizer.Format("#KERBALISM_Generic_TO"), " <b>", dst.vesselName, "</b>") ); } }
// get sample to analyze, return null if there isn't a sample static string next_sample(Vessel v) { // get vessel drive Drive drive = DB.Vessel(v).drive; // for each sample foreach (var pair in drive.samples) { // shortcuts string filename = pair.Key; Sample sample = pair.Value; // if flagged for analysis if (sample.analyze) { // we found it return(filename); } } // there was no sample to analyze return(string.Empty); }
public VesselData() { msg_signal = false; msg_belt = false; cfg_ec = PreferencesMessages.Instance.ec; cfg_supply = PreferencesMessages.Instance.supply; cfg_signal = PreferencesMessages.Instance.signal; cfg_malfunction = PreferencesMessages.Instance.malfunction; cfg_storm = PreferencesMessages.Instance.storm; cfg_script = PreferencesMessages.Instance.script; cfg_highlights = PreferencesBasic.Instance.highlights; cfg_showlink = true; hyspos_signal = 0.0; hysneg_signal = 5.0; storm_time = 0.0; storm_age = 0.0; storm_state = 0; group = "NONE"; computer = new Computer(); drive = new Drive(); supplies = new Dictionary <string, SupplyData>(); scansat_id = new List <uint>(); }
// transfer data between two vessels public static void transfer(Vessel src, Vessel dst) { // get drives Drive a = DB.Vessel(src).drive; Drive b = DB.Vessel(dst).drive; // get size of data being transfered double amount = a.size(); // if there is data if (amount > double.Epsilon) { // transfer the data a.move(b); // inform the user Message.Post ( Lib.BuildString(Lib.HumanReadableDataSize(amount), " of data transfered"), Lib.BuildString("from <b>", src.vesselName, "</b> to <b>", dst.vesselName, "</b>") ); } }
void VesselModified(Vessel vessel_a) { // do nothing in the editor if (Lib.IsEditor()) return; // bah if (string.IsNullOrEmpty(vessel_a.vesselName)) return; // get drive from first vessel // - there is a possibility this will create it // - we avoid adding a db entry for invalid vessels Drive drive_a = Cache.VesselInfo(vessel_a).is_valid ? DB.Vessel(vessel_a).drive : new Drive(); // for each loaded vessel foreach (Vessel vessel_b in FlightGlobals.VesselsLoaded) { // do not check against itself if (vessel_a.id == vessel_b.id) continue; // get drive of the other vessel // - there is a possibility this will create it // - we avoid adding a db entry for invalid vessels Drive drive_b = Cache.VesselInfo(vessel_b).is_valid ? DB.Vessel(vessel_b).drive : new Drive(); // if location of A is now in B, or viceversa // - this support the case when one or both the drives locations are 0 if (vessel_a.parts.Find(k => k.flightID == drive_b.location) != null || vessel_b.parts.Find(k => k.flightID == drive_a.location) != null) { // swap the drives Lib.Swap(ref DB.Vessel(vessel_a).drive, ref DB.Vessel(vessel_b).drive); // done, no need to go through the rest of the loaded vessels break; } } }
public Drive BestDrive(double minDataCapacity = 0, int minSlots = 0) { Drive result = null; foreach (var drive in drives.Values) { if (result == null) { result = drive; continue; } if (minDataCapacity > double.Epsilon && drive.FileCapacityAvailable() < minDataCapacity) { continue; } if (minSlots > 0 && drive.SampleCapacityAvailable() < minSlots) { continue; } if (minDataCapacity > double.Epsilon && drive.FileCapacityAvailable() > result.FileCapacityAvailable()) { result = drive; } if (minSlots > 0 && drive.SampleCapacityAvailable() > result.SampleCapacityAvailable()) { result = drive; } } if (result == null) { // vessel has no drive. return(new Drive("Broken", 0, 0)); } return(result); }
void VesselDestroyed(Vessel v) { DB.vessels.Remove(Lib.VesselID(v)); // rescan the damn kerbals // - vessel crew is empty at destruction time // - we can't even use the flightglobal roster, because sometimes it isn't updated yet at this point HashSet <string> kerbals_alive = new HashSet <string>(); HashSet <string> kerbals_dead = new HashSet <string>(); foreach (Vessel ov in FlightGlobals.Vessels) { foreach (ProtoCrewMember c in Lib.CrewList(ov)) { kerbals_alive.Add(c.name); } } foreach (KeyValuePair <string, KerbalData> p in DB.Kerbals()) { if (!kerbals_alive.Contains(p.Key)) { kerbals_dead.Add(p.Key); } } foreach (string n in kerbals_dead) { // we don't know if the kerbal really is dead, or if it is just not currently assigned to a mission DB.KillKerbal(n, false); } // purge the caches ResourceCache.Purge(v); Drive.Purge(v); Cache.PurgeObjects(v); }
/// <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); }
static void Render_File(Panel p, string filename, File file, Drive drive) { // get experiment info ExperimentInfo exp = Science.Experiment(filename); // render experiment name string exp_label = Lib.BuildString ( "<b>", Lib.Ellipsis(exp.name, 24), "</b> <size=10>", Lib.Ellipsis(exp.situation, 32u - (uint)Math.Min(24, exp.name.Length)), "</size>" ); string exp_tooltip = Lib.BuildString ( exp.name, "\n", "<color=#aaaaaa>", exp.situation, "</color>" ); double exp_value = Science.Value(filename, file.size); if (exp_value > double.Epsilon) { exp_tooltip = Lib.BuildString(exp_tooltip, "\n<b>", Lib.HumanReadableScience(exp_value), "</b>"); } p.SetContent(exp_label, Lib.HumanReadableDataSize(file.size), exp_tooltip); p.SetIcon(file.send ? Icons.send_cyan : Icons.send_black, "Flag the file for transmission to <b>DSN</b>", () => { file.send = !file.send; }); p.SetIcon(Icons.toggle_red, "Delete the file", () => Lib.Popup ( "Warning!", Lib.BuildString("Do you really want to delete ", exp.fullname, "?"), new DialogGUIButton("Delete it", () => drive.files.Remove(filename)), new DialogGUIButton("Keep it", () => {}) )); }
private static string TestForIssues(Vessel v, Resource_info ec, Experiment experiment, uint hdId, bool broken, double remainingSampleMass, bool didPrepare, bool isShrouded, string last_subject_id) { var subject_id = Science.Generate_subject_id(experiment.experiment_id, v); if (broken) { return("broken"); } if (isShrouded && !experiment.allow_shrouded) { return("shrouded"); } bool needsReset = experiment.crew_reset.Length > 0 && !string.IsNullOrEmpty(last_subject_id) && subject_id != last_subject_id; if (needsReset) { return("reset required"); } if (ec.amount < double.Epsilon && experiment.ec_rate > double.Epsilon) { return("no Electricity"); } if (!string.IsNullOrEmpty(experiment.crew_operate)) { var cs = new CrewSpecs(experiment.crew_operate); if (!cs && Lib.CrewCount(v) > 0) { return("crew on board"); } else if (cs && !cs.Check(v)) { return(cs.Warning()); } } if (!experiment.sample_collecting && remainingSampleMass < double.Epsilon && experiment.sample_mass > double.Epsilon) { return("depleted"); } if (!didPrepare && !string.IsNullOrEmpty(experiment.crew_prepare)) { return("not prepared"); } string situationIssue = Science.TestRequirements(experiment.experiment_id, experiment.requires, v); if (situationIssue.Length > 0) { return(Science.RequirementText(situationIssue)); } var experimentSize = Science.Experiment(subject_id).max_amount; double chunkSize = Math.Min(experiment.data_rate * Kerbalism.elapsed_s, experimentSize); Drive drive = GetDrive(experiment, v, hdId, chunkSize, subject_id); var isFile = experiment.sample_mass < double.Epsilon; double available = 0; if (isFile) { available = drive.FileCapacityAvailable(); available += Cache.WarpCache(v).FileCapacityAvailable(); } else { available = drive.SampleCapacityAvailable(subject_id); } if (Math.Min(experiment.data_rate * Kerbalism.elapsed_s, experimentSize) > available) { return(insufficient_storage); } return(string.Empty); }
private static bool DoRecord(Experiment experiment, string subject_id, Vessel vessel, Resource_info ec, uint hdId, Vessel_resources resources, List <KeyValuePair <string, double> > resourceDefs, double remainingSampleMass, double dataSampled, out double sampledOut, out double remainingSampleMassOut) { // default output values for early returns sampledOut = dataSampled; remainingSampleMassOut = remainingSampleMass; var exp = Science.Experiment(subject_id); if (Done(exp, dataSampled)) { return(true); } double elapsed = Kerbalism.elapsed_s; double chunkSize = Math.Min(experiment.data_rate * elapsed, exp.max_amount); double massDelta = experiment.sample_mass * chunkSize / exp.max_amount; Drive drive = GetDrive(experiment, vessel, hdId, chunkSize, subject_id); // on high time warp this chunk size could be too big, but we could store a sizable amount if we process less bool isFile = experiment.sample_mass < float.Epsilon; double maxCapacity = isFile ? drive.FileCapacityAvailable() : drive.SampleCapacityAvailable(subject_id); Drive warpCacheDrive = null; if (isFile) { if (drive.GetFileSend(subject_id)) { warpCacheDrive = Cache.WarpCache(vessel); } if (warpCacheDrive != null) { maxCapacity += warpCacheDrive.FileCapacityAvailable(); } } double factor = Rate(vessel, chunkSize, maxCapacity, elapsed, ec, experiment.ec_rate, resources, resourceDefs); if (factor < double.Epsilon) { return(false); } chunkSize *= factor; massDelta *= factor; elapsed *= factor; bool stored = false; if (chunkSize > double.Epsilon) { if (isFile) { if (warpCacheDrive != null) { double s = Math.Min(chunkSize, warpCacheDrive.FileCapacityAvailable()); stored = warpCacheDrive.Record_file(subject_id, s, true); if (chunkSize > s) // only write to persisted drive if the data cannot be transmitted in this tick { stored &= drive.Record_file(subject_id, chunkSize - s, true); } } else { stored = drive.Record_file(subject_id, chunkSize, true); } } else { stored = drive.Record_sample(subject_id, chunkSize, massDelta); } } if (!stored) { return(false); } // consume resources ec.Consume(experiment.ec_rate * elapsed, "experiment"); foreach (var p in resourceDefs) { resources.Consume(vessel, p.Key, p.Value * elapsed, "experiment"); } dataSampled += chunkSize; dataSampled = Math.Min(dataSampled, exp.max_amount); sampledOut = dataSampled; if (!experiment.sample_collecting) { remainingSampleMass -= massDelta; remainingSampleMass = Math.Max(remainingSampleMass, 0); } remainingSampleMassOut = remainingSampleMass; return(true); }
/// <summary> /// If short_strings parameter is true then the strings used for display of the data will be shorter when inflight. /// </summary> public static void Fileman(this Panel p, Vessel v, bool short_strings = false) { // avoid corner-case when this is called in a lambda after scene changes v = FlightGlobals.FindVessel(v.id); // if vessel doesn't exist anymore, leave the panel empty if (v == null) { return; } // get info from the cache Vessel_info vi = Cache.VesselInfo(v); // if not a valid vessel, leave the panel empty if (!vi.is_valid) { return; } // set metadata p.Title(Lib.BuildString(Lib.Ellipsis(v.vesselName, Styles.ScaleStringLength(40)), " <color=#cccccc>FILE MANAGER</color>")); p.Width(Styles.ScaleWidthFloat(465.0f)); p.paneltype = Panel.PanelType.data; // time-out simulation if (!Lib.IsControlUnit(v) && p.Timeout(vi)) { return; } var drives = Drive.GetDriveParts(v); int filesCount = 0; double usedDataCapacity = 0; double totalDataCapacity = 0; int samplesCount = 0; int usedSlots = 0; int totalSlots = 0; double totalMass = 0; bool unlimitedData = false; bool unlimitedSamples = false; foreach (var idDrivePair in drives) { var drive = idDrivePair.Value; if (!drive.is_private) { usedDataCapacity += drive.FilesSize(); totalDataCapacity += drive.dataCapacity; unlimitedData |= drive.dataCapacity < 0; unlimitedSamples |= drive.sampleCapacity < 0; usedSlots += drive.SamplesSize(); totalSlots += drive.sampleCapacity; } filesCount += drive.files.Count; samplesCount += drive.samples.Count; foreach (var sample in drive.samples.Values) { totalMass += sample.mass; } } if (filesCount > 0 || totalDataCapacity > 0) { var title = "DATA " + Lib.HumanReadableDataSize(usedDataCapacity); if (!unlimitedData) { title += Lib.BuildString(" (", Lib.HumanReadablePerc((totalDataCapacity - usedDataCapacity) / totalDataCapacity), " available)"); } p.AddSection(title); foreach (var idDrivePair in drives) { uint partId = idDrivePair.Key; var drive = idDrivePair.Value; foreach (var pair in drive.files) { string filename = pair.Key; File file = pair.Value; Render_file(p, partId, filename, file, drive, short_strings && Lib.IsFlight(), v); } } if (filesCount == 0) { p.AddContent("<i>no files</i>", string.Empty); } } if (samplesCount > 0 || totalSlots > 0) { var title = "SAMPLES " + Lib.HumanReadableMass(totalMass) + " " + Lib.HumanReadableSampleSize(usedSlots); if (totalSlots > 0 && !unlimitedSamples) { title += ", " + Lib.HumanReadableSampleSize(totalSlots) + " available"; } p.AddSection(title); foreach (var idDrivePair in drives) { uint partId = idDrivePair.Key; var drive = idDrivePair.Value; foreach (var pair in drive.samples) { string samplename = pair.Key; Sample sample = pair.Value; Render_sample(p, partId, samplename, sample, drive, short_strings && Lib.IsFlight()); } } if (samplesCount == 0) { p.AddContent("<i>no samples</i>", string.Empty); } } }
void VesselRecoveryProcessing(ProtoVessel v, MissionRecoveryDialog dialog, float score) { // note: // this function accumulate science stored in drives on recovery, // and visualize the data in the recovery dialog window // do nothing if science system is disabled, or in sandbox mode if (!Features.Science || HighLogic.CurrentGame.Mode == Game.Modes.SANDBOX) { return; } var vesselID = Lib.VesselID(v); // get the drive data from DB if (!DB.vessels.ContainsKey(vesselID)) { return; } foreach (Drive drive in Drive.GetDrives(v)) { // for each file in the drive foreach (KeyValuePair <string, File> p in drive.files) { // shortcuts string filename = p.Key; File file = p.Value; // de-buffer partially transmitted data file.size += file.buff; file.buff = 0.0; // get subject ScienceSubject subject = ResearchAndDevelopment.GetSubjectByID(filename); // credit science float credits = Science.Credit(filename, file.size, false, v); // create science widged ScienceSubjectWidget widged = ScienceSubjectWidget.Create ( subject, // subject (float)file.size, // data gathered credits, // science points dialog // recovery dialog ); // add widget to dialog dialog.AddDataWidget(widged); // add science credits to total dialog.scienceEarned += (float)credits; } // for each sample in the drive // for each file in the drive foreach (KeyValuePair <string, Sample> p in drive.samples) { // shortcuts string filename = p.Key; Sample sample = p.Value; // get subject ScienceSubject subject = ResearchAndDevelopment.GetSubjectByID(filename); // credit science float credits = Science.Credit(filename, sample.size, false, v); // create science widged ScienceSubjectWidget widged = ScienceSubjectWidget.Create ( subject, // subject (float)sample.size, // data gathered credits, // science points dialog // recovery dialog ); // add widget to dialog dialog.AddDataWidget(widged); // add science credits to total dialog.scienceEarned += (float)credits; } } }
// consume EC for transmission, and transmit science data public static void Update(Vessel v, VesselData vd, ResourceInfo ec, double elapsed_s) { // do nothing if science system is disabled if (!Features.Science) { return; } // consume ec for transmitters ec.Consume(vd.Connection.ec_idle * elapsed_s, ResourceBroker.CommsIdle); // avoid corner-case when RnD isn't live during scene changes // - this avoid losing science if the buffer reach threshold during a scene change if (HighLogic.CurrentGame.Mode != Game.Modes.SANDBOX && ResearchAndDevelopment.Instance == null) { return; } // clear list of files transmitted vd.filesTransmitted.Clear(); // check connection if (vd.Connection == null || !vd.Connection.linked || vd.Connection.rate <= 0.0 || !vd.deviceTransmit || ec.Amount < vd.Connection.ec_idle * elapsed_s) { // reset all files transmit rate foreach (Drive drive in Drive.GetDrives(vd, true)) { foreach (File f in drive.files.Values) { f.transmitRate = 0.0; } } // do nothing else return; } double totalTransmitCapacity = vd.Connection.rate * elapsed_s; double remainingTransmitCapacity = totalTransmitCapacity; GetFilesToTransmit(v, vd); if (xmitFiles.Count == 0) { return; } UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.Science.Update-Loop"); // traverse the list in reverse because : // - warp cache files are at the end, and they are always transmitted regerdless of transmit capacity // - others files are next, sorted in science value per MB ascending order for (int i = xmitFiles.Count - 1; i >= 0; i--) { XmitFile xmitFile = xmitFiles[i]; if (xmitFile.file.size == 0.0) { continue; } // always transmit everything in the warp cache if (!xmitFile.isInWarpCache && remainingTransmitCapacity <= 0.0) { break; } // determine how much data is transmitted double transmitted = xmitFile.isInWarpCache ? xmitFile.file.size : Math.Min(xmitFile.file.size, remainingTransmitCapacity); if (transmitted == 0.0) { continue; } // consume transmit capacity remainingTransmitCapacity -= transmitted; // get science value double xmitScienceValue = transmitted * xmitFile.sciencePerMB; // consume data in the file xmitFile.file.size -= transmitted; // remove science collected (ignoring final science value clamped to subject completion) xmitFile.file.subjectData.RemoveScienceCollectedInFlight(xmitScienceValue); // save transmit rate for the file, and add it to the VesselData list of files being transmitted if (xmitFile.isInWarpCache && xmitFile.realDriveFile != null) { xmitFile.realDriveFile.transmitRate = transmitted / elapsed_s; vd.filesTransmitted.Add(xmitFile.realDriveFile); } else { xmitFile.file.transmitRate = transmitted / elapsed_s; vd.filesTransmitted.Add(xmitFile.file); } if (xmitScienceValue > 0.0) { // add science to the subject (and eventually included subjects), trigger completion events, credit the science, return how much has been credited. vd.scienceTransmitted += xmitFile.file.subjectData.RetrieveScience(xmitScienceValue, true, v.protoVessel, xmitFile.file); } } UnityEngine.Profiling.Profiler.EndSample(); // consume EC cost for transmission (ec_idle is consumed above) double transmittedCapacity = totalTransmitCapacity - remainingTransmitCapacity; double transmissionCost = (vd.Connection.ec - vd.Connection.ec_idle) * (transmittedCapacity / (vd.Connection.rate * elapsed_s)); ec.Consume(transmissionCost * elapsed_s, ResourceBroker.CommsXmit); }
private static void GetFilesToTransmit(Vessel v, VesselData vd) { UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.Science.GetFilesToTransmit"); Drive warpCache = Cache.WarpCache(v); xmitFiles.Clear(); List <SubjectData> filesToRemove = new List <SubjectData>(); foreach (Drive drive in Drive.GetDrives(vd, true)) { foreach (File f in drive.files.Values) { // always reset transmit rate f.transmitRate = 0.0; // delete empty files that aren't being transmitted // note : this won't work in case the same subject is split over multiple files (on different drives) if (f.size <= 0.0 && (!warpCache.files.ContainsKey(f.subjectData) || warpCache.files[f.subjectData].size <= 0.0)) { filesToRemove.Add(f.subjectData); continue; } // get files tagged for transmit if (drive.GetFileSend(f.subjectData.Id)) { xmitFiles.Add(new XmitFile(f, drive, f.subjectData.SciencePerMB, false)); } } // delete found empty files from the drive foreach (SubjectData fileToRemove in filesToRemove) { drive.files.Remove(fileToRemove); } filesToRemove.Clear(); } // sort files by science value per MB ascending order so high value files are transmitted first // because XmitFile list is processed from end to start UnityEngine.Profiling.Profiler.BeginSample("Kerbalism.Science.GetFilesToTransmit-Sort"); xmitFiles.Sort((x, y) => x.sciencePerMB.CompareTo(y.sciencePerMB)); UnityEngine.Profiling.Profiler.EndSample(); // add all warpcache files to the beginning of the XmitFile list foreach (File f in warpCache.files.Values) { // don't transmit empty files if (f.size <= 0.0) { continue; } // find the file on a "real" drive that correspond to this warpcache file // this allow to use the real file for displaying transmit info and saving state (filemanager, monitor, vesseldata...) int driveFileIndex = xmitFiles.FindIndex(df => df.file.subjectData == f.subjectData); if (driveFileIndex >= 0) { xmitFiles.Add(new XmitFile(f, warpCache, f.subjectData.SciencePerMB, true, xmitFiles[driveFileIndex].file)); } else { xmitFiles.Add(new XmitFile(f, warpCache, f.subjectData.SciencePerMB, true)); // should not be happening, but better safe than sorry } } UnityEngine.Profiling.Profiler.EndSample(); }
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; var vd = DB.Vessel(vessel); if (IsScanning) { Science.Generate_subject(experimentType, vessel); var subject_id = Science.Generate_subject_id(experimentType, vessel); var exp = Science.Experiment(subject_id); double size = exp.max_amount * coverage_delta / 100.0; // coverage is 0-100% size += warp_buffer; size = Drive.StoreFile(vessel, subject_id, 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("red", "Scanner halted", true), "Scanner halted on <b>" + vessel.vesselName + "</b>. No storage left on vessel."); } } } else if (vd.scansat_id.Contains(part.flightID)) { var vi = Cache.VesselInfo(vessel); if (vi.free_capacity / vi.total_capacity > 0.9) // restart when 90% of capacity is available { StartScan(); vd.scansat_id.Remove(part.flightID); if (vd.cfg_ec) { Message.Post(Lib.BuildString("SCANsat sensor resumed operations on <b>", vessel.vesselName, "</b>")); } } } } }
// consume EC for transmission, and transmit science data public static void Update(Vessel v, Vessel_info vi, VesselData vd, Vessel_resources resources, double elapsed_s) { // do nothing if science system is disabled if (!Features.Science) { return; } // avoid corner-case when RnD isn't live during scene changes // - this avoid losing science if the buffer reach threshold during a scene change if (HighLogic.CurrentGame.Mode != Game.Modes.SANDBOX && ResearchAndDevelopment.Instance == null) { return; } // get connection info ConnectionInfo conn = vi.connection; if (conn == null) { return; } if (String.IsNullOrEmpty(vi.transmitting)) { return; } Drive warpCache = Cache.WarpCache(v); bool isWarpCache = false; double transmitSize = conn.rate * elapsed_s; while (warpCache.files.Count > 0 || // transmit EVERYTHING in the cache, regardless of transmitSize. (transmitSize > double.Epsilon && !String.IsNullOrEmpty(vi.transmitting))) { // get filename of data being downloaded var exp_filename = vi.transmitting; if (string.IsNullOrEmpty(exp_filename)) { break; } Drive drive = null; if (warpCache.files.ContainsKey(exp_filename)) { drive = warpCache; isWarpCache = true; } else { drive = FindDrive(v, exp_filename); isWarpCache = false; } if (drive == null) { break; } File file = drive.files[exp_filename]; if (isWarpCache) { file.buff = file.size; file.size = 0; transmitSize -= file.size; } else { if (transmitSize < double.Epsilon) { break; } // determine how much data is transmitted double transmitted = Math.Min(file.size, transmitSize); transmitSize -= transmitted; // consume data in the file file.size -= transmitted; // accumulate in the buffer file.buff += transmitted; } // special case: file size on drive = 0 -> buffer is 0, so no need to do anyhting. just delete. if (file.buff > double.Epsilon) { // this is the science value remaining for this experiment var remainingValue = Value(exp_filename, 0); // this is the science value of this sample double dataValue = Value(exp_filename, file.buff); bool doCredit = file.size <= double.Epsilon || dataValue > buffer_science_value;; // if buffer science value is high enough or file was transmitted completely if (doCredit) { var totalValue = TotalValue(exp_filename); // collect the science data Credit(exp_filename, file.buff, true, v.protoVessel); // reset the buffer file.buff = 0.0; // this was the last useful bit, there is no more value in the experiment if (remainingValue >= 0.1 && remainingValue - dataValue < 0.1) { Message.Post( Lib.BuildString(Lib.HumanReadableScience(totalValue), " ", Experiment(exp_filename).FullName(exp_filename), " completed"), Lib.TextVariant( "Our researchers will jump on it right now", "This cause some excitement", "These results are causing a brouhaha in R&D", "Our scientists look very confused", "The scientists won't believe these readings" )); } } } // if file was transmitted completely if (file.size <= double.Epsilon) { // remove the file drive.files.Remove(exp_filename); vi.transmitting = Science.Transmitting(v, true); } } }
public static void BackgroundUpdate(Vessel vessel, ProtoPartSnapshot p, ProtoPartModuleSnapshot m, KerbalismScansat kerbalismScansat, Part part_prefab, VesselData vd, Resource_info ec, double elapsed_s) { List <ProtoPartModuleSnapshot> scanners = Cache.VesselObjectsCache <List <ProtoPartModuleSnapshot> >(vessel, "scansat_" + p.flightID); if (scanners == null) { scanners = Lib.FindModules(p, "SCANsat"); if (scanners.Count == 0) { scanners = Lib.FindModules(p, "ModuleSCANresourceScanner"); } Cache.SetVesselObjectsCache(vessel, "scansat_" + p.flightID, scanners); } if (scanners.Count == 0) { return; } var scanner = scanners[0]; bool is_scanning = Lib.Proto.GetBool(scanner, "scanning"); if (is_scanning && kerbalismScansat.ec_rate > double.Epsilon) { ec.Consume(kerbalismScansat.ec_rate * elapsed_s, "scanner"); } if (!Features.Science) { if (is_scanning && ec.amount < double.Epsilon) { SCANsat.StopScanner(vessel, scanner, part_prefab); is_scanning = false; // remember disabled scanner vd.scansat_id.Add(p.flightID); // give the user some feedback if (vd.cfg_ec) { Message.Post(Lib.BuildString("SCANsat sensor was disabled on <b>", vessel.vesselName, "</b>")); } } else if (vd.scansat_id.Contains(p.flightID)) { // if there is enough ec // note: comparing against amount in previous simulation step // re-enable at 25% EC if (ec.level > 0.25) { // re-enable the scanner SCANsat.ResumeScanner(vessel, m, part_prefab); is_scanning = true; // give the user some feedback if (vd.cfg_ec) { Message.Post(Lib.BuildString("SCANsat sensor resumed operations on <b>", vessel.vesselName, "</b>")); } } } // forget active scanners if (is_scanning) { vd.scansat_id.Remove(p.flightID); } return; } // if(!Feature.Science) string body_name = Lib.Proto.GetString(m, "body_name"); int sensorType = (int)Lib.Proto.GetUInt(m, "sensorType"); double body_coverage = Lib.Proto.GetDouble(m, "body_coverage"); double warp_buffer = Lib.Proto.GetDouble(m, "warp_buffer"); double new_coverage = SCANsat.Coverage(sensorType, vessel.mainBody); if (body_name == vessel.mainBody.name && new_coverage < body_coverage) { // SCANsat sometimes reports a coverage of 0, which is wrong new_coverage = body_coverage; } if (vessel.mainBody.name != body_name) { body_name = vessel.mainBody.name; body_coverage = new_coverage; } else { double coverage_delta = new_coverage - body_coverage; body_coverage = new_coverage; if (is_scanning) { Science.Generate_subject(kerbalismScansat.experimentType, vessel); var subject_id = Science.Generate_subject_id(kerbalismScansat.experimentType, vessel); var exp = Science.Experiment(subject_id); double size = exp.max_amount * coverage_delta / 100.0; // coverage is 0-100% size += warp_buffer; if (size > double.Epsilon) { // store what we can foreach (var d in Drive.GetDrives(vessel)) { var available = d.FileCapacityAvailable(); var chunk = Math.Min(size, available); if (!d.Record_file(subject_id, chunk, true)) { break; } size -= chunk; if (size < double.Epsilon) { break; } } } if (size > double.Epsilon) { // we filled all drives up to the brim but were unable to store everything if (warp_buffer < double.Epsilon) { // warp buffer is empty, so lets store the rest there warp_buffer = size; size = 0; } else { // warp buffer not empty. that's ok if we didn't get new data if (coverage_delta < double.Epsilon) { size = 0; } // else we're scanning too fast. stop. } } // we filled all drives up to the brim but were unable to store everything // cancel scanning and annoy the user if (size > double.Epsilon || ec.amount < double.Epsilon) { warp_buffer = 0; SCANsat.StopScanner(vessel, scanner, part_prefab); vd.scansat_id.Add(p.flightID); if (vd.cfg_ec) { Message.Post(Lib.BuildString("SCANsat sensor was disabled on <b>", vessel.vesselName, "</b>")); } } } else if (vd.scansat_id.Contains(p.flightID)) { var vi = Cache.VesselInfo(vessel); if (ec.level >= 0.25 && (vi.free_capacity / vi.total_capacity > 0.9)) { SCANsat.ResumeScanner(vessel, scanner, part_prefab); vd.scansat_id.Remove(p.flightID); if (vd.cfg_ec) { Message.Post(Lib.BuildString("SCANsat sensor resumed operations on <b>", vessel.vesselName, "</b>")); } } } } Lib.Proto.Set(m, "warp_buffer", warp_buffer); Lib.Proto.Set(m, "body_coverage", body_coverage); Lib.Proto.Set(m, "body_name", body_name); }