using Observatory.Framework; using Observatory.Framework.Files; using Observatory.Framework.Files.Journal; using Observatory.Framework.Interfaces; using Observatory.Framework.Files.ParameterTypes; using System.Collections.Generic; using System.Linq; using System; using System.Collections.ObjectModel; namespace Observatory.Botanist { public class Botanist : IObservatoryWorker { private IObservatoryCore Core; private bool OdysseyLoaded = false; private Dictionary < ( ulong systemAddress, int bodyID ), ( string bodyName, int bioTotal, List speciesFound, List speciesAnalysed ) > BioPlanets; ObservableCollection GridCollection; private PluginUI pluginUI; private Guid? samplerStatusNotification = null; private BotanistSettings botanistSettings = new() { OverlayEnabled = true, }; public string Name => "Observatory Botanist"; public string ShortName => "Botanist"; public string Version => typeof(Botanist).Assembly.GetName().Version.ToString(); public PluginUI PluginUI => pluginUI; public object Settings { get => botanistSettings; set { botanistSettings = (BotanistSettings)value; } } public void JournalEvent(TJournal journal) where TJournal : JournalBase { switch (journal) { case LoadGame loadGame: OdysseyLoaded = loadGame.Odyssey; break; case SAASignalsFound signalsFound: { var systemBodyId = (signalsFound.SystemAddress, signalsFound.BodyID); if (OdysseyLoaded && !BioPlanets.ContainsKey(systemBodyId)) { var bioSignals = from signal in signalsFound.Signals where signal.Type == "$SAA_SignalType_Biological;" select signal; if (bioSignals.Any()) { if (!BioPlanets.ContainsKey(systemBodyId)) { BioPlanets.Add( systemBodyId, (signalsFound.BodyName, bioSignals.First().Count, new List(), new List()) ); } else { var bioPlanet = BioPlanets[systemBodyId]; bioPlanet.bodyName = signalsFound.BodyName; bioPlanet.bioTotal = bioSignals.First().Count; } } } } break; case ScanOrganic scanOrganic: { var systemBodyId = (scanOrganic.SystemAddress, scanOrganic.Body); if (!BioPlanets.ContainsKey(systemBodyId)) { // Unlikely to ever end up in here, but just in case create a new planet entry. List genus = new(); List species = new(); genus.Add(scanOrganic.Genus_Localised); species.Add(scanOrganic.Species_Localised); var bioPlanet = (string.Empty, 0, genus, species); BioPlanets.Add(systemBodyId, bioPlanet); } else { var bioPlanet = BioPlanets[systemBodyId]; switch (scanOrganic.ScanType) { case ScanOrganicType.Log: case ScanOrganicType.Sample: if (!Core.IsLogMonitorBatchReading && botanistSettings.OverlayEnabled) { NotificationArgs args = new() { Title = scanOrganic.Species_Localised, Detail = string.Format("Sample {0} of 3", scanOrganic.ScanType == ScanOrganicType.Log ? 1 : 2), Rendering = NotificationRendering.NativeVisual, Timeout = 0, }; if (samplerStatusNotification == null) { samplerStatusNotification = Core.SendNotification(args); } else { Core.UpdateNotification(samplerStatusNotification.Value, args); } } if (!bioPlanet.speciesFound.Contains(scanOrganic.Species_Localised)) { bioPlanet.speciesFound.Add(scanOrganic.Species_Localised); } break; case ScanOrganicType.Analyse: if (!bioPlanet.speciesAnalysed.Contains(scanOrganic.Species_Localised)) { bioPlanet.speciesAnalysed.Add(scanOrganic.Species_Localised); } MaybeCloseSamplerStatusNotification(); break; } } UpdateUIGrid(); } break; case LeaveBody: case FSDJump: case Shutdown: // These are all good reasons to kill any open notification. Note that SupercruiseEntry is NOT a // suitable reason to close the notification as the player hopping out only to double check the // DSS map for another location. Note that a game client crash will not close the status notification. MaybeCloseSamplerStatusNotification(); break; } } private void MaybeCloseSamplerStatusNotification() { if (samplerStatusNotification != null) { Core.CancelNotification(samplerStatusNotification.Value); samplerStatusNotification = null; } } public void Load(IObservatoryCore observatoryCore) { GridCollection = new(); BotanistGrid uiObject = new(); GridCollection.Add(uiObject); pluginUI = new PluginUI(GridCollection); BioPlanets = new(); Core = observatoryCore; } public void LogMonitorStateChanged(LogMonitorStateChangedEventArgs args) { if (LogMonitorStateChangedEventArgs.IsBatchRead(args.NewState)) { // Beginning a batch read. Clear grid. Core.ClearGrid(this, new BotanistGrid()); } else if (LogMonitorStateChangedEventArgs.IsBatchRead(args.PreviousState)) { // Batch read is complete. Show data. UpdateUIGrid(); } } private void UpdateUIGrid() { // Suppress repainting the entire contents of the grid on every ScanOrganic record we read. if (Core.IsLogMonitorBatchReading) return; BotanistGrid uiObject = new(); Core.ClearGrid(this, uiObject); foreach (var bioPlanet in BioPlanets.Values) { if (bioPlanet.speciesFound.Count == 0) { var planetRow = new BotanistGrid() { Body = bioPlanet.bodyName, BioTotal = bioPlanet.bioTotal.ToString(), Species = "(NO SAMPLES TAKEN)", Analysed = string.Empty }; Core.AddGridItem(this, planetRow); } for (int i = 0; i < bioPlanet.speciesFound.Count; i++) { var speciesRow = new BotanistGrid() { Body = i == 0 ? bioPlanet.bodyName : string.Empty, BioTotal = i == 0 ? bioPlanet.bioTotal.ToString() : string.Empty, Species = bioPlanet.speciesFound[i], Analysed = bioPlanet.speciesAnalysed.Contains(bioPlanet.speciesFound[i]) ? "✓" : "" }; Core.AddGridItem(this, speciesRow); } } } } public class BotanistGrid { public string Body { get; set; } public string BioTotal { get; set; } public string Species { get; set; } public string Analysed { get; set; } } }