mirror of
https://github.com/9ParsonsB/Pulsar.git
synced 2025-04-05 17:39:39 -04:00
Initial Commit
This commit is contained in:
parent
8e178cbb7b
commit
63ed43f4af
379
Botanist/Botanist.cs
Normal file
379
Botanist/Botanist.cs
Normal file
@ -0,0 +1,379 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reflection;
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Files.Journal;
|
||||
using Observatory.Framework.Files.Journal.Exploration;
|
||||
using Observatory.Framework.Files.Journal.Odyssey;
|
||||
using Observatory.Framework.Files.Journal.Other;
|
||||
using Observatory.Framework.Files.Journal.Startup;
|
||||
using Observatory.Framework.Files.Journal.Travel;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Botanist;
|
||||
|
||||
public class Botanist : IObservatoryWorker
|
||||
{
|
||||
private IObservatoryCore Core;
|
||||
private bool OdysseyLoaded;
|
||||
private Dictionary<BodyAddress, BioPlanetDetail> BioPlanets;
|
||||
|
||||
// To make this journal locale agnostic, use the genus identifier and map to English names used in notifications.
|
||||
// Note: Values here are also used in the lookup for colony distance, so we also use this to resolve misspellings and Frontier bugs.
|
||||
private readonly Dictionary<string, string> EnglishGenusByIdentifier = new()
|
||||
{
|
||||
{ "$Codex_Ent_Aleoids_Genus_Name;", "Aleoida" },
|
||||
{ "$Codex_Ent_Bacterial_Genus_Name;", "Bacterium" },
|
||||
{ "$Codex_Ent_Cactoid_Genus_Name;", "Cactoida" },
|
||||
{
|
||||
"$Codex_Ent_Clepeus_Genus_Name;;", "Clypeus"
|
||||
}, // Fun misspelling of the identifier discovered in the journals
|
||||
{ "$Codex_Ent_Clypeus_Genus_Name;", "Clypeus" },
|
||||
{ "$Codex_Ent_Conchas_Genus_Name;", "Concha" },
|
||||
{ "$Codex_Ent_Electricae_Genus_Name;", "Electricae" },
|
||||
{ "$Codex_Ent_Fonticulus_Genus_Name;", "Fonticulua" },
|
||||
{ "$Codex_Ent_Shrubs_Genus_Name;", "Frutexa" },
|
||||
{ "$Codex_Ent_Fumerolas_Genus_Name;", "Fumerola" },
|
||||
{ "$Codex_Ent_Fungoids_Genus_Name;", "Fungoida" },
|
||||
{ "$Codex_Ent_Osseus_Genus_Name;", "Osseus" },
|
||||
{ "$Codex_Ent_Recepta_Genus_Name;", "Recepta" },
|
||||
{ "$Codex_Ent_Stratum_Genus_Name;", "Stratum" },
|
||||
{ "$Codex_Ent_Tubus_Genus_Name;", "Tubus" },
|
||||
{ "$Codex_Ent_Tussocks_Genus_Name;", "Tussock" },
|
||||
{ "$Codex_Ent_Ground_Struct_Ice_Name;", "Crystalline Shards" },
|
||||
{ "$Codex_Ent_Brancae_Name;", "Brain Trees" },
|
||||
{
|
||||
"$Codex_Ent_Seed_Name;", "Brain Tree"
|
||||
}, // Misspelling? :shrug: 'Seed' also seems to refer to peduncle things.
|
||||
{ "$Codex_Ent_Sphere_Name;", "Anemone" },
|
||||
{ "$Codex_Ent_Tube_Name;", "Sinuous Tubers" },
|
||||
{ "$Codex_Ent_Vents_Name;", "Amphora Plant" },
|
||||
{ "$Codex_Ent_Cone_Name;", "Bark Mounds" },
|
||||
};
|
||||
|
||||
// Note: Some Horizons bios may be missing, but they'll get localized genus name and default colony distance
|
||||
private readonly Dictionary<string, int> ColonyDistancesByGenus = new()
|
||||
{
|
||||
{ "Aleoida", 150 },
|
||||
{ "Bacterium", 500 },
|
||||
{ "Cactoida", 300 },
|
||||
{ "Clypeus", 150 },
|
||||
{ "Concha", 150 },
|
||||
{ "Electricae", 1000 },
|
||||
{ "Fonticulua", 500 },
|
||||
{ "Frutexa", 150 },
|
||||
{ "Fumerola", 100 },
|
||||
{ "Fungoida", 300 },
|
||||
{ "Osseus", 800 },
|
||||
{ "Recepta", 150 },
|
||||
{ "Stratum", 500 },
|
||||
{ "Tubus", 800 },
|
||||
{ "Tussock", 200 },
|
||||
{ "Crystalline Shards", DEFAULT_COLONY_DISTANCE },
|
||||
{ "Brain Tree", DEFAULT_COLONY_DISTANCE },
|
||||
{ "Anemone", DEFAULT_COLONY_DISTANCE },
|
||||
{ "Sinuous Tubers", DEFAULT_COLONY_DISTANCE },
|
||||
{ "Amphora Plant", DEFAULT_COLONY_DISTANCE },
|
||||
{ "Bark Mounds", DEFAULT_COLONY_DISTANCE },
|
||||
};
|
||||
|
||||
private const int DEFAULT_COLONY_DISTANCE = 100;
|
||||
|
||||
ObservableCollection<object> GridCollection;
|
||||
private PluginUI pluginUI;
|
||||
private Guid? samplerStatusNotification;
|
||||
|
||||
private BotanistSettings botanistSettings = new()
|
||||
{
|
||||
OverlayEnabled = true,
|
||||
OverlayIsSticky = 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>(TJournal journal) where TJournal : JournalBase
|
||||
{
|
||||
switch (journal)
|
||||
{
|
||||
case LoadGame loadGame:
|
||||
OdysseyLoaded = loadGame.Odyssey;
|
||||
break;
|
||||
case SAASignalsFound signalsFound:
|
||||
{
|
||||
BodyAddress systemBodyId = new()
|
||||
{
|
||||
SystemAddress = signalsFound.SystemAddress,
|
||||
BodyID = 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())
|
||||
{
|
||||
BioPlanets.Add(
|
||||
systemBodyId,
|
||||
new()
|
||||
{
|
||||
BodyName = signalsFound.BodyName,
|
||||
BioTotal = bioSignals.First().Count,
|
||||
SpeciesFound = new()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ScanOrganic scanOrganic:
|
||||
{
|
||||
BodyAddress systemBodyId = new()
|
||||
{
|
||||
SystemAddress = scanOrganic.SystemAddress,
|
||||
BodyID = scanOrganic.Body
|
||||
};
|
||||
if (!BioPlanets.ContainsKey(systemBodyId))
|
||||
{
|
||||
// Unlikely to ever end up in here, but just in case create a new planet entry.
|
||||
Dictionary<string, BioSampleDetail> bioSampleDetails = new();
|
||||
bioSampleDetails.Add(scanOrganic.Species_Localised, new()
|
||||
{
|
||||
Genus = EnglishGenusByIdentifier.GetValueOrDefault(scanOrganic.Genus,
|
||||
scanOrganic.Genus_Localised),
|
||||
Analysed = false
|
||||
});
|
||||
|
||||
BioPlanets.Add(systemBodyId, new()
|
||||
{
|
||||
BodyName = string.Empty,
|
||||
BioTotal = 0,
|
||||
SpeciesFound = bioSampleDetails
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var bioPlanet = BioPlanets[systemBodyId];
|
||||
|
||||
switch (scanOrganic.ScanType)
|
||||
{
|
||||
case ScanOrganicType.Log:
|
||||
case ScanOrganicType.Sample:
|
||||
if (!Core.IsLogMonitorBatchReading && botanistSettings.OverlayEnabled)
|
||||
{
|
||||
var colonyDistance = GetColonyDistance(scanOrganic);
|
||||
var sampleNum = scanOrganic.ScanType == ScanOrganicType.Log ? 1 : 2;
|
||||
NotificationArgs args = new()
|
||||
{
|
||||
Title = scanOrganic.Species_Localised,
|
||||
Detail =
|
||||
$"Sample {sampleNum} of 3{Environment.NewLine}Colony distance: {colonyDistance} m",
|
||||
Rendering = NotificationRendering.NativeVisual,
|
||||
Timeout = (botanistSettings.OverlayIsSticky ? 0 : -1),
|
||||
Sender = ShortName,
|
||||
};
|
||||
if (samplerStatusNotification == null)
|
||||
{
|
||||
var notificationId = Core.SendNotification(args);
|
||||
if (botanistSettings.OverlayIsSticky)
|
||||
samplerStatusNotification = notificationId;
|
||||
}
|
||||
else
|
||||
{
|
||||
Core.UpdateNotification(samplerStatusNotification.Value, args);
|
||||
}
|
||||
}
|
||||
|
||||
if (!bioPlanet.SpeciesFound.ContainsKey(scanOrganic.Species_Localised))
|
||||
{
|
||||
bioPlanet.SpeciesFound.Add(scanOrganic.Species_Localised, new()
|
||||
{
|
||||
Genus = EnglishGenusByIdentifier.GetValueOrDefault(scanOrganic.Genus,
|
||||
scanOrganic.Genus_Localised),
|
||||
Analysed = false
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
case ScanOrganicType.Analyse:
|
||||
if (!bioPlanet.SpeciesFound[scanOrganic.Species_Localised].Analysed)
|
||||
{
|
||||
bioPlanet.SpeciesFound[scanOrganic.Species_Localised].Analysed = true;
|
||||
}
|
||||
|
||||
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 object GetColonyDistance(ScanOrganic scan)
|
||||
{
|
||||
// Map the Genus to a Genus name then lookup colony distance.
|
||||
return ColonyDistancesByGenus.GetValueOrDefault(
|
||||
EnglishGenusByIdentifier.GetValueOrDefault(scan.Genus, string.Empty), DEFAULT_COLONY_DISTANCE);
|
||||
}
|
||||
|
||||
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.SetGridItems(this,
|
||||
[
|
||||
typeof(BotanistGrid).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Select(p => p.Name)
|
||||
.ToDictionary(p => p, p => string.Empty)
|
||||
]);
|
||||
}
|
||||
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;
|
||||
|
||||
Core.SetGridItems(this, [
|
||||
typeof(BotanistGrid).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Select(p => p.Name)
|
||||
.ToDictionary(p => p, p => string.Empty)
|
||||
]);
|
||||
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,
|
||||
ColonyDistance = string.Empty,
|
||||
};
|
||||
Core.AddGridItem(this, planetRow);
|
||||
}
|
||||
|
||||
var firstRow = true;
|
||||
foreach (var entry in bioPlanet.SpeciesFound)
|
||||
{
|
||||
var colonyDistance =
|
||||
ColonyDistancesByGenus.GetValueOrDefault(entry.Value.Genus ?? "", DEFAULT_COLONY_DISTANCE);
|
||||
var speciesRow = new BotanistGrid
|
||||
{
|
||||
Body = firstRow ? bioPlanet.BodyName : string.Empty,
|
||||
BioTotal = firstRow ? bioPlanet.BioTotal.ToString() : string.Empty,
|
||||
Species = entry.Key,
|
||||
Analysed = entry.Value.Analysed ? "✓" : "",
|
||||
ColonyDistance = $"{colonyDistance}m",
|
||||
};
|
||||
Core.AddGridItem(this, speciesRow);
|
||||
firstRow = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BodyAddress
|
||||
{
|
||||
public ulong SystemAddress { get; set; }
|
||||
public int BodyID { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
// We want value equality here.
|
||||
|
||||
//
|
||||
// See the full list of guidelines at
|
||||
// http://go.microsoft.com/fwlink/?LinkID=85237
|
||||
// and also the guidance for operator== at
|
||||
// http://go.microsoft.com/fwlink/?LinkId=85238
|
||||
//
|
||||
|
||||
if (obj == null || GetType() != obj.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var other = (BodyAddress)obj;
|
||||
return other.SystemAddress == SystemAddress
|
||||
&& other.BodyID == BodyID;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(SystemAddress, BodyID);
|
||||
}
|
||||
}
|
||||
|
||||
class BioPlanetDetail
|
||||
{
|
||||
public string BodyName { get; set; }
|
||||
public int BioTotal { get; set; }
|
||||
public Dictionary<string, BioSampleDetail> SpeciesFound { get; set; }
|
||||
}
|
||||
|
||||
class BioSampleDetail
|
||||
{
|
||||
public string Genus { get; set; }
|
||||
public bool Analysed { get; set; }
|
||||
}
|
||||
|
||||
public class BotanistGrid
|
||||
{
|
||||
public string Body { get; set; }
|
||||
public string BioTotal { get; set; }
|
||||
public string Species { get; set; }
|
||||
public string Analysed { get; set; }
|
||||
public string ColonyDistance { get; set; }
|
||||
}
|
15
Botanist/Botanist.csproj
Normal file
15
Botanist/Botanist.csproj
Normal file
@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
<Configurations>Debug;Release;Portable</Configurations>
|
||||
<RootNamespace>Botanist</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ObservatoryFramework\ObservatoryFramework.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
12
Botanist/BotanistSettings.cs
Normal file
12
Botanist/BotanistSettings.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using Observatory.Framework;
|
||||
|
||||
namespace Botanist;
|
||||
|
||||
class BotanistSettings
|
||||
{
|
||||
[SettingDisplayName("Enable Sampler Status Overlay")]
|
||||
public bool OverlayEnabled { get; set; }
|
||||
|
||||
[SettingDisplayName("Status Overlay is sticky until sampling is complete")]
|
||||
public bool OverlayIsSticky { get; set; }
|
||||
}
|
@ -1 +0,0 @@
|
||||
* @xjph
|
13
Explorer/CriteriaLoadException.cs
Normal file
13
Explorer/CriteriaLoadException.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Explorer;
|
||||
|
||||
internal class CriteriaLoadException : Exception
|
||||
{
|
||||
public CriteriaLoadException(string message, string script)
|
||||
{
|
||||
Message = message;
|
||||
OriginalScript = script;
|
||||
}
|
||||
|
||||
new public readonly string Message;
|
||||
public readonly string OriginalScript;
|
||||
}
|
382
Explorer/CustomCriteriaManager.cs
Normal file
382
Explorer/CustomCriteriaManager.cs
Normal file
@ -0,0 +1,382 @@
|
||||
using System.Text;
|
||||
using NLua;
|
||||
using NLua.Exceptions;
|
||||
using Observatory.Framework.Files.Journal.Exploration;
|
||||
|
||||
namespace Explorer;
|
||||
|
||||
internal class CustomCriteriaManager
|
||||
{
|
||||
private Lua LuaState;
|
||||
private Dictionary<string,LuaFunction> CriteriaFunctions;
|
||||
private Dictionary<string, string> CriteriaWithErrors = new();
|
||||
Action<Exception, string> ErrorLogger;
|
||||
private uint ScanCount;
|
||||
|
||||
public CustomCriteriaManager(Action<Exception, string> errorLogger)
|
||||
{
|
||||
ErrorLogger = errorLogger;
|
||||
CriteriaFunctions = new();
|
||||
ScanCount = 0;
|
||||
}
|
||||
|
||||
public void RefreshCriteria(string criteriaPath)
|
||||
{
|
||||
LuaState = new();
|
||||
LuaState.State.Encoding = Encoding.UTF8;
|
||||
LuaState.LoadCLRPackage();
|
||||
|
||||
#region Iterators
|
||||
|
||||
// Empty function for nil iterators
|
||||
LuaState.DoString("function nil_iterator() end");
|
||||
|
||||
//Materials and AtmosphereComposition
|
||||
LuaState.DoString(@"
|
||||
function materials (material_list)
|
||||
if material_list then
|
||||
local i = 0
|
||||
local count = material_list.Count
|
||||
return function ()
|
||||
i = i + 1
|
||||
if i <= count then
|
||||
return { name = material_list[i - 1].Name, percent = material_list[i - 1].Percent }
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil_iterator
|
||||
end
|
||||
end");
|
||||
|
||||
//Rings - internal filterable iterator
|
||||
LuaState.DoString(@"
|
||||
function _ringsFiltered (ring_list, filter_by)
|
||||
if ring_list then
|
||||
local i = 0
|
||||
local count = ring_list.Count
|
||||
return function ()
|
||||
i = i + 1
|
||||
while i <= count do
|
||||
local ring = ring_list[i - 1]
|
||||
if (filter_by == nil or string.find(ring.Name, filter_by)) then
|
||||
return { name = ring.Name, ringclass = ring.RingClass, massmt = ring.MassMT, innerrad = ring.InnerRad, outerrad = ring.OuterRad }
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil_iterator
|
||||
end
|
||||
end");
|
||||
|
||||
//Rings - internal filterable hasX check
|
||||
LuaState.DoString(@"
|
||||
function _hasRingsFiltered (ring_list, filter_by)
|
||||
if ring_list then
|
||||
local i = 0
|
||||
local count = ring_list.Count
|
||||
while i < count do
|
||||
if string.find(ring_list[i].Name, filter_by) then
|
||||
return true
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
return false
|
||||
end");
|
||||
|
||||
//Rings - iterate all - nil filter
|
||||
LuaState.DoString(@"
|
||||
function rings (ring_list)
|
||||
return _ringsFiltered(ring_list, nil)
|
||||
end");
|
||||
|
||||
//Rings - iterate proper rings only
|
||||
LuaState.DoString(@"
|
||||
function ringsOnly (ring_list)
|
||||
return _ringsFiltered(ring_list, 'Ring')
|
||||
end");
|
||||
|
||||
//Rings - iterate belts only
|
||||
LuaState.DoString(@"
|
||||
function beltsOnly (ring_list)
|
||||
return _ringsFiltered(ring_list, 'Belt')
|
||||
end");
|
||||
|
||||
//Bodies in system
|
||||
LuaState.DoString(@"
|
||||
function bodies (system_list)
|
||||
if system_list then
|
||||
local i = 0
|
||||
local count = system_list.Count
|
||||
return function ()
|
||||
i = i + 1
|
||||
if i <= count then
|
||||
return system_list[i - 1]
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil_iterator
|
||||
end
|
||||
end");
|
||||
|
||||
//Parent bodies
|
||||
LuaState.DoString(@"
|
||||
function allparents (parent_list)
|
||||
if parent_list then
|
||||
local i = 0
|
||||
local count
|
||||
if parent_list then count = parent_list.Count else count = 0 end
|
||||
return function ()
|
||||
i = i + 1
|
||||
if i <= count then
|
||||
return { parenttype = parent_list[i - 1].ParentType, body = parent_list[i - 1].Body, scan = parent_list[i - 1].Scan }
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil_iterator
|
||||
end
|
||||
end");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Convenience Functions
|
||||
|
||||
//Rings - has > 0 belts
|
||||
LuaState.DoString(@"
|
||||
function hasBelts (ring_list)
|
||||
return _hasRingsFiltered(ring_list, 'Belt')
|
||||
end");
|
||||
|
||||
//Rings - has > 0 proper rings
|
||||
LuaState.DoString(@"
|
||||
function hasRings (ring_list)
|
||||
return _hasRingsFiltered(ring_list, 'Ring')
|
||||
end");
|
||||
|
||||
LuaState.DoString(@"
|
||||
function isStar (scan)
|
||||
return scan.StarType and scan.StarType ~= ''
|
||||
end");
|
||||
|
||||
LuaState.DoString(@"
|
||||
function isPlanet (scan)
|
||||
return scan.PlanetClass ~= nil
|
||||
end");
|
||||
|
||||
LuaState.DoString(@"
|
||||
function hasAtmosphere (scan)
|
||||
return scan.AtmosphereComposition ~= nil
|
||||
end");
|
||||
|
||||
LuaState.DoString(@"
|
||||
function hasLandableAtmosphere (scan)
|
||||
return scan.Landable and scan.AtmosphereComposition ~= nil
|
||||
end");
|
||||
|
||||
#endregion
|
||||
|
||||
CriteriaFunctions.Clear();
|
||||
CriteriaWithErrors.Clear();
|
||||
var criteria = File.Exists(criteriaPath) ? File.ReadAllLines(criteriaPath) : Array.Empty<string>();
|
||||
StringBuilder script = new();
|
||||
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < criteria.Length; i++)
|
||||
{
|
||||
if (criteria[i].Trim().StartsWith("::"))
|
||||
{
|
||||
var scriptDescription = criteria[i].Trim().Replace("::", string.Empty);
|
||||
if (scriptDescription.ToLower() == "criteria" || scriptDescription.ToLower().StartsWith("criteria="))
|
||||
{
|
||||
var functionName = $"Criteria{i}";
|
||||
script.AppendLine($"function {functionName} (scan, parents, system, biosignals, geosignals)");
|
||||
i++;
|
||||
do
|
||||
{
|
||||
if (i >= criteria.Length)
|
||||
throw new Exception("Unterminated multi-line criteria.\r\nAre you missing an ::End::?");
|
||||
|
||||
script.AppendLine(criteria[i]);
|
||||
i++;
|
||||
} while (!criteria[i].Trim().ToLower().StartsWith("::end::"));
|
||||
script.AppendLine("end");
|
||||
|
||||
LuaState.DoString(script.ToString());
|
||||
CriteriaFunctions.Add(GetUniqueDescription(functionName, scriptDescription), LuaState[functionName] as LuaFunction);
|
||||
script.Clear();
|
||||
}
|
||||
else if (scriptDescription.ToLower() == "global")
|
||||
{
|
||||
i++;
|
||||
do
|
||||
{
|
||||
script.AppendLine(criteria[i]);
|
||||
i++;
|
||||
} while (!criteria[i].Trim().ToLower().StartsWith("::end::"));
|
||||
LuaState.DoString(script.ToString());
|
||||
script.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
|
||||
var functionName = $"Criteria{i}";
|
||||
|
||||
script.AppendLine($"function {functionName} (scan, parents, system, biosignals, geosignals)");
|
||||
script.AppendLine($" local result = {criteria[i]}");
|
||||
script.AppendLine(" local detail = ''");
|
||||
|
||||
if (criteria.Length > i + 1 && criteria[i + 1].Trim().ToLower() == "::detail::")
|
||||
{
|
||||
i++; i++;
|
||||
// Gate detail evaluation on result to allow safe use of criteria-checked values in detail string.
|
||||
script.AppendLine(" if result then");
|
||||
script.AppendLine($" detail = {criteria[i]}");
|
||||
script.AppendLine(" end");
|
||||
}
|
||||
|
||||
script.AppendLine($" return result, '{scriptDescription}', detail");
|
||||
script.AppendLine("end");
|
||||
|
||||
LuaState.DoString(script.ToString());
|
||||
CriteriaFunctions.Add(GetUniqueDescription(functionName, scriptDescription), LuaState[functionName] as LuaFunction);
|
||||
script.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var originalScript = script.ToString().Trim();
|
||||
|
||||
originalScript = originalScript.Remove(originalScript.LastIndexOf(Environment.NewLine));
|
||||
originalScript = originalScript[(originalScript.IndexOf(Environment.NewLine) + Environment.NewLine.Length)..];
|
||||
originalScript = originalScript.Replace('\t', ' ');
|
||||
|
||||
StringBuilder errorDetail = new();
|
||||
errorDetail.AppendLine("Error Reading Custom Criteria File:")
|
||||
.AppendLine(originalScript)
|
||||
.AppendLine("To correct this problem, make changes to the Lua source file, save it and either re-run read-all or scan another body. It will be automatically reloaded."); ErrorLogger(e, errorDetail.ToString());
|
||||
CriteriaFunctions.Clear(); // Don't use partial parse.
|
||||
throw new CriteriaLoadException(e.Message, originalScript);
|
||||
}
|
||||
}
|
||||
|
||||
public List<(string, string, bool)> CheckInterest(Scan scan, Dictionary<ulong, Dictionary<int, Scan>> scanHistory, Dictionary<ulong, Dictionary<int, FSSBodySignals>> signalHistory, ExplorerSettings settings)
|
||||
{
|
||||
List<(string, string, bool)> results = new();
|
||||
ScanCount++;
|
||||
|
||||
foreach (var criteriaFunction in CriteriaFunctions)
|
||||
{
|
||||
// Skip criteria which have previously thrown an error. We can't remove them from the dictionary while iterating it.
|
||||
if (CriteriaWithErrors.ContainsKey(criteriaFunction.Key)) continue;
|
||||
|
||||
var scanList = scanHistory[scan.SystemAddress].Values.ToList();
|
||||
|
||||
int bioSignals;
|
||||
int geoSignals;
|
||||
|
||||
if (signalHistory.ContainsKey(scan.SystemAddress) && signalHistory[scan.SystemAddress].ContainsKey(scan.BodyID))
|
||||
{
|
||||
bioSignals = signalHistory[scan.SystemAddress][scan.BodyID].Signals.Where(s => s.Type == "$SAA_SignalType_Biological;").Select(s => s.Count).FirstOrDefault();
|
||||
geoSignals = signalHistory[scan.SystemAddress][scan.BodyID].Signals.Where(s => s.Type == "$SAA_SignalType_Geological;").Select(s => s.Count).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
bioSignals = 0;
|
||||
geoSignals = 0;
|
||||
}
|
||||
|
||||
|
||||
List<Parent> parents;
|
||||
|
||||
if (scan.Parent != null)
|
||||
{
|
||||
parents = new();
|
||||
foreach (var parent in scan.Parent)
|
||||
{
|
||||
var parentScan = scanList.Where(s => s.BodyID == parent.Body);
|
||||
|
||||
parents.Add(new Parent
|
||||
{
|
||||
ParentType = parent.ParentType.ToString(),
|
||||
Body = parent.Body,
|
||||
Scan = parentScan.Any() ? parentScan.First() : null
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
parents = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = criteriaFunction.Value.Call(scan, parents, scanList, bioSignals, geoSignals);
|
||||
if (result.Length == 3 && ((bool?)result[0]).GetValueOrDefault(false))
|
||||
{
|
||||
results.Add((result[1].ToString(), result[2].ToString(), false));
|
||||
}
|
||||
else if (result.Length == 2)
|
||||
{
|
||||
results.Add((result[0].ToString(), result[1].ToString(), false));
|
||||
}
|
||||
}
|
||||
catch (LuaScriptException e)
|
||||
{
|
||||
results.Add((e.Message, scan.Json, false));
|
||||
|
||||
StringBuilder errorDetail = new();
|
||||
errorDetail.AppendLine($"while processing custom criteria '{criteriaFunction.Key}' on scan:")
|
||||
.AppendLine(scan.Json)
|
||||
.AppendLine("To correct this problem, make changes to the Lua source file, save it and either re-run read-all or scan another body. It will be automatically reloaded.");
|
||||
ErrorLogger(e, errorDetail.ToString());
|
||||
CriteriaWithErrors.Add(criteriaFunction.Key, e.Message + Environment.NewLine + errorDetail);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any erroring criteria. They will be repopulated next time the file is parsed.
|
||||
if (CriteriaWithErrors.Count > 0)
|
||||
{
|
||||
foreach (var criteriaKey in CriteriaWithErrors.Keys)
|
||||
{
|
||||
if (CriteriaFunctions.ContainsKey(criteriaKey)) CriteriaFunctions.Remove(criteriaKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (ScanCount > 99)
|
||||
{
|
||||
ScanCount = 0;
|
||||
LuaGC();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private string GetUniqueDescription(string functionName, string scriptDescription)
|
||||
{
|
||||
var uniqueDescription = functionName;
|
||||
if (scriptDescription.ToLower().StartsWith("criteria="))
|
||||
{
|
||||
uniqueDescription += scriptDescription.Replace("criteria=", "=", StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
return uniqueDescription;
|
||||
}
|
||||
|
||||
private void LuaGC()
|
||||
{
|
||||
LuaState?.DoString("collectgarbage()");
|
||||
}
|
||||
|
||||
internal class Parent
|
||||
{
|
||||
public string ParentType;
|
||||
public int Body;
|
||||
public Scan Scan;
|
||||
}
|
||||
|
||||
}
|
389
Explorer/DefaultCriteria.cs
Normal file
389
Explorer/DefaultCriteria.cs
Normal file
@ -0,0 +1,389 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Observatory.Framework.Files.Journal.Exploration;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Explorer;
|
||||
|
||||
internal static class DefaultCriteria
|
||||
{
|
||||
public static List<(string Description, string Detail, bool SystemWide)> CheckInterest(Scan scan, Dictionary<ulong, Dictionary<int, Scan>> scanHistory, Dictionary<ulong, Dictionary<int, FSSBodySignals>> signalHistory, ExplorerSettings settings)
|
||||
{
|
||||
List<(string, string, bool)> results = new();
|
||||
var textInfo = new CultureInfo("en-US", false).TextInfo;
|
||||
|
||||
var isRing = scan.BodyName.Contains("Ring");
|
||||
|
||||
#if DEBUG
|
||||
// results.Add("Test Scan Event", "Test Detail");
|
||||
#endif
|
||||
|
||||
#region Landable Checks
|
||||
if (scan.Landable)
|
||||
{
|
||||
if (settings.LandableTerraformable && scan.TerraformState?.Length > 0)
|
||||
{
|
||||
results.Add($"Landable and {scan.TerraformState}");
|
||||
}
|
||||
|
||||
if (settings.LandableRing && scan.Rings?.Count > 0)
|
||||
{
|
||||
results.Add("Ringed Landable Body");
|
||||
}
|
||||
|
||||
if (settings.LandableAtmosphere && scan.Atmosphere.Length > 0)
|
||||
{
|
||||
results.Add("Landable with Atmosphere", textInfo.ToTitleCase(scan.Atmosphere));
|
||||
}
|
||||
|
||||
if (settings.LandableHighG && scan.SurfaceGravity > 29.4)
|
||||
{
|
||||
results.Add("Landable with High Gravity", $"Surface gravity: {scan.SurfaceGravity / 9.81:0.00}g");
|
||||
}
|
||||
|
||||
if (settings.LandableLarge && scan.Radius > 18000000)
|
||||
{
|
||||
results.Add("Landable Large Planet", $"Radius: {scan.Radius / 1000:0}km");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Value Checks
|
||||
if (settings.HighValueMappable)
|
||||
{
|
||||
IList<string> HighValueNonTerraformablePlanetClasses = new[] {
|
||||
"Earthlike body",
|
||||
"Ammonia world",
|
||||
"Water world",
|
||||
};
|
||||
|
||||
if (HighValueNonTerraformablePlanetClasses.Contains(scan.PlanetClass) || scan.TerraformState?.Length > 0)
|
||||
{
|
||||
var info = new StringBuilder();
|
||||
|
||||
if (!scan.WasMapped)
|
||||
{
|
||||
if (!scan.WasDiscovered)
|
||||
info.Append("Undiscovered ");
|
||||
else
|
||||
info.Append("Unmapped ");
|
||||
}
|
||||
|
||||
if (scan.TerraformState?.Length > 0)
|
||||
info.Append("Terraformable ");
|
||||
|
||||
results.Add("High-Value Body", $"{info}{textInfo.ToTitleCase(scan.PlanetClass)}, {scan.DistanceFromArrivalLS:N0}Ls");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Parent Relative Checks
|
||||
|
||||
if (scan.SystemAddress != 0 && scan.SemiMajorAxis != 0 &&
|
||||
scanHistory[scan.SystemAddress].ContainsKey(scan.Parent[0].Body))
|
||||
{
|
||||
var parent = scanHistory[scan.SystemAddress][scan.Parent[0].Body];
|
||||
|
||||
if (settings.CloseOrbit && !isRing && parent.Radius * 3 > scan.SemiMajorAxis)
|
||||
{
|
||||
results.Add("Close Orbit", $"Orbital Radius: {scan.SemiMajorAxis / 1000:N0}km, Parent Radius: {parent.Radius / 1000:N0}km");
|
||||
}
|
||||
|
||||
if (settings.ShepherdMoon && !isRing && parent.Rings?.Any() == true && parent.Rings.Last().OuterRad > scan.SemiMajorAxis && !parent.Rings.Last().Name.Contains("Belt"))
|
||||
{
|
||||
results.Add("Shepherd Moon", $"Orbit: {scan.SemiMajorAxis / 1000:N0}km, Ring Radius: {parent.Rings.Last().OuterRad / 1000:N0}km");
|
||||
}
|
||||
|
||||
if (settings.CloseRing && parent.Rings?.Count > 0)
|
||||
{
|
||||
foreach (var ring in parent.Rings)
|
||||
{
|
||||
var separation = Math.Min(Math.Abs(scan.SemiMajorAxis - ring.OuterRad), Math.Abs(ring.InnerRad - scan.SemiMajorAxis));
|
||||
if (separation < scan.Radius * 10)
|
||||
{
|
||||
var ringTypeName = ring.Name.Contains("Belt") ? "Belt" : "Ring";
|
||||
var isLandable = scan.Landable ? ", Landable" : "";
|
||||
results.Add($"Close {ringTypeName} Proximity",
|
||||
$"Orbit: {scan.SemiMajorAxis / 1000:N0}km, Radius: {scan.Radius / 1000:N0}km, Distance from {ringTypeName.ToLower()}: {separation / 1000:N0}km{isLandable}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
if (settings.DiverseLife && signalHistory.ContainsKey(scan.SystemAddress) && signalHistory[scan.SystemAddress].ContainsKey(scan.BodyID))
|
||||
{
|
||||
var bioSignals = signalHistory[scan.SystemAddress][scan.BodyID].Signals.Where(s => s.Type == "$SAA_SignalType_Biological;");
|
||||
|
||||
if (bioSignals.Count() > 0 && bioSignals.First().Count > 7)
|
||||
{
|
||||
results.Add("Diverse Life", $"Biological Signals: {bioSignals.First().Count}");
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.WideRing && scan.Rings?.Count > 0)
|
||||
{
|
||||
foreach (var ring in scan.Rings.Where(r => !r.Name.Contains("Belt")))
|
||||
{
|
||||
var ringWidth = ring.OuterRad - ring.InnerRad;
|
||||
if (ringWidth > scan.Radius * 5)
|
||||
{
|
||||
var ringName = ring.Name.Replace(scan.BodyName, "").Trim();
|
||||
results.Add("Wide Ring", $"{ringName}: Width: {ringWidth / 299792458:N2}Ls / {ringWidth / 1000:N0}km, Parent Radius: {scan.Radius / 1000:N0}km");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.SmallObject && scan.StarType == null && scan.PlanetClass != null && scan.PlanetClass != "Barycentre" && scan.Radius < 300000)
|
||||
{
|
||||
results.Add("Small Object", $"Radius: {scan.Radius / 1000:N0}km");
|
||||
}
|
||||
|
||||
if (settings.HighEccentricity && scan.Eccentricity > 0.9)
|
||||
{
|
||||
results.Add("Highly Eccentric Orbit", $"Eccentricity: {Math.Round(scan.Eccentricity, 4)}");
|
||||
}
|
||||
|
||||
if (settings.Nested && !isRing && scan.Parent?.Count > 1 && scan.Parent[0].ParentType == ParentType.Planet && scan.Parent[1].ParentType == ParentType.Planet)
|
||||
{
|
||||
results.Add("Nested Moon");
|
||||
}
|
||||
|
||||
if (settings.FastRotation && scan.RotationPeriod != 0 && !scan.TidalLock && Math.Abs(scan.RotationPeriod) < 28800 && !isRing && string.IsNullOrEmpty(scan.StarType))
|
||||
{
|
||||
results.Add("Non-locked Body with Fast Rotation", $"Period: {scan.RotationPeriod/3600:N1} hours");
|
||||
}
|
||||
|
||||
if (settings.FastOrbit && scan.OrbitalPeriod != 0 && Math.Abs(scan.OrbitalPeriod) < 28800 && !isRing)
|
||||
{
|
||||
results.Add("Fast Orbit", $"Orbital Period: {Math.Abs(scan.OrbitalPeriod / 3600):N1} hours");
|
||||
}
|
||||
|
||||
// Close binary pair
|
||||
if ((settings.CloseBinary || settings.CollidingBinary) && scan.Parent?[0].ParentType == ParentType.Null && scan.Radius / scan.SemiMajorAxis > 0.4)
|
||||
{
|
||||
var binaryPartner = scanHistory[scan.SystemAddress].Where(priorScan => priorScan.Value.Parent?[0].Body == scan.Parent?[0].Body && scan.BodyID != priorScan.Key);
|
||||
|
||||
if (binaryPartner.Count() == 1)
|
||||
{
|
||||
if (binaryPartner.First().Value.Radius / binaryPartner.First().Value.SemiMajorAxis > 0.4)
|
||||
{
|
||||
if (settings.CollidingBinary && binaryPartner.First().Value.Radius + scan.Radius >= binaryPartner.First().Value.SemiMajorAxis * (1 - binaryPartner.First().Value.Eccentricity) + scan.SemiMajorAxis * (1 - scan.Eccentricity))
|
||||
{
|
||||
results.Add("COLLIDING binary", $"Orbit: {Math.Truncate((double)scan.SemiMajorAxis / 1000):N0}km, Radius: {Math.Truncate((double)scan.Radius / 1000):N0}km, Partner: {binaryPartner.First().Value.BodyName}");
|
||||
}
|
||||
else if (settings.CloseBinary)
|
||||
{
|
||||
results.Add("Close binary relative to body size", $"Orbit: {Math.Truncate((double)scan.SemiMajorAxis / 1000):N0}km, Radius: {Math.Truncate((double)scan.Radius / 1000):N0}km, Partner: {binaryPartner.First().Value.BodyName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.GoodFSDBody && scan.Landable)
|
||||
{
|
||||
List<string> boostMaterials = new()
|
||||
{
|
||||
"Carbon",
|
||||
"Germanium",
|
||||
"Arsenic",
|
||||
"Niobium",
|
||||
"Yttrium",
|
||||
"Polonium"
|
||||
};
|
||||
|
||||
if (boostMaterials.RemoveMatchedMaterials(scan) == 1)
|
||||
{
|
||||
results.Add("5 of 6 Premium Boost Materials", $"Missing material: {boostMaterials[0]}");
|
||||
}
|
||||
}
|
||||
|
||||
if ((settings.GreenSystem || settings.GoldSystem) && scan.Materials != null)
|
||||
{
|
||||
List<string> boostMaterials = new()
|
||||
{
|
||||
"Carbon",
|
||||
"Germanium",
|
||||
"Arsenic",
|
||||
"Niobium",
|
||||
"Yttrium",
|
||||
"Polonium"
|
||||
};
|
||||
|
||||
List<string> allSurfaceMaterials = new()
|
||||
{
|
||||
"Antimony", "Arsenic", "Cadmium", "Carbon",
|
||||
"Chromium", "Germanium", "Iron", "Manganese",
|
||||
"Mercury", "Molybdenum", "Nickel", "Niobium",
|
||||
"Phosphorus", "Polonium", "Ruthenium", "Selenium",
|
||||
"Sulphur", "Technetium", "Tellurium", "Tin",
|
||||
"Tungsten", "Vanadium", "Yttrium", "Zinc",
|
||||
"Zirconium"
|
||||
};
|
||||
|
||||
var systemBodies = scanHistory[scan.SystemAddress];
|
||||
|
||||
var notifyGreen = false;
|
||||
var notifyGold = false;
|
||||
|
||||
foreach (var body in systemBodies.Values)
|
||||
{
|
||||
|
||||
// If we enter either check and the count is already zero then a
|
||||
// previous body in system triggered the check, suppress notification.
|
||||
if (settings.GreenSystem && body.Materials != null)
|
||||
{
|
||||
if (boostMaterials.Count == 0)
|
||||
notifyGreen = false;
|
||||
else if (boostMaterials.RemoveMatchedMaterials(body) == 0)
|
||||
notifyGreen = true;
|
||||
}
|
||||
|
||||
if (settings.GoldSystem && body.Materials != null)
|
||||
{
|
||||
if (allSurfaceMaterials.Count == 0)
|
||||
notifyGold = false;
|
||||
else if (allSurfaceMaterials.RemoveMatchedMaterials(body) == 0)
|
||||
notifyGold = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (notifyGreen)
|
||||
results.Add("All Premium Boost Materials In System", string.Empty, true);
|
||||
|
||||
if (notifyGold)
|
||||
results.Add("All Surface Materials In System", string.Empty, true);
|
||||
}
|
||||
|
||||
if (settings.UncommonSecondary && scan.BodyID > 0 && !string.IsNullOrWhiteSpace(scan.StarType) && scan.DistanceFromArrivalLS > 10)
|
||||
{
|
||||
var excludeTypes = new List<string> { "O", "B", "A", "F", "G", "K", "M", "L", "T", "Y", "TTS" };
|
||||
if (!excludeTypes.Contains(scan.StarType.ToUpper()))
|
||||
{
|
||||
results.Add("Uncommon Secondary Star Type", $"{GetUncommonStarTypeName(scan.StarType)}, Distance: {scan.DistanceFromArrivalLS:N0}Ls");
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static string GetUncommonStarTypeName(string starType)
|
||||
{
|
||||
string name;
|
||||
|
||||
switch (starType.ToLower())
|
||||
{
|
||||
case "b_bluewhitesupergiant":
|
||||
name = "B Blue-White Supergiant";
|
||||
break;
|
||||
case "a_bluewhitesupergiant":
|
||||
name = "A Blue-White Supergiant";
|
||||
break;
|
||||
case "f_whitesupergiant":
|
||||
name = "F White Supergiant";
|
||||
break;
|
||||
case "g_whitesupergiant":
|
||||
name = "G White Supergiant";
|
||||
break;
|
||||
case "k_orangegiant":
|
||||
name = "K Orange Giant";
|
||||
break;
|
||||
case "m_redgiant":
|
||||
name = "M Red Giant";
|
||||
break;
|
||||
case "m_redsupergiant":
|
||||
name = "M Red Supergiant";
|
||||
break;
|
||||
case "aebe":
|
||||
name = "Herbig Ae/Be";
|
||||
break;
|
||||
case "w":
|
||||
case "wn":
|
||||
case "wnc":
|
||||
case "wc":
|
||||
case "wo":
|
||||
name = "Wolf-Rayet";
|
||||
break;
|
||||
case "c":
|
||||
case "cs":
|
||||
case "cn":
|
||||
case "cj":
|
||||
case "ch":
|
||||
case "chd":
|
||||
name = "Carbon Star";
|
||||
break;
|
||||
case "s":
|
||||
name = "S-Type Star";
|
||||
break;
|
||||
case "ms":
|
||||
name = "MS-Type Star";
|
||||
break;
|
||||
case "d":
|
||||
case "da":
|
||||
case "dab":
|
||||
case "dao":
|
||||
case "daz":
|
||||
case "dav":
|
||||
case "db":
|
||||
case "dbz":
|
||||
case "dbv":
|
||||
case "do":
|
||||
case "dov":
|
||||
case "dq":
|
||||
case "dc":
|
||||
case "dcv":
|
||||
case "dx":
|
||||
name = "White Dwarf";
|
||||
break;
|
||||
case "n":
|
||||
name = "Neutron Star";
|
||||
break;
|
||||
case "h":
|
||||
name = "Black Hole";
|
||||
break;
|
||||
case "supermassiveblackhole":
|
||||
name = "Supermassive Black Hole";
|
||||
break;
|
||||
case "x":
|
||||
name = "Exotic Star";
|
||||
break;
|
||||
case "rogueplanet":
|
||||
name = "Rogue Planet";
|
||||
break;
|
||||
case "tts":
|
||||
case "t":
|
||||
name = "T Tauri Type";
|
||||
break;
|
||||
default:
|
||||
name = starType + "-Type Star";
|
||||
break;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes materials from the list if found on the specified body.
|
||||
/// </summary>
|
||||
/// <param name="materials"></param>
|
||||
/// <param name="body"></param>
|
||||
/// <returns>Count of materials remaining in list.</returns>
|
||||
private static int RemoveMatchedMaterials(this List<string> materials, Scan body)
|
||||
{
|
||||
foreach (var material in body.Materials)
|
||||
{
|
||||
var matchedMaterial = materials.Find(mat => mat.Equals(material.Name, StringComparison.OrdinalIgnoreCase));
|
||||
if (matchedMaterial != null)
|
||||
{
|
||||
materials.Remove(matchedMaterial);
|
||||
}
|
||||
}
|
||||
return materials.Count;
|
||||
}
|
||||
|
||||
private static void Add(this List<(string, string, bool)> results, string description, string detail = "", bool systemWide = false)
|
||||
{
|
||||
results.Add((description, detail, systemWide));
|
||||
}
|
||||
}
|
304
Explorer/Explorer.cs
Normal file
304
Explorer/Explorer.cs
Normal file
@ -0,0 +1,304 @@
|
||||
using System.Reflection;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Files.Journal.Exploration;
|
||||
|
||||
namespace Explorer;
|
||||
|
||||
internal class Explorer
|
||||
{
|
||||
private IObservatoryCore ObservatoryCore;
|
||||
private ExplorerWorker ExplorerWorker;
|
||||
private Dictionary<ulong, Dictionary<int, Scan>> SystemBodyHistory;
|
||||
private Dictionary<ulong, Dictionary<int, FSSBodySignals>> BodySignalHistory;
|
||||
private Dictionary<ulong, Dictionary<int, ScanBaryCentre>> BarycentreHistory;
|
||||
private CustomCriteriaManager CustomCriteriaManager;
|
||||
private DateTime CriteriaLastModified;
|
||||
private string currentSystem = string.Empty;
|
||||
|
||||
internal Explorer(ExplorerWorker explorerWorker, IObservatoryCore core)
|
||||
{
|
||||
SystemBodyHistory = new();
|
||||
BodySignalHistory = new();
|
||||
BarycentreHistory = new();
|
||||
ExplorerWorker = explorerWorker;
|
||||
ObservatoryCore = core;
|
||||
CustomCriteriaManager = new(core.GetPluginErrorLogger(explorerWorker));
|
||||
CriteriaLastModified = new DateTime(0);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
SystemBodyHistory.Clear();
|
||||
BodySignalHistory.Clear();
|
||||
BarycentreHistory.Clear();
|
||||
}
|
||||
|
||||
public void RecordSignal(FSSBodySignals bodySignals)
|
||||
{
|
||||
if (!BodySignalHistory.ContainsKey(bodySignals.SystemAddress))
|
||||
{
|
||||
BodySignalHistory.Add(bodySignals.SystemAddress, new Dictionary<int, FSSBodySignals>());
|
||||
}
|
||||
|
||||
if (!BodySignalHistory[bodySignals.SystemAddress].ContainsKey(bodySignals.BodyID))
|
||||
{
|
||||
BodySignalHistory[bodySignals.SystemAddress].Add(bodySignals.BodyID, bodySignals);
|
||||
}
|
||||
}
|
||||
|
||||
public void RecordBarycentre(ScanBaryCentre scan)
|
||||
{
|
||||
if (!BarycentreHistory.ContainsKey(scan.SystemAddress))
|
||||
{
|
||||
BarycentreHistory.Add(scan.SystemAddress, new Dictionary<int, ScanBaryCentre>());
|
||||
}
|
||||
|
||||
if (!BarycentreHistory[scan.SystemAddress].ContainsKey(scan.BodyID))
|
||||
{
|
||||
BarycentreHistory[scan.SystemAddress].Add(scan.BodyID, scan);
|
||||
}
|
||||
}
|
||||
|
||||
private static string IncrementOrdinal(string ordinal)
|
||||
{
|
||||
var ordChar = ordinal.ToCharArray().Last();
|
||||
|
||||
if (new[] {'Z', '9'}.Contains(ordChar))
|
||||
{
|
||||
ordinal = IncrementOrdinal(ordinal.Length == 1 ? " " : string.Empty + ordinal[..^1]);
|
||||
ordChar = (char)(ordChar - 10);
|
||||
}
|
||||
|
||||
return ordinal[..^1] + (char)(ordChar + 1);
|
||||
}
|
||||
|
||||
private static string DecrementOrdinal(string ordinal)
|
||||
{
|
||||
var ordChar = ordinal.ToCharArray().Last();
|
||||
|
||||
if (new[] { 'A', '0' }.Contains(ordChar))
|
||||
{
|
||||
ordinal = DecrementOrdinal(ordinal.Length == 1 ? " " : string.Empty + ordinal[..^1]);
|
||||
ordChar = (char)(ordChar + 10);
|
||||
}
|
||||
|
||||
return ordinal[..^1] + (char)(ordChar - 1);
|
||||
}
|
||||
|
||||
public Scan ConvertBarycentre(ScanBaryCentre barycentre, Scan childScan)
|
||||
{
|
||||
var childAffix = childScan.BodyName
|
||||
.Replace(childScan.StarSystem, string.Empty).Trim();
|
||||
|
||||
string baryName;
|
||||
|
||||
if (!string.IsNullOrEmpty(childAffix))
|
||||
{
|
||||
var childOrdinal = childAffix.ToCharArray().Last();
|
||||
|
||||
// If the ID is one higher than the barycentre than this is the "first" child body
|
||||
var lowChild = childScan.BodyID - barycentre.BodyID == 1;
|
||||
|
||||
string baryAffix;
|
||||
|
||||
// Barycentre ordinal always labelled as low before high, e.g. "AB"
|
||||
if (lowChild)
|
||||
{
|
||||
baryAffix = childAffix + "-" + IncrementOrdinal(childAffix);
|
||||
}
|
||||
else
|
||||
{
|
||||
baryAffix = DecrementOrdinal(childAffix) + "-" + childAffix;
|
||||
}
|
||||
|
||||
baryName = barycentre.StarSystem + " " + baryAffix;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Without ordinals it's complicated to determine what the ordinal *should* be.
|
||||
// Just name the barycentre after the child object.
|
||||
baryName = childScan.BodyName + " Barycentre";
|
||||
}
|
||||
|
||||
Scan barycentreScan = new()
|
||||
{
|
||||
Timestamp = barycentre.Timestamp,
|
||||
BodyName = baryName,
|
||||
Parents = childScan.Parents.RemoveAt(0),
|
||||
PlanetClass = "Barycentre",
|
||||
StarSystem = barycentre.StarSystem,
|
||||
SystemAddress = barycentre.SystemAddress,
|
||||
BodyID = barycentre.BodyID,
|
||||
SemiMajorAxis = barycentre.SemiMajorAxis,
|
||||
Eccentricity = barycentre.Eccentricity,
|
||||
OrbitalInclination = barycentre.OrbitalInclination,
|
||||
Periapsis = barycentre.Periapsis,
|
||||
OrbitalPeriod = barycentre.OrbitalPeriod,
|
||||
AscendingNode = barycentre.AscendingNode,
|
||||
MeanAnomaly = barycentre.MeanAnomaly,
|
||||
Json = barycentre.Json
|
||||
};
|
||||
|
||||
return barycentreScan;
|
||||
}
|
||||
public void SetSystem(string potentialNewSystem)
|
||||
{
|
||||
if (string.IsNullOrEmpty(currentSystem) || currentSystem != potentialNewSystem)
|
||||
{
|
||||
currentSystem = potentialNewSystem;
|
||||
if (ExplorerWorker.settings.OnlyShowCurrentSystem && !ObservatoryCore.IsLogMonitorBatchReading)
|
||||
{
|
||||
ObservatoryCore.SetGridItems(ExplorerWorker, [
|
||||
typeof(ExplorerUIResults).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Select(p => p.Name)
|
||||
.ToDictionary(p => p, p => string.Empty)
|
||||
]);
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessScan(Scan scanEvent, bool readAll)
|
||||
{
|
||||
if (!readAll)
|
||||
{
|
||||
|
||||
// if (File.Exists(criteriaFilePath))
|
||||
// {
|
||||
// var fileModified = new FileInfo(criteriaFilePath).LastWriteTime;
|
||||
//
|
||||
// if (fileModified != CriteriaLastModified)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// CustomCriteriaManager.RefreshCriteria(criteriaFilePath);
|
||||
// }
|
||||
// catch (CriteriaLoadException e)
|
||||
// {
|
||||
// var exceptionResult = new ExplorerUIResults
|
||||
// {
|
||||
// BodyName = "Error Reading Custom Criteria File",
|
||||
// Time = DateTime.Now.ToString("G"),
|
||||
// Description = e.Message,
|
||||
// Details = e.OriginalScript
|
||||
// };
|
||||
// ObservatoryCore.AddGridItem(ExplorerWorker, exceptionResult);
|
||||
// }
|
||||
//
|
||||
// CriteriaLastModified = fileModified;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
Dictionary<int, Scan> systemBodies;
|
||||
if (SystemBodyHistory.ContainsKey(scanEvent.SystemAddress))
|
||||
{
|
||||
systemBodies = SystemBodyHistory[scanEvent.SystemAddress];
|
||||
if (systemBodies.ContainsKey(scanEvent.BodyID))
|
||||
{
|
||||
if (scanEvent.SystemAddress != 0)
|
||||
{
|
||||
//We've already checked this object.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
systemBodies = new();
|
||||
SystemBodyHistory.Add(scanEvent.SystemAddress, systemBodies);
|
||||
}
|
||||
|
||||
if (SystemBodyHistory.Count > 1000)
|
||||
{
|
||||
foreach (var entry in SystemBodyHistory.Where(entry => entry.Key != scanEvent.SystemAddress).ToList())
|
||||
{
|
||||
SystemBodyHistory.Remove(entry.Key);
|
||||
}
|
||||
SystemBodyHistory.TrimExcess();
|
||||
}
|
||||
|
||||
if (scanEvent.SystemAddress != 0 && !systemBodies.ContainsKey(scanEvent.BodyID))
|
||||
systemBodies.Add(scanEvent.BodyID, scanEvent);
|
||||
|
||||
var results = DefaultCriteria.CheckInterest(scanEvent, SystemBodyHistory, BodySignalHistory, ExplorerWorker.settings);
|
||||
|
||||
if (BarycentreHistory.ContainsKey(scanEvent.SystemAddress) && scanEvent.Parent != null && BarycentreHistory[scanEvent.SystemAddress].ContainsKey(scanEvent.Parent[0].Body))
|
||||
{
|
||||
ProcessScan(ConvertBarycentre(BarycentreHistory[scanEvent.SystemAddress][scanEvent.Parent[0].Body], scanEvent), readAll);
|
||||
}
|
||||
|
||||
// if (ExplorerWorker.settings.EnableCustomCriteria)
|
||||
// results.AddRange(CustomCriteriaManager.CheckInterest(scanEvent, SystemBodyHistory, BodySignalHistory, ExplorerWorker.settings));
|
||||
|
||||
if (results.Count > 0)
|
||||
{
|
||||
StringBuilder notificationDetail = new();
|
||||
StringBuilder notificationExtendedDetail = new();
|
||||
foreach (var result in results)
|
||||
{
|
||||
var scanResult = new ExplorerUIResults
|
||||
{
|
||||
BodyName = result.SystemWide ? scanEvent.StarSystem : scanEvent.BodyName,
|
||||
Time = scanEvent.TimestampDateTime.ToString("G"),
|
||||
Description = result.Description,
|
||||
Details = result.Detail
|
||||
};
|
||||
ObservatoryCore.AddGridItem(ExplorerWorker, scanResult);
|
||||
notificationDetail.AppendLine(result.Description);
|
||||
notificationExtendedDetail.AppendLine(result.Detail);
|
||||
}
|
||||
|
||||
string bodyAffix;
|
||||
|
||||
if (scanEvent.StarSystem != null && scanEvent.BodyName.StartsWith(scanEvent.StarSystem))
|
||||
{
|
||||
bodyAffix = scanEvent.BodyName.Replace(scanEvent.StarSystem, string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
bodyAffix = string.Empty;
|
||||
}
|
||||
|
||||
var bodyLabel = SecurityElement.Escape(scanEvent.PlanetClass == "Barycentre" ? "Barycentre" : "Body");
|
||||
|
||||
string spokenAffix;
|
||||
|
||||
if (bodyAffix.Length > 0)
|
||||
{
|
||||
if (bodyAffix.Contains("Ring"))
|
||||
{
|
||||
var ringIndex = bodyAffix.Length - 6;
|
||||
// spokenAffix =
|
||||
// "<say-as interpret-as=\"spell-out\">" + bodyAffix[..ringIndex]
|
||||
// + "</say-as><break strength=\"weak\"/>" + SplitOrdinalForSsml(bodyAffix.Substring(ringIndex, 1))
|
||||
// + bodyAffix[(ringIndex + 1)..];
|
||||
}
|
||||
else
|
||||
{
|
||||
//spokenAffix = SplitOrdinalForSsml(bodyAffix);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bodyLabel = "Primary Star";
|
||||
spokenAffix = string.Empty;
|
||||
}
|
||||
|
||||
throw new NotImplementedException("Scan Complete Notification Not Implemented");
|
||||
// NotificationArgs args = new()
|
||||
// {
|
||||
// Title = bodyLabel + bodyAffix,
|
||||
// TitleSsml = $"<speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xml:lang=\"en-US\"><voice name=\"\">{bodyLabel} {spokenAffix}</voice></speak>",
|
||||
// Detail = notificationDetail.ToString(),
|
||||
// Sender = ExplorerWorker.ShortName,
|
||||
// ExtendedDetails = notificationExtendedDetail.ToString(),
|
||||
// CoalescingId = scanEvent.BodyID,
|
||||
// };
|
||||
//
|
||||
// ObservatoryCore.SendNotification(args);
|
||||
}
|
||||
}
|
||||
}
|
17
Explorer/Explorer.csproj
Normal file
17
Explorer/Explorer.csproj
Normal file
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>Explorer</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLua" Version="1.7.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ObservatoryFramework\ObservatoryFramework.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
78
Explorer/ExplorerSettings.cs
Normal file
78
Explorer/ExplorerSettings.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using Observatory.Framework;
|
||||
|
||||
namespace Explorer;
|
||||
|
||||
public class ExplorerSettings
|
||||
{
|
||||
[SettingDisplayName("Only Show Current System")]
|
||||
public bool OnlyShowCurrentSystem { get; set; }
|
||||
|
||||
[SettingDisplayName("Landable & Terraformable")]
|
||||
public bool LandableTerraformable { get; set; }
|
||||
|
||||
[SettingDisplayName("Landable w/ Atmosphere")]
|
||||
public bool LandableAtmosphere { get; set; }
|
||||
|
||||
[SettingDisplayName("Landable High-g")]
|
||||
public bool LandableHighG { get; set; }
|
||||
|
||||
[SettingDisplayName("Landable Large")]
|
||||
public bool LandableLarge { get; set; }
|
||||
|
||||
[SettingDisplayName("Close Orbit")]
|
||||
public bool CloseOrbit { get; set; }
|
||||
|
||||
[SettingDisplayName("Shepherd Moon")]
|
||||
public bool ShepherdMoon { get; set; }
|
||||
|
||||
[SettingDisplayName("Wide Ring")]
|
||||
public bool WideRing { get; set; }
|
||||
|
||||
[SettingDisplayName("Close Binary")]
|
||||
public bool CloseBinary { get; set; }
|
||||
|
||||
[SettingDisplayName("Colliding Binary")]
|
||||
public bool CollidingBinary { get; set; }
|
||||
|
||||
[SettingDisplayName("Close Ring Proximity")]
|
||||
public bool CloseRing { get; set; }
|
||||
|
||||
[SettingDisplayName("Codex Discoveries")]
|
||||
public bool Codex { get; set; }
|
||||
|
||||
[SettingDisplayName("Uncommon Secondary Star")]
|
||||
public bool UncommonSecondary { get; set; }
|
||||
|
||||
[SettingDisplayName("Landable w/ Ring")]
|
||||
public bool LandableRing { get; set; }
|
||||
|
||||
[SettingDisplayName("Nested Moon")]
|
||||
public bool Nested { get; set; }
|
||||
|
||||
[SettingDisplayName("Small Object")]
|
||||
public bool SmallObject { get; set; }
|
||||
|
||||
[SettingDisplayName("Fast Rotation")]
|
||||
public bool FastRotation { get; set; }
|
||||
|
||||
[SettingDisplayName("Fast Orbit")]
|
||||
public bool FastOrbit { get; set; }
|
||||
|
||||
[SettingDisplayName("High Eccentricity")]
|
||||
public bool HighEccentricity { get; set; }
|
||||
|
||||
[SettingDisplayName("Diverse Life")]
|
||||
public bool DiverseLife { get; set; }
|
||||
|
||||
[SettingDisplayName("Good FSD Injection")]
|
||||
public bool GoodFSDBody { get; set; }
|
||||
|
||||
[SettingDisplayName("All FSD Mats In System")]
|
||||
public bool GreenSystem { get; set; }
|
||||
|
||||
[SettingDisplayName("All Surface Mats In System")]
|
||||
public bool GoldSystem { get; set; }
|
||||
|
||||
[SettingDisplayName("High-Value Body")]
|
||||
public bool HighValueMappable { get; set; }
|
||||
}
|
12
Explorer/ExplorerUIResults.cs
Normal file
12
Explorer/ExplorerUIResults.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Explorer;
|
||||
|
||||
public class ExplorerUIResults
|
||||
{
|
||||
public string Time { get; set; }
|
||||
|
||||
public string BodyName { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public string Details { get; set; }
|
||||
}
|
121
Explorer/Worker.cs
Normal file
121
Explorer/Worker.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reflection;
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Files.Journal;
|
||||
using Observatory.Framework.Files.Journal.Exploration;
|
||||
using Observatory.Framework.Files.Journal.FleetCarrier;
|
||||
using Observatory.Framework.Files.Journal.Travel;
|
||||
|
||||
namespace Explorer;
|
||||
|
||||
public class ExplorerWorker : IObservatoryWorker
|
||||
{
|
||||
public ExplorerWorker()
|
||||
{
|
||||
settings = new()
|
||||
{
|
||||
CloseBinary = true,
|
||||
CloseOrbit = true,
|
||||
CloseRing = true,
|
||||
CollidingBinary = true,
|
||||
FastRotation = true,
|
||||
HighEccentricity = true,
|
||||
LandableAtmosphere = true,
|
||||
LandableHighG = true,
|
||||
LandableLarge = true,
|
||||
LandableRing = true,
|
||||
LandableTerraformable = true,
|
||||
Nested = true,
|
||||
ShepherdMoon = true,
|
||||
SmallObject = true,
|
||||
WideRing = true
|
||||
};
|
||||
resultsGrid = new();
|
||||
}
|
||||
|
||||
private Explorer explorer;
|
||||
private ObservableCollection<object> resultsGrid;
|
||||
private IObservatoryCore Core;
|
||||
|
||||
private bool recordProcessedSinceBatchStart;
|
||||
|
||||
public string Name => "Observatory Explorer";
|
||||
|
||||
public string ShortName => "Explorer";
|
||||
|
||||
public string Version => typeof(ExplorerWorker).Assembly.GetName().Version.ToString();
|
||||
|
||||
private PluginUI pluginUI;
|
||||
|
||||
public PluginUI PluginUI => pluginUI;
|
||||
|
||||
public void Load(IObservatoryCore observatoryCore)
|
||||
{
|
||||
explorer = new Explorer(this, observatoryCore);
|
||||
resultsGrid.Add(new ExplorerUIResults());
|
||||
pluginUI = new PluginUI(resultsGrid);
|
||||
Core = observatoryCore;
|
||||
}
|
||||
|
||||
public void JournalEvent<TJournal>(TJournal journal) where TJournal : JournalBase
|
||||
{
|
||||
switch (journal)
|
||||
{
|
||||
case Scan scan:
|
||||
explorer.ProcessScan(scan, Core.IsLogMonitorBatchReading && recordProcessedSinceBatchStart);
|
||||
// Set this *after* the first scan processes so that we get the current custom criteria file.
|
||||
if (Core.IsLogMonitorBatchReading) recordProcessedSinceBatchStart = true;
|
||||
break;
|
||||
case FSSBodySignals signals:
|
||||
explorer.RecordSignal(signals);
|
||||
break;
|
||||
case ScanBaryCentre barycentre:
|
||||
explorer.RecordBarycentre(barycentre);
|
||||
break;
|
||||
case FSDJump fsdjump:
|
||||
if (fsdjump is CarrierJump && !((CarrierJump)fsdjump).Docked)
|
||||
break;
|
||||
explorer.SetSystem(fsdjump.StarSystem);
|
||||
break;
|
||||
case Location location:
|
||||
explorer.SetSystem(location.StarSystem);
|
||||
break;
|
||||
case DiscoveryScan discoveryScan:
|
||||
break;
|
||||
case FSSDiscoveryScan discoveryScan:
|
||||
break;
|
||||
case FSSSignalDiscovered signalDiscovered:
|
||||
break;
|
||||
case NavBeaconScan beaconScan:
|
||||
break;
|
||||
case SAAScanComplete scanComplete:
|
||||
break;
|
||||
case SAASignalsFound signalsFound:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void LogMonitorStateChanged(LogMonitorStateChangedEventArgs args)
|
||||
{
|
||||
if (LogMonitorStateChangedEventArgs.IsBatchRead(args.NewState))
|
||||
{
|
||||
// Beginning a batch read. Clear grid.
|
||||
recordProcessedSinceBatchStart = false;
|
||||
Core.SetGridItems(this, [
|
||||
typeof(ExplorerUIResults).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Select(p => p.Name)
|
||||
.ToDictionary(p => p, p => string.Empty)
|
||||
]);
|
||||
explorer.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public object Settings
|
||||
{
|
||||
get => settings;
|
||||
set => settings = (ExplorerSettings)value;
|
||||
}
|
||||
|
||||
internal ExplorerSettings settings;
|
||||
}
|
@ -1,353 +0,0 @@
|
||||
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<BodyAddress, BioPlanetDetail> BioPlanets;
|
||||
|
||||
// To make this journal locale agnostic, use the genus identifier and map to English names used in notifications.
|
||||
// Note: Values here are also used in the lookup for colony distance, so we also use this to resolve misspellings and Frontier bugs.
|
||||
private readonly Dictionary<String, String> EnglishGenusByIdentifier = new() {
|
||||
{ "$Codex_Ent_Aleoids_Genus_Name;", "Aleoida" },
|
||||
{ "$Codex_Ent_Bacterial_Genus_Name;", "Bacterium" },
|
||||
{ "$Codex_Ent_Cactoid_Genus_Name;", "Cactoida" },
|
||||
{ "$Codex_Ent_Clepeus_Genus_Name;;", "Clypeus" }, // Fun misspelling of the identifier discovered in the journals
|
||||
{ "$Codex_Ent_Clypeus_Genus_Name;", "Clypeus" },
|
||||
{ "$Codex_Ent_Conchas_Genus_Name;", "Concha" },
|
||||
{ "$Codex_Ent_Electricae_Genus_Name;", "Electricae" },
|
||||
{ "$Codex_Ent_Fonticulus_Genus_Name;", "Fonticulua" },
|
||||
{ "$Codex_Ent_Shrubs_Genus_Name;", "Frutexa" },
|
||||
{ "$Codex_Ent_Fumerolas_Genus_Name;", "Fumerola" },
|
||||
{ "$Codex_Ent_Fungoids_Genus_Name;", "Fungoida" },
|
||||
{ "$Codex_Ent_Osseus_Genus_Name;", "Osseus" },
|
||||
{ "$Codex_Ent_Recepta_Genus_Name;", "Recepta" },
|
||||
{ "$Codex_Ent_Stratum_Genus_Name;", "Stratum" },
|
||||
{ "$Codex_Ent_Tubus_Genus_Name;", "Tubus" },
|
||||
{ "$Codex_Ent_Tussocks_Genus_Name;", "Tussock" },
|
||||
{ "$Codex_Ent_Ground_Struct_Ice_Name;", "Crystalline Shards" },
|
||||
{ "$Codex_Ent_Brancae_Name;", "Brain Trees" },
|
||||
{ "$Codex_Ent_Seed_Name;", "Brain Tree" }, // Misspelling? :shrug: 'Seed' also seems to refer to peduncle things.
|
||||
{ "$Codex_Ent_Sphere_Name;", "Anemone" },
|
||||
{ "$Codex_Ent_Tube_Name;", "Sinuous Tubers" },
|
||||
{ "$Codex_Ent_Vents_Name;", "Amphora Plant" },
|
||||
{ "$Codex_Ent_Cone_Name;", "Bark Mounds" },
|
||||
};
|
||||
|
||||
// Note: Some Horizons bios may be missing, but they'll get localized genus name and default colony distance
|
||||
private readonly Dictionary<String, int> ColonyDistancesByGenus = new() {
|
||||
{ "Aleoida", 150 },
|
||||
{ "Bacterium", 500 },
|
||||
{ "Cactoida", 300 },
|
||||
{ "Clypeus", 150 },
|
||||
{ "Concha", 150 },
|
||||
{ "Electricae", 1000 },
|
||||
{ "Fonticulua", 500 },
|
||||
{ "Frutexa", 150 },
|
||||
{ "Fumerola", 100 },
|
||||
{ "Fungoida", 300 },
|
||||
{ "Osseus", 800 },
|
||||
{ "Recepta", 150 },
|
||||
{ "Stratum", 500 },
|
||||
{ "Tubus", 800 },
|
||||
{ "Tussock", 200 },
|
||||
{ "Crystalline Shards", DEFAULT_COLONY_DISTANCE },
|
||||
{ "Brain Tree", DEFAULT_COLONY_DISTANCE },
|
||||
{ "Anemone", DEFAULT_COLONY_DISTANCE },
|
||||
{ "Sinuous Tubers", DEFAULT_COLONY_DISTANCE },
|
||||
{ "Amphora Plant", DEFAULT_COLONY_DISTANCE },
|
||||
{ "Bark Mounds", DEFAULT_COLONY_DISTANCE },
|
||||
};
|
||||
private const int DEFAULT_COLONY_DISTANCE = 100;
|
||||
|
||||
ObservableCollection<object> GridCollection;
|
||||
private PluginUI pluginUI;
|
||||
private Guid? samplerStatusNotification = null;
|
||||
private BotanistSettings botanistSettings = new()
|
||||
{
|
||||
OverlayEnabled = true,
|
||||
OverlayIsSticky = 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>(TJournal journal) where TJournal : JournalBase
|
||||
{
|
||||
switch (journal)
|
||||
{
|
||||
case LoadGame loadGame:
|
||||
OdysseyLoaded = loadGame.Odyssey;
|
||||
break;
|
||||
case SAASignalsFound signalsFound:
|
||||
{
|
||||
BodyAddress systemBodyId = new()
|
||||
{
|
||||
SystemAddress = signalsFound.SystemAddress,
|
||||
BodyID = 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())
|
||||
{
|
||||
BioPlanets.Add(
|
||||
systemBodyId,
|
||||
new() {
|
||||
BodyName = signalsFound.BodyName,
|
||||
BioTotal = bioSignals.First().Count,
|
||||
SpeciesFound = new()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ScanOrganic scanOrganic:
|
||||
{
|
||||
BodyAddress systemBodyId = new()
|
||||
{
|
||||
SystemAddress = scanOrganic.SystemAddress,
|
||||
BodyID = scanOrganic.Body
|
||||
};
|
||||
if (!BioPlanets.ContainsKey(systemBodyId))
|
||||
{
|
||||
// Unlikely to ever end up in here, but just in case create a new planet entry.
|
||||
Dictionary<string, BioSampleDetail> bioSampleDetails = new();
|
||||
bioSampleDetails.Add(scanOrganic.Species_Localised, new()
|
||||
{
|
||||
Genus = EnglishGenusByIdentifier.GetValueOrDefault(scanOrganic.Genus, scanOrganic.Genus_Localised),
|
||||
Analysed = false
|
||||
});
|
||||
|
||||
BioPlanets.Add(systemBodyId, new() {
|
||||
BodyName = string.Empty,
|
||||
BioTotal = 0,
|
||||
SpeciesFound = bioSampleDetails
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var bioPlanet = BioPlanets[systemBodyId];
|
||||
|
||||
switch (scanOrganic.ScanType)
|
||||
{
|
||||
case ScanOrganicType.Log:
|
||||
case ScanOrganicType.Sample:
|
||||
if (!Core.IsLogMonitorBatchReading && botanistSettings.OverlayEnabled)
|
||||
{
|
||||
var colonyDistance = GetColonyDistance(scanOrganic);
|
||||
var sampleNum = scanOrganic.ScanType == ScanOrganicType.Log ? 1 : 2;
|
||||
NotificationArgs args = new()
|
||||
{
|
||||
Title = scanOrganic.Species_Localised,
|
||||
Detail = $"Sample {sampleNum} of 3{Environment.NewLine}Colony distance: {colonyDistance} m",
|
||||
Rendering = NotificationRendering.NativeVisual,
|
||||
Timeout = (botanistSettings.OverlayIsSticky ? 0 : -1),
|
||||
Sender = ShortName,
|
||||
};
|
||||
if (samplerStatusNotification == null)
|
||||
{
|
||||
var notificationId = Core.SendNotification(args);
|
||||
if (botanistSettings.OverlayIsSticky)
|
||||
samplerStatusNotification = notificationId;
|
||||
}
|
||||
else
|
||||
{
|
||||
Core.UpdateNotification(samplerStatusNotification.Value, args);
|
||||
}
|
||||
}
|
||||
|
||||
if (!bioPlanet.SpeciesFound.ContainsKey(scanOrganic.Species_Localised))
|
||||
{
|
||||
bioPlanet.SpeciesFound.Add(scanOrganic.Species_Localised, new()
|
||||
{
|
||||
Genus = EnglishGenusByIdentifier.GetValueOrDefault(scanOrganic.Genus, scanOrganic.Genus_Localised),
|
||||
Analysed = false
|
||||
});
|
||||
}
|
||||
break;
|
||||
case ScanOrganicType.Analyse:
|
||||
if (!bioPlanet.SpeciesFound[scanOrganic.Species_Localised].Analysed)
|
||||
{
|
||||
bioPlanet.SpeciesFound[scanOrganic.Species_Localised].Analysed = true;
|
||||
}
|
||||
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 object GetColonyDistance(ScanOrganic scan)
|
||||
{
|
||||
// Map the Genus to a Genus name then lookup colony distance.
|
||||
return ColonyDistancesByGenus.GetValueOrDefault(EnglishGenusByIdentifier.GetValueOrDefault(scan.Genus, String.Empty), DEFAULT_COLONY_DISTANCE);
|
||||
}
|
||||
|
||||
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,
|
||||
ColonyDistance = string.Empty,
|
||||
};
|
||||
Core.AddGridItem(this, planetRow);
|
||||
}
|
||||
|
||||
bool firstRow = true;
|
||||
foreach (var entry in bioPlanet.SpeciesFound)
|
||||
{
|
||||
int colonyDistance = ColonyDistancesByGenus.GetValueOrDefault(entry.Value.Genus ?? "", DEFAULT_COLONY_DISTANCE);
|
||||
var speciesRow = new BotanistGrid()
|
||||
{
|
||||
Body = firstRow ? bioPlanet.BodyName : string.Empty,
|
||||
BioTotal = firstRow ? bioPlanet.BioTotal.ToString() : string.Empty,
|
||||
Species = entry.Key,
|
||||
Analysed = entry.Value.Analysed ? "✓" : "",
|
||||
ColonyDistance = $"{colonyDistance}m",
|
||||
};
|
||||
Core.AddGridItem(this, speciesRow);
|
||||
firstRow = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BodyAddress
|
||||
{
|
||||
public ulong SystemAddress { get; set; }
|
||||
public int BodyID { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
// We want value equality here.
|
||||
|
||||
//
|
||||
// See the full list of guidelines at
|
||||
// http://go.microsoft.com/fwlink/?LinkID=85237
|
||||
// and also the guidance for operator== at
|
||||
// http://go.microsoft.com/fwlink/?LinkId=85238
|
||||
//
|
||||
|
||||
if (obj == null || GetType() != obj.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BodyAddress other = (BodyAddress)obj;
|
||||
return other.SystemAddress == SystemAddress
|
||||
&& other.BodyID == BodyID;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(SystemAddress, BodyID);
|
||||
}
|
||||
}
|
||||
|
||||
class BioPlanetDetail
|
||||
{
|
||||
public string BodyName { get; set; }
|
||||
public int BioTotal { get; set; }
|
||||
public Dictionary<string, BioSampleDetail> SpeciesFound { get; set; }
|
||||
}
|
||||
|
||||
class BioSampleDetail
|
||||
{
|
||||
public string Genus { get; set; }
|
||||
public bool Analysed { get; set; }
|
||||
}
|
||||
|
||||
public class BotanistGrid
|
||||
{
|
||||
[ColumnSuggestedWidth(300)]
|
||||
public string Body { get; set; }
|
||||
[ColumnSuggestedWidth(100)]
|
||||
public string BioTotal { get; set; }
|
||||
[ColumnSuggestedWidth(300)]
|
||||
public string Species { get; set; }
|
||||
[ColumnSuggestedWidth(100)]
|
||||
public string Analysed { get; set; }
|
||||
[ColumnSuggestedWidth(100)]
|
||||
public string ColonyDistance { get; set; }
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
using Observatory.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Observatory.Botanist
|
||||
{
|
||||
[SettingSuggestedColumnWidth(450)]
|
||||
class BotanistSettings
|
||||
{
|
||||
[SettingDisplayName("Enable Sampler Status Overlay")]
|
||||
public bool OverlayEnabled { get; set; }
|
||||
[SettingDisplayName("Status Overlay is sticky until sampling is complete")]
|
||||
public bool OverlayIsSticky { get; set; }
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>ObservatoryKey.snk</AssemblyOriginatorKeyFile>
|
||||
<Configurations>Debug;Release;Portable</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<VersionSuffix>0.22.$([System.DateTime]::UtcNow.Year.ToString().Substring(2))$([System.DateTime]::UtcNow.DayOfYear.ToString().PadLeft(3, "0")).$([System.DateTime]::UtcNow.ToString(HHmm))</VersionSuffix>
|
||||
<AssemblyVersion Condition=" '$(VersionSuffix)' == '' ">0.0.0.1</AssemblyVersion>
|
||||
<AssemblyVersion Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</AssemblyVersion>
|
||||
<Version Condition=" '$(VersionSuffix)' == '' ">0.0.1.0</Version>
|
||||
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="ObservatoryFramework">
|
||||
<HintPath>..\ObservatoryFramework\bin\Release\net6.0\ObservatoryFramework.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Condition=" '$(OS)' == 'Windows_NT' " Command="xcopy "$(TargetPath)" "$(ProjectDir)..\ObservatoryCore\$(OutDir)..\net6.0-windows\plugins\" /y" />
|
||||
<Exec Condition=" '$(OS)' != 'Windows_NT' " Command="cp "$(TargetPath)" "$(ProjectDir)../ObservatoryCore/$(OutDir)plugins/" -f" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
73
ObservatoryCore/Assets/Resources.Designer.cs
generated
73
ObservatoryCore/Assets/Resources.Designer.cs
generated
@ -1,73 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Observatory.Assets {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Observatory.Assets.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
|
||||
/// </summary>
|
||||
internal static System.Drawing.Icon EOCIcon_Presized {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("EOCIcon_Presized", resourceCulture);
|
||||
return ((System.Drawing.Icon)(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="EOCIcon_Presized" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>EOCIcon-Presized.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||
</data>
|
||||
</root>
|
@ -1,79 +0,0 @@
|
||||
using Observatory.Framework;
|
||||
using Observatory.UI;
|
||||
using System;
|
||||
|
||||
namespace Observatory.NativeNotification
|
||||
{
|
||||
public class NativePopup
|
||||
{
|
||||
private Dictionary<Guid, NotificationForm> notifications;
|
||||
|
||||
public NativePopup()
|
||||
{
|
||||
notifications = new();
|
||||
}
|
||||
|
||||
public Guid InvokeNativeNotification(NotificationArgs notificationArgs)
|
||||
{
|
||||
var notificationGuid = Guid.NewGuid();
|
||||
Application.OpenForms[0].Invoke(() =>
|
||||
{
|
||||
var notification = new NotificationForm(notificationGuid, notificationArgs);
|
||||
|
||||
notification.FormClosed += NotifyWindow_Closed;
|
||||
|
||||
foreach(var notificationForm in notifications)
|
||||
{
|
||||
notificationForm.Value.AdjustOffset(true);
|
||||
}
|
||||
|
||||
notifications.Add(notificationGuid, notification);
|
||||
notification.Show();
|
||||
});
|
||||
|
||||
return notificationGuid;
|
||||
}
|
||||
|
||||
private void NotifyWindow_Closed(object? sender, EventArgs e)
|
||||
{
|
||||
if (sender != null)
|
||||
{
|
||||
var currentNotification = (NotificationForm)sender;
|
||||
|
||||
foreach (var notification in notifications.Where(n => n.Value.CreationTime < currentNotification.CreationTime))
|
||||
{
|
||||
notification.Value.AdjustOffset(false);
|
||||
}
|
||||
|
||||
if (notifications.ContainsKey(currentNotification.Guid))
|
||||
{
|
||||
notifications.Remove(currentNotification.Guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseNotification(Guid guid)
|
||||
{
|
||||
if (notifications.ContainsKey(guid))
|
||||
{
|
||||
notifications[guid].Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateNotification(Guid guid, NotificationArgs notificationArgs)
|
||||
{
|
||||
if (notifications.ContainsKey(guid))
|
||||
{
|
||||
notifications[guid].Update(notificationArgs);
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseAll()
|
||||
{
|
||||
foreach (var notification in notifications)
|
||||
{
|
||||
notification.Value?.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
using Observatory.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Speech.Synthesis;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Observatory.NativeNotification
|
||||
{
|
||||
public class NativeVoice
|
||||
{
|
||||
private readonly Queue<NotificationArgs> notificationEvents;
|
||||
private bool processing;
|
||||
|
||||
public NativeVoice()
|
||||
{
|
||||
notificationEvents = new();
|
||||
processing = false;
|
||||
}
|
||||
|
||||
public void EnqueueAndAnnounce(NotificationArgs eventArgs)
|
||||
{
|
||||
notificationEvents.Enqueue(eventArgs);
|
||||
|
||||
if (!processing)
|
||||
{
|
||||
processing = true;
|
||||
ProcessQueueAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async void ProcessQueueAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Factory.StartNew(ProcessQueue);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
ObservatoryCore.LogError(ex, " - Native Voice Notifier");
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessQueue()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
string voice = Properties.Core.Default.VoiceSelected;
|
||||
|
||||
var speech = new SpeechSynthesizer()
|
||||
{
|
||||
Volume = Properties.Core.Default.VoiceVolume,
|
||||
Rate = Properties.Core.Default.VoiceRate
|
||||
};
|
||||
speech.SelectVoice(voice);
|
||||
|
||||
while (notificationEvents.Any())
|
||||
{
|
||||
var notification = notificationEvents.Dequeue();
|
||||
|
||||
if (notification.TitleSsml?.Length > 0)
|
||||
{
|
||||
string ssml = AddVoiceToSsml(notification.TitleSsml, voice);
|
||||
speech.SpeakSsml(ssml);
|
||||
}
|
||||
else
|
||||
{
|
||||
speech.Speak(notification.Title);
|
||||
}
|
||||
|
||||
if (notification.DetailSsml?.Length > 0)
|
||||
{
|
||||
string ssml = AddVoiceToSsml(notification.DetailSsml, voice);
|
||||
speech.SpeakSsml(ssml);
|
||||
}
|
||||
else
|
||||
{
|
||||
speech.Speak(notification.Detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
processing = false;
|
||||
}
|
||||
|
||||
private static string AddVoiceToSsml(string ssml, string voiceName)
|
||||
{
|
||||
XmlDocument ssmlDoc = new();
|
||||
ssmlDoc.LoadXml(ssml);
|
||||
|
||||
var ssmlNamespace = ssmlDoc.DocumentElement?.NamespaceURI;
|
||||
XmlNamespaceManager ssmlNs = new(ssmlDoc.NameTable);
|
||||
ssmlNs.AddNamespace("ssml", ssmlNamespace ?? string.Empty);
|
||||
|
||||
|
||||
var voiceNode = ssmlDoc.SelectSingleNode("/ssml:speak/ssml:voice", ssmlNs);
|
||||
|
||||
var voiceNameNode = voiceNode?.Attributes?.GetNamedItem("name");
|
||||
if (voiceNameNode != null)
|
||||
{
|
||||
voiceNameNode.Value = voiceName;
|
||||
}
|
||||
|
||||
return ssmlDoc.OuterXml;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
using Observatory.PluginManagement;
|
||||
using Observatory.Utils;
|
||||
using System.Reflection.PortableExecutable;
|
||||
|
||||
namespace Observatory
|
||||
{
|
||||
internal static class ObservatoryCore
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main(string[] args)
|
||||
{
|
||||
SettingsManager.Load();
|
||||
|
||||
if (args.Length > 0 && File.Exists(args[0]))
|
||||
{
|
||||
var fileInfo = new FileInfo(args[0]);
|
||||
if (fileInfo.Extension == ".eop" || fileInfo.Extension == ".zip")
|
||||
File.Copy(
|
||||
fileInfo.FullName,
|
||||
$"{AppDomain.CurrentDomain.BaseDirectory}{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}{fileInfo.Name}");
|
||||
}
|
||||
|
||||
string version = System.Reflection.Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0";
|
||||
try
|
||||
{
|
||||
if (Properties.Core.Default.CoreVersion != version)
|
||||
{
|
||||
try
|
||||
{
|
||||
Properties.Core.Default.Upgrade();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Silently ignore properties upgrade failure.
|
||||
}
|
||||
Properties.Core.Default.CoreVersion = version;
|
||||
SettingsManager.Save();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// To customize application configuration such as set high DPI settings or default font,
|
||||
// see https://aka.ms/applicationconfiguration.
|
||||
ApplicationConfiguration.Initialize();
|
||||
Application.Run(new UI.CoreForm());
|
||||
PluginManagement.PluginManager.GetInstance.Shutdown();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError(ex, version);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void LogError(Exception ex, string context)
|
||||
{
|
||||
var docPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments);
|
||||
var errorMessage = new System.Text.StringBuilder();
|
||||
var timestamp = DateTime.Now.ToString("G");
|
||||
errorMessage
|
||||
.AppendLine($"[{timestamp}] Error encountered in Elite Observatory {context}")
|
||||
.AppendLine(FormatExceptionMessage(ex))
|
||||
.AppendLine();
|
||||
System.IO.File.AppendAllText(docPath + System.IO.Path.DirectorySeparatorChar + "ObservatoryCrashLog.txt", errorMessage.ToString());
|
||||
}
|
||||
|
||||
static string FormatExceptionMessage(Exception ex, bool inner = false)
|
||||
{
|
||||
var errorMessage = new System.Text.StringBuilder();
|
||||
errorMessage
|
||||
.AppendLine($"{(inner ? "Inner e" : "E")}xception message: {ex.Message}")
|
||||
.AppendLine($"Stack trace:")
|
||||
.AppendLine(ex.StackTrace);
|
||||
if (ex.InnerException != null)
|
||||
errorMessage.AppendLine(FormatExceptionMessage(ex.InnerException, true));
|
||||
return errorMessage.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>Observatory</RootNamespace>
|
||||
<Configurations>Debug;Release;Portable</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<VersionSuffix>0.2.$([System.DateTime]::UtcNow.Year.ToString().Substring(2))$([System.DateTime]::UtcNow.DayOfYear.ToString().PadLeft(3, "0")).$([System.DateTime]::UtcNow.ToString(HHmm))</VersionSuffix>
|
||||
<AssemblyVersion Condition=" '$(VersionSuffix)' == '' ">0.0.0.1</AssemblyVersion>
|
||||
<AssemblyVersion Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</AssemblyVersion>
|
||||
<Version Condition=" '$(VersionSuffix)' == '' ">0.0.1.0</Version>
|
||||
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>
|
||||
<ApplicationIcon>Assets\EOCIcon-Presized.ico</ApplicationIcon>
|
||||
<StartupObject>Observatory.ObservatoryCore</StartupObject>
|
||||
<SignAssembly>False</SignAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Security.Extensions" Version="1.3.0" />
|
||||
<PackageReference Include="NAudio" Version="2.2.1" />
|
||||
<PackageReference Include="System.Speech" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="ObservatoryFramework">
|
||||
<HintPath>..\ObservatoryFramework\bin\Release\net6.0\ObservatoryFramework.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Assets\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Properties\Core.Designer.cs">
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Core.settings</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Assets\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Properties\Core.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Core.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
<Exec Condition=" '$(OS)' == 'Windows_NT'" Command="if not exist "$(ProjectDir)..\ObservatoryFramework\bin\Release\net6.0\ObservatoryFramework.dll" dotnet build "$(ProjectDir)..\ObservatoryFramework\ObservatoryFramework.csproj" -c Release" />
|
||||
<Exec Condition=" '$(OS)' == 'Windows_NT'" Command="if not exist "$(OutDir)plugins\ObservatoryExplorer.dll" dotnet build "$(ProjectDir)..\ObservatoryExplorer\ObservatoryExplorer.csproj" -c $(ConfigurationName)" />
|
||||
<Exec Condition=" '$(OS)' != 'Windows_NT'" Command="[ ! -e "$(ProjectDir)../ObservatoryFramework/bin/Release/net6.0/ObservatoryFramework.dll" ] && dotnet build "$(ProjectDir)../ObservatoryFramework/ObservatoryFramework.csproj" -c Release || echo No build necessary" />
|
||||
<Exec Condition=" '$(OS)' != 'Windows_NT'" Command="[ ! -e "$(ProjectDir)$(OutDir)plugins/ObservatoryExplorer.dll" ] && dotnet build "$(ProjectDir)../ObservatoryExplorer/ObservatoryExplorer.csproj" -c $(ConfigurationName) || echo No build necessary" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
@ -1,25 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.3.32922.545
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObservatoryCore", "ObservatoryCore.csproj", "{036A9A33-8C38-4A0C-BE2E-AC64B1B22090}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{036A9A33-8C38-4A0C-BE2E-AC64B1B22090}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{036A9A33-8C38-4A0C-BE2E-AC64B1B22090}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{036A9A33-8C38-4A0C-BE2E-AC64B1B22090}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{036A9A33-8C38-4A0C-BE2E-AC64B1B22090}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {53E6C705-9815-47F7-8ABF-92A7FA4E2F4B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
@ -1,38 +0,0 @@
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Files;
|
||||
using Observatory.Framework.Files.Journal;
|
||||
using Observatory.Framework.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Observatory.PluginManagement
|
||||
{
|
||||
public class PlaceholderPlugin : IObservatoryNotifier
|
||||
{
|
||||
public PlaceholderPlugin(string name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public string Name => name;
|
||||
|
||||
private string name;
|
||||
|
||||
public string ShortName => name;
|
||||
|
||||
public string Version => string.Empty;
|
||||
|
||||
public PluginUI PluginUI => new PluginUI(PluginUI.UIType.None, null);
|
||||
|
||||
public object Settings { get => null; set { } }
|
||||
|
||||
public void Load(IObservatoryCore observatoryCore)
|
||||
{ }
|
||||
|
||||
public void OnNotificationEvent(NotificationArgs notificationArgs)
|
||||
{ }
|
||||
}
|
||||
}
|
@ -1,242 +0,0 @@
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Files;
|
||||
using Observatory.Framework.Interfaces;
|
||||
using Observatory.NativeNotification;
|
||||
using Observatory.UI;
|
||||
using Observatory.Utils;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
|
||||
namespace Observatory.PluginManagement
|
||||
{
|
||||
public class PluginCore : IObservatoryCore
|
||||
{
|
||||
|
||||
private readonly NativeVoice NativeVoice;
|
||||
private readonly NativePopup NativePopup;
|
||||
private bool OverridePopup;
|
||||
private bool OverrideAudio;
|
||||
|
||||
public PluginCore(bool OverridePopup = false, bool OverrideAudio = false)
|
||||
{
|
||||
NativeVoice = new();
|
||||
NativePopup = new();
|
||||
this.OverridePopup = OverridePopup;
|
||||
this.OverrideAudio = OverrideAudio;
|
||||
}
|
||||
|
||||
public string Version => System.Reflection.Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0";
|
||||
|
||||
public Action<Exception, String> GetPluginErrorLogger(IObservatoryPlugin plugin)
|
||||
{
|
||||
return (ex, context) =>
|
||||
{
|
||||
ObservatoryCore.LogError(ex, $"from plugin {plugin.ShortName} {context}");
|
||||
};
|
||||
}
|
||||
|
||||
public Status GetStatus() => LogMonitor.GetInstance.Status;
|
||||
|
||||
public Guid SendNotification(string title, string text)
|
||||
{
|
||||
return SendNotification(new NotificationArgs() { Title = title, Detail = text });
|
||||
}
|
||||
|
||||
public Guid SendNotification(NotificationArgs notificationArgs)
|
||||
{
|
||||
var guid = Guid.Empty;
|
||||
|
||||
#if DEBUG // For exercising testing notifier plugins in read-all
|
||||
if (notificationArgs.Rendering.HasFlag(NotificationRendering.PluginNotifier))
|
||||
{
|
||||
var handler = Notification;
|
||||
handler?.Invoke(this, notificationArgs);
|
||||
}
|
||||
#endif
|
||||
if (!IsLogMonitorBatchReading)
|
||||
{
|
||||
#if !DEBUG
|
||||
if (notificationArgs.Rendering.HasFlag(NotificationRendering.PluginNotifier))
|
||||
{
|
||||
var handler = Notification;
|
||||
handler?.Invoke(this, notificationArgs);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!OverridePopup && Properties.Core.Default.NativeNotify && notificationArgs.Rendering.HasFlag(NotificationRendering.NativeVisual))
|
||||
{
|
||||
guid = NativePopup.InvokeNativeNotification(notificationArgs);
|
||||
}
|
||||
|
||||
if (!OverrideAudio && Properties.Core.Default.VoiceNotify && notificationArgs.Rendering.HasFlag(NotificationRendering.NativeVocal))
|
||||
{
|
||||
NativeVoice.EnqueueAndAnnounce(notificationArgs);
|
||||
}
|
||||
}
|
||||
|
||||
return guid;
|
||||
}
|
||||
|
||||
public void CancelNotification(Guid id)
|
||||
{
|
||||
ExecuteOnUIThread(() => NativePopup.CloseNotification(id));
|
||||
}
|
||||
|
||||
public void UpdateNotification(Guid id, NotificationArgs notificationArgs)
|
||||
{
|
||||
if (!IsLogMonitorBatchReading)
|
||||
{
|
||||
if (notificationArgs.Rendering.HasFlag(NotificationRendering.PluginNotifier))
|
||||
{
|
||||
var handler = Notification;
|
||||
handler?.Invoke(this, notificationArgs);
|
||||
}
|
||||
|
||||
if (notificationArgs.Rendering.HasFlag(NotificationRendering.NativeVisual))
|
||||
NativePopup.UpdateNotification(id, notificationArgs);
|
||||
|
||||
if (Properties.Core.Default.VoiceNotify && notificationArgs.Rendering.HasFlag(NotificationRendering.NativeVocal))
|
||||
{
|
||||
NativeVoice.EnqueueAndAnnounce(notificationArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an item to the datagrid on UI thread to ensure visual update.
|
||||
/// </summary>
|
||||
/// <param name="worker"></param>
|
||||
/// <param name="item"></param>
|
||||
public void AddGridItem(IObservatoryWorker worker, object item)
|
||||
{
|
||||
worker.PluginUI.DataGrid.Add(item);
|
||||
}
|
||||
|
||||
public void AddGridItems(IObservatoryWorker worker, IEnumerable<object> items)
|
||||
{
|
||||
BeginBulkUpdate(worker);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
worker.PluginUI.DataGrid.Add(item);
|
||||
}
|
||||
|
||||
EndBulkUpdate(worker);
|
||||
}
|
||||
|
||||
public void SetGridItems(IObservatoryWorker worker, IEnumerable<object> items)
|
||||
{
|
||||
BeginBulkUpdate(worker);
|
||||
|
||||
worker.PluginUI.DataGrid.Clear();
|
||||
foreach (var item in items)
|
||||
{
|
||||
worker.PluginUI.DataGrid.Add(item);
|
||||
}
|
||||
|
||||
EndBulkUpdate(worker);
|
||||
}
|
||||
|
||||
public void ClearGrid(IObservatoryWorker worker, object templateItem)
|
||||
{
|
||||
worker.PluginUI.DataGrid.Clear();
|
||||
}
|
||||
|
||||
public void ExecuteOnUIThread(Action action)
|
||||
{
|
||||
if (Application.OpenForms.Count > 0)
|
||||
Application.OpenForms[0].Invoke(action);
|
||||
}
|
||||
|
||||
public System.Net.Http.HttpClient HttpClient
|
||||
{
|
||||
get => Utils.HttpClient.Client;
|
||||
}
|
||||
|
||||
public LogMonitorState CurrentLogMonitorState
|
||||
{
|
||||
get => LogMonitor.GetInstance.CurrentState;
|
||||
}
|
||||
|
||||
public bool IsLogMonitorBatchReading
|
||||
{
|
||||
get => LogMonitorStateChangedEventArgs.IsBatchRead(LogMonitor.GetInstance.CurrentState);
|
||||
}
|
||||
|
||||
public event EventHandler<NotificationArgs> Notification;
|
||||
|
||||
internal event EventHandler<PluginMessageArgs> PluginMessage;
|
||||
|
||||
public string PluginStorageFolder
|
||||
{
|
||||
get
|
||||
{
|
||||
var context = new System.Diagnostics.StackFrame(1).GetMethod();
|
||||
#if PORTABLE
|
||||
string? observatoryLocation = System.Diagnostics.Process.GetCurrentProcess()?.MainModule?.FileName;
|
||||
var obsDir = new FileInfo(observatoryLocation ?? String.Empty).DirectoryName;
|
||||
return $"{obsDir}{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}{context?.DeclaringType?.Assembly.GetName().Name}-Data{Path.DirectorySeparatorChar}";
|
||||
#else
|
||||
string folderLocation = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
|
||||
+ $"{Path.DirectorySeparatorChar}ObservatoryCore{Path.DirectorySeparatorChar}{context?.DeclaringType?.Assembly.GetName().Name}{Path.DirectorySeparatorChar}";
|
||||
|
||||
if (!Directory.Exists(folderLocation))
|
||||
Directory.CreateDirectory(folderLocation);
|
||||
|
||||
return folderLocation;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PlayAudioFile(string filePath)
|
||||
{
|
||||
await AudioHandler.PlayFile(filePath);
|
||||
}
|
||||
|
||||
public void SendPluginMessage(IObservatoryPlugin plugin, object message)
|
||||
{
|
||||
PluginMessage?.Invoke(this, new PluginMessageArgs(plugin.Name, plugin.Version, message));
|
||||
}
|
||||
|
||||
internal void Shutdown()
|
||||
{
|
||||
NativePopup.CloseAll();
|
||||
}
|
||||
|
||||
private void BeginBulkUpdate(IObservatoryWorker worker)
|
||||
{
|
||||
PluginListView? listView = FindPluginListView(worker);
|
||||
if (listView == null) return;
|
||||
|
||||
ExecuteOnUIThread(() => { listView.SuspendDrawing(); });
|
||||
}
|
||||
|
||||
private void EndBulkUpdate(IObservatoryWorker worker)
|
||||
{
|
||||
PluginListView? listView = FindPluginListView(worker);
|
||||
if (listView == null) return;
|
||||
|
||||
ExecuteOnUIThread(() => { listView.ResumeDrawing(); });
|
||||
}
|
||||
|
||||
private PluginListView? FindPluginListView(IObservatoryWorker worker)
|
||||
{
|
||||
if (worker.PluginUI.PluginUIType != PluginUI.UIType.Basic
|
||||
|| !(worker.PluginUI.UI is Panel)) return null;
|
||||
|
||||
PluginListView? listView = null;
|
||||
Panel panel = worker.PluginUI.UI as Panel;
|
||||
|
||||
foreach (var control in panel.Controls)
|
||||
{
|
||||
if (control?.GetType() == typeof(PluginListView))
|
||||
{
|
||||
listView = (PluginListView)control;
|
||||
return listView;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,477 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Data;
|
||||
using Observatory.Framework.Interfaces;
|
||||
using System.IO;
|
||||
using Observatory.Framework;
|
||||
using System.Text.Json;
|
||||
using Observatory.Utils;
|
||||
using Microsoft.Security.Extensions;
|
||||
|
||||
namespace Observatory.PluginManagement
|
||||
{
|
||||
public class PluginManager
|
||||
{
|
||||
public static PluginManager GetInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
return _instance.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Lazy<PluginManager> _instance = new Lazy<PluginManager>(NewPluginManager);
|
||||
|
||||
private static PluginManager NewPluginManager()
|
||||
{
|
||||
return new PluginManager();
|
||||
}
|
||||
|
||||
|
||||
public readonly List<(string error, string? detail)> errorList;
|
||||
public readonly List<Panel> pluginPanels;
|
||||
public readonly List<DataTable> pluginTables;
|
||||
public readonly List<(IObservatoryWorker plugin, PluginStatus signed)> workerPlugins;
|
||||
public readonly List<(IObservatoryNotifier plugin, PluginStatus signed)> notifyPlugins;
|
||||
private readonly PluginCore core;
|
||||
private readonly PluginEventHandler pluginHandler;
|
||||
|
||||
private PluginManager()
|
||||
{
|
||||
errorList = LoadPlugins(out workerPlugins, out notifyPlugins);
|
||||
|
||||
pluginHandler = new PluginEventHandler(workerPlugins.Select(p => p.plugin), notifyPlugins.Select(p => p.plugin));
|
||||
var logMonitor = LogMonitor.GetInstance;
|
||||
pluginPanels = new();
|
||||
pluginTables = new();
|
||||
|
||||
logMonitor.JournalEntry += pluginHandler.OnJournalEvent;
|
||||
logMonitor.StatusUpdate += pluginHandler.OnStatusUpdate;
|
||||
logMonitor.LogMonitorStateChanged += pluginHandler.OnLogMonitorStateChanged;
|
||||
|
||||
var ovPopup = notifyPlugins.Any(n => n.plugin.OverridePopupNotifications);
|
||||
var ovAudio = notifyPlugins.Any(n => n.plugin.OverrideAudioNotifications);
|
||||
|
||||
core = new PluginCore(ovPopup, ovAudio);
|
||||
|
||||
List<IObservatoryPlugin> errorPlugins = new();
|
||||
|
||||
foreach (var plugin in workerPlugins.Select(p => p.plugin))
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadSettings(plugin);
|
||||
plugin.Load(core);
|
||||
}
|
||||
catch (PluginException ex)
|
||||
{
|
||||
errorList.Add((FormatErrorMessage(ex), ex.StackTrace));
|
||||
errorPlugins.Add(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
workerPlugins.RemoveAll(w => errorPlugins.Contains(w.plugin));
|
||||
errorPlugins.Clear();
|
||||
|
||||
foreach (var plugin in notifyPlugins.Select(p => p.plugin))
|
||||
{
|
||||
// Notifiers which are also workers need not be loaded again (they are the same instance).
|
||||
if (!plugin.GetType().IsAssignableTo(typeof(IObservatoryWorker)))
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadSettings(plugin);
|
||||
plugin.Load(core);
|
||||
}
|
||||
catch (PluginException ex)
|
||||
{
|
||||
errorList.Add((FormatErrorMessage(ex), ex.StackTrace));
|
||||
errorPlugins.Add(plugin);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorList.Add(($"{plugin.ShortName}: {ex.Message}", ex.StackTrace));
|
||||
errorPlugins.Add(plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notifyPlugins.RemoveAll(n => errorPlugins.Contains(n.plugin));
|
||||
|
||||
core.Notification += pluginHandler.OnNotificationEvent;
|
||||
core.PluginMessage += pluginHandler.OnPluginMessageEvent;
|
||||
|
||||
if (errorList.Any())
|
||||
ErrorReporter.ShowErrorPopup("Plugin Load Error" + (errorList.Count > 1 ? "s" : String.Empty), errorList);
|
||||
}
|
||||
|
||||
private static string FormatErrorMessage(PluginException ex)
|
||||
{
|
||||
return $"{ex.PluginName}: {ex.UserMessage}";
|
||||
}
|
||||
|
||||
private void LoadSettings(IObservatoryPlugin plugin)
|
||||
{
|
||||
string savedSettings = Properties.Core.Default.PluginSettings;
|
||||
Dictionary<string, object> pluginSettings;
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(savedSettings))
|
||||
{
|
||||
var settings = JsonSerializer.Deserialize<Dictionary<string, object>>(savedSettings);
|
||||
if (settings != null)
|
||||
{
|
||||
pluginSettings = settings;
|
||||
}
|
||||
else
|
||||
{
|
||||
pluginSettings = new();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pluginSettings = new();
|
||||
}
|
||||
|
||||
if (pluginSettings.ContainsKey(plugin.Name))
|
||||
{
|
||||
var settingsElement = (JsonElement)pluginSettings[plugin.Name];
|
||||
var settingsObject = JsonSerializer.Deserialize(settingsElement.GetRawText(), plugin.Settings.GetType());
|
||||
plugin.Settings = settingsObject;
|
||||
}
|
||||
}
|
||||
|
||||
public static Dictionary<PropertyInfo, string> GetSettingDisplayNames(object settings)
|
||||
{
|
||||
var settingNames = new Dictionary<PropertyInfo, string>();
|
||||
|
||||
if (settings != null)
|
||||
{
|
||||
var properties = settings.GetType().GetProperties();
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var attrib = property.GetCustomAttribute<SettingDisplayName>();
|
||||
if (attrib == null)
|
||||
{
|
||||
settingNames.Add(property, property.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
settingNames.Add(property, attrib.DisplayName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return settingNames;
|
||||
}
|
||||
|
||||
public void SaveSettings(IObservatoryPlugin plugin, object settings)
|
||||
{
|
||||
string savedSettings = Properties.Core.Default.PluginSettings;
|
||||
Dictionary<string, object> pluginSettings;
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(savedSettings))
|
||||
{
|
||||
pluginSettings = JsonSerializer.Deserialize<Dictionary<string, object>>(savedSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
pluginSettings = new();
|
||||
}
|
||||
|
||||
if (pluginSettings.ContainsKey(plugin.Name))
|
||||
{
|
||||
pluginSettings[plugin.Name] = settings;
|
||||
}
|
||||
else
|
||||
{
|
||||
pluginSettings.Add(plugin.Name, settings);
|
||||
}
|
||||
|
||||
string newSettings = JsonSerializer.Serialize(pluginSettings, new JsonSerializerOptions()
|
||||
{
|
||||
ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.Preserve
|
||||
});
|
||||
|
||||
Properties.Core.Default.PluginSettings = newSettings;
|
||||
SettingsManager.Save();
|
||||
}
|
||||
|
||||
public void SetPluginEnabled(IObservatoryPlugin plugin, bool enabled)
|
||||
{
|
||||
pluginHandler.SetPluginEnabled(plugin, enabled);
|
||||
}
|
||||
|
||||
private static List<(string, string?)> LoadPlugins(out List<(IObservatoryWorker plugin, PluginStatus signed)> observatoryWorkers, out List<(IObservatoryNotifier plugin, PluginStatus signed)> observatoryNotifiers)
|
||||
{
|
||||
observatoryWorkers = new();
|
||||
observatoryNotifiers = new();
|
||||
var errorList = new List<(string, string?)>();
|
||||
|
||||
string pluginPath = $"{AppDomain.CurrentDomain.BaseDirectory}{Path.DirectorySeparatorChar}plugins";
|
||||
|
||||
string? ownExe = System.Diagnostics.Process.GetCurrentProcess()?.MainModule?.FileName;
|
||||
FileSignatureInfo ownSig;
|
||||
|
||||
// This will throw if ownExe is null, but that's an error condition regardless.
|
||||
using (var stream = File.OpenRead(ownExe ?? String.Empty))
|
||||
ownSig = FileSignatureInfo.GetFromFileStream(stream);
|
||||
|
||||
|
||||
if (Directory.Exists(pluginPath))
|
||||
{
|
||||
ExtractPlugins(pluginPath);
|
||||
|
||||
var pluginLibraries = Directory.GetFiles($"{AppDomain.CurrentDomain.BaseDirectory}{Path.DirectorySeparatorChar}plugins", "*.dll");
|
||||
foreach (var dll in pluginLibraries)
|
||||
{
|
||||
try
|
||||
{
|
||||
PluginStatus pluginStatus = PluginStatus.SigCheckDisabled;
|
||||
bool loadOkay = true;
|
||||
|
||||
if (!Properties.Core.Default.AllowUnsigned)
|
||||
{
|
||||
if (ownSig.Kind == SignatureKind.Embedded)
|
||||
{
|
||||
FileSignatureInfo pluginSig;
|
||||
using (var stream = File.OpenRead(dll))
|
||||
pluginSig = FileSignatureInfo.GetFromFileStream(stream);
|
||||
|
||||
if (pluginSig.Kind == SignatureKind.Embedded)
|
||||
{
|
||||
if (pluginSig.SigningCertificate.Thumbprint == ownSig.SigningCertificate.Thumbprint)
|
||||
{
|
||||
pluginStatus = PluginStatus.Signed;
|
||||
}
|
||||
else
|
||||
{
|
||||
pluginStatus = PluginStatus.InvalidSignature;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pluginStatus = PluginStatus.Unsigned;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pluginStatus = PluginStatus.NoCert;
|
||||
}
|
||||
|
||||
if (pluginStatus != PluginStatus.Signed && pluginStatus != PluginStatus.NoCert)
|
||||
{
|
||||
string pluginHash = ComputeSha512Hash(dll);
|
||||
|
||||
if (Properties.Core.Default.UnsignedAllowed == null)
|
||||
Properties.Core.Default.UnsignedAllowed = new();
|
||||
|
||||
if (!Properties.Core.Default.UnsignedAllowed.Contains(pluginHash))
|
||||
{
|
||||
string warning;
|
||||
warning = $"Unable to confirm signature of plugin library {dll}.\r\n\r\n";
|
||||
warning += "Please ensure that you trust the source of this plugin before loading it.\r\n\r\n";
|
||||
warning += "Do you wish to continue loading the plugin? If you load this plugin you will not be asked again for this file.";
|
||||
|
||||
var response = MessageBox.Show(warning, "Plugin Signature Warning", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning);
|
||||
|
||||
if (response == DialogResult.OK)
|
||||
{
|
||||
Properties.Core.Default.UnsignedAllowed.Add(pluginHash);
|
||||
SettingsManager.Save();
|
||||
}
|
||||
else
|
||||
{
|
||||
loadOkay = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (loadOkay)
|
||||
{
|
||||
string error = LoadPluginAssembly(dll, observatoryWorkers, observatoryNotifiers, pluginStatus);
|
||||
if (!string.IsNullOrWhiteSpace(error))
|
||||
{
|
||||
errorList.Add((error, string.Empty));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorList.Add(($"ERROR: {new FileInfo(dll).Name}, {ex.Message}", ex.StackTrace ?? String.Empty));
|
||||
LoadPlaceholderPlugin(dll, PluginStatus.InvalidLibrary, observatoryNotifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
return errorList;
|
||||
}
|
||||
|
||||
private static string ComputeSha512Hash(string filePath)
|
||||
{
|
||||
using (var SHA512 = System.Security.Cryptography.SHA512.Create())
|
||||
{
|
||||
using (FileStream fileStream = File.OpenRead(filePath))
|
||||
return BitConverter.ToString(SHA512.ComputeHash(fileStream)).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExtractPlugins(string pluginFolder)
|
||||
{
|
||||
var files = Directory.GetFiles(pluginFolder, "*.zip")
|
||||
.Concat(Directory.GetFiles(pluginFolder, "*.eop")); // Elite Observatory Plugin
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.IO.Compression.ZipFile.ExtractToDirectory(file, pluginFolder, true);
|
||||
File.Delete(file);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Just ignore files that don't extract successfully.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string LoadPluginAssembly(string dllPath, List<(IObservatoryWorker plugin, PluginStatus signed)> workers, List<(IObservatoryNotifier plugin, PluginStatus signed)> notifiers, PluginStatus pluginStatus)
|
||||
{
|
||||
string recursionGuard = string.Empty;
|
||||
|
||||
System.Runtime.Loader.AssemblyLoadContext.Default.Resolving += (context, name) => {
|
||||
|
||||
if ((name?.Name?.EndsWith("resources")).GetValueOrDefault(false))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Importing Observatory.Framework in the Explorer Lua scripts causes an attempt to reload
|
||||
// the assembly, just hand it back the one we already have.
|
||||
if ((name?.Name?.StartsWith("Observatory.Framework")).GetValueOrDefault(false) || name?.Name == "ObservatoryFramework")
|
||||
{
|
||||
return context.Assemblies.Where(a => (a.FullName?.Contains("ObservatoryFramework")).GetValueOrDefault(false)).First();
|
||||
}
|
||||
|
||||
var foundDlls = Directory.GetFileSystemEntries(new FileInfo($"{AppDomain.CurrentDomain.BaseDirectory}{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}deps").FullName, name.Name + ".dll", SearchOption.TopDirectoryOnly);
|
||||
if (foundDlls.Any())
|
||||
{
|
||||
return context.LoadFromAssemblyPath(foundDlls[0]);
|
||||
}
|
||||
|
||||
if (name.Name != recursionGuard && name.Name != null)
|
||||
{
|
||||
recursionGuard = name.Name;
|
||||
return context.LoadFromAssemblyName(name);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Unable to load assembly " + name.Name);
|
||||
}
|
||||
};
|
||||
|
||||
var pluginAssembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(new FileInfo(dllPath).FullName);
|
||||
Type[] types;
|
||||
string err = string.Empty;
|
||||
int pluginCount = 0;
|
||||
try
|
||||
{
|
||||
types = pluginAssembly.GetTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
types = ex.Types.OfType<Type>().ToArray();
|
||||
}
|
||||
catch
|
||||
{
|
||||
types = Array.Empty<Type>();
|
||||
}
|
||||
|
||||
IEnumerable<Type> workerTypes = types.Where(t => t.IsAssignableTo(typeof(IObservatoryWorker)));
|
||||
foreach (Type worker in workerTypes)
|
||||
{
|
||||
ConstructorInfo? constructor = worker.GetConstructor(Array.Empty<Type>());
|
||||
if (constructor != null)
|
||||
{
|
||||
object instance = constructor.Invoke(Array.Empty<object>());
|
||||
workers.Add(((instance as IObservatoryWorker)!, pluginStatus));
|
||||
if (instance is IObservatoryNotifier)
|
||||
{
|
||||
// This is also a notifier; add to the notifier list as well, so the work and notifier are
|
||||
// the same instance and can share state.
|
||||
notifiers.Add(((instance as IObservatoryNotifier)!, pluginStatus));
|
||||
}
|
||||
pluginCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out items which are also workers as we've already created them above.
|
||||
var notifyTypes = types.Where(t =>
|
||||
t.IsAssignableTo(typeof(IObservatoryNotifier)) && !t.IsAssignableTo(typeof(IObservatoryWorker)));
|
||||
foreach (Type notifier in notifyTypes)
|
||||
{
|
||||
ConstructorInfo? constructor = notifier.GetConstructor(Array.Empty<Type>());
|
||||
if (constructor != null)
|
||||
{
|
||||
object instance = constructor.Invoke(Array.Empty<object>());
|
||||
notifiers.Add(((instance as IObservatoryNotifier)!, pluginStatus));
|
||||
pluginCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (pluginCount == 0)
|
||||
{
|
||||
err += $"ERROR: Library '{dllPath}' contains no suitable interfaces.";
|
||||
LoadPlaceholderPlugin(dllPath, PluginStatus.InvalidPlugin, notifiers);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
internal void Shutdown()
|
||||
{
|
||||
core.Shutdown();
|
||||
}
|
||||
|
||||
private static void LoadPlaceholderPlugin(string dllPath, PluginStatus pluginStatus, List<(IObservatoryNotifier plugin, PluginStatus signed)> notifiers)
|
||||
{
|
||||
PlaceholderPlugin placeholder = new(new FileInfo(dllPath).Name);
|
||||
notifiers.Add((placeholder, pluginStatus));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible plugin load results and signature statuses.
|
||||
/// </summary>
|
||||
public enum PluginStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Plugin valid and signed with matching certificate.
|
||||
/// </summary>
|
||||
Signed,
|
||||
/// <summary>
|
||||
/// Plugin valid but not signed with any certificate.
|
||||
/// </summary>
|
||||
Unsigned,
|
||||
/// <summary>
|
||||
/// Plugin valid but not signed with valid certificate.
|
||||
/// </summary>
|
||||
InvalidSignature,
|
||||
/// <summary>
|
||||
/// Plugin invalid and cannot be loaded. Possible version mismatch.
|
||||
/// </summary>
|
||||
InvalidPlugin,
|
||||
/// <summary>
|
||||
/// Plugin not a CLR library.
|
||||
/// </summary>
|
||||
InvalidLibrary,
|
||||
/// <summary>
|
||||
/// Plugin valid but executing assembly has no certificate to match against.
|
||||
/// </summary>
|
||||
NoCert,
|
||||
/// <summary>
|
||||
/// Plugin signature checks disabled.
|
||||
/// </summary>
|
||||
SigCheckDisabled
|
||||
}
|
||||
}
|
||||
}
|
325
ObservatoryCore/Properties/Core.Designer.cs
generated
325
ObservatoryCore/Properties/Core.Designer.cs
generated
@ -1,325 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Observatory.Properties {
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.8.0.0")]
|
||||
internal sealed partial class Core : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Core defaultInstance = ((Core)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Core())));
|
||||
|
||||
public static Core Default {
|
||||
get {
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string JournalFolder {
|
||||
get {
|
||||
return ((string)(this["JournalFolder"]));
|
||||
}
|
||||
set {
|
||||
this["JournalFolder"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
public bool AllowUnsigned {
|
||||
get {
|
||||
return ((bool)(this["AllowUnsigned"]));
|
||||
}
|
||||
set {
|
||||
this["AllowUnsigned"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("True")]
|
||||
public bool NativeNotify {
|
||||
get {
|
||||
return ((bool)(this["NativeNotify"]));
|
||||
}
|
||||
set {
|
||||
this["NativeNotify"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string NativeNotifyFont {
|
||||
get {
|
||||
return ((string)(this["NativeNotifyFont"]));
|
||||
}
|
||||
set {
|
||||
this["NativeNotifyFont"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("4294944000")]
|
||||
public uint NativeNotifyColour {
|
||||
get {
|
||||
return ((uint)(this["NativeNotifyColour"]));
|
||||
}
|
||||
set {
|
||||
this["NativeNotifyColour"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("0")]
|
||||
public int NativeNotifyCorner {
|
||||
get {
|
||||
return ((int)(this["NativeNotifyCorner"]));
|
||||
}
|
||||
set {
|
||||
this["NativeNotifyCorner"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("-1")]
|
||||
public int NativeNotifyScreen {
|
||||
get {
|
||||
return ((int)(this["NativeNotifyScreen"]));
|
||||
}
|
||||
set {
|
||||
this["NativeNotifyScreen"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
public bool TryPrimeSystemContextOnStartMonitor {
|
||||
get {
|
||||
return ((bool)(this["TryPrimeSystemContextOnStartMonitor"]));
|
||||
}
|
||||
set {
|
||||
this["TryPrimeSystemContextOnStartMonitor"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string CoreVersion {
|
||||
get {
|
||||
return ((string)(this["CoreVersion"]));
|
||||
}
|
||||
set {
|
||||
this["CoreVersion"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string PluginSettings {
|
||||
get {
|
||||
return ((string)(this["PluginSettings"]));
|
||||
}
|
||||
set {
|
||||
this["PluginSettings"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
public bool VoiceNotify {
|
||||
get {
|
||||
return ((bool)(this["VoiceNotify"]));
|
||||
}
|
||||
set {
|
||||
this["VoiceNotify"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string VoiceSelected {
|
||||
get {
|
||||
return ((string)(this["VoiceSelected"]));
|
||||
}
|
||||
set {
|
||||
this["VoiceSelected"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("75")]
|
||||
public int VoiceVolume {
|
||||
get {
|
||||
return ((int)(this["VoiceVolume"]));
|
||||
}
|
||||
set {
|
||||
this["VoiceVolume"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("0")]
|
||||
public int VoiceRate {
|
||||
get {
|
||||
return ((int)(this["VoiceRate"]));
|
||||
}
|
||||
set {
|
||||
this["VoiceRate"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("800, 500")]
|
||||
public global::System.Drawing.Size MainWindowSize {
|
||||
get {
|
||||
return ((global::System.Drawing.Size)(this["MainWindowSize"]));
|
||||
}
|
||||
set {
|
||||
this["MainWindowSize"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("100, 100")]
|
||||
public global::System.Drawing.Point MainWindowPosition {
|
||||
get {
|
||||
return ((global::System.Drawing.Point)(this["MainWindowPosition"]));
|
||||
}
|
||||
set {
|
||||
this["MainWindowPosition"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("100")]
|
||||
public int NativeNotifyScale {
|
||||
get {
|
||||
return ((int)(this["NativeNotifyScale"]));
|
||||
}
|
||||
set {
|
||||
this["NativeNotifyScale"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("8000")]
|
||||
public int NativeNotifyTimeout {
|
||||
get {
|
||||
return ((int)(this["NativeNotifyTimeout"]));
|
||||
}
|
||||
set {
|
||||
this["NativeNotifyTimeout"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
public bool StartMonitor {
|
||||
get {
|
||||
return ((bool)(this["StartMonitor"]));
|
||||
}
|
||||
set {
|
||||
this["StartMonitor"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string ExportFolder {
|
||||
get {
|
||||
return ((string)(this["ExportFolder"]));
|
||||
}
|
||||
set {
|
||||
this["ExportFolder"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("False")]
|
||||
public bool StartReadAll {
|
||||
get {
|
||||
return ((bool)(this["StartReadAll"]));
|
||||
}
|
||||
set {
|
||||
this["StartReadAll"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
public global::System.Collections.Specialized.StringCollection UnsignedAllowed {
|
||||
get {
|
||||
return ((global::System.Collections.Specialized.StringCollection)(this["UnsignedAllowed"]));
|
||||
}
|
||||
set {
|
||||
this["UnsignedAllowed"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("Dark")]
|
||||
public string Theme {
|
||||
get {
|
||||
return ((string)(this["Theme"]));
|
||||
}
|
||||
set {
|
||||
this["Theme"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string ColumnSizing {
|
||||
get {
|
||||
return ((string)(this["ColumnSizing"]));
|
||||
}
|
||||
set {
|
||||
this["ColumnSizing"] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.UserScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("")]
|
||||
public string PluginsEnabled {
|
||||
get {
|
||||
return ((string)(this["PluginsEnabled"]));
|
||||
}
|
||||
set {
|
||||
this["PluginsEnabled"] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="Observatory.Properties" GeneratedClassName="Core">
|
||||
<Profiles />
|
||||
<Settings>
|
||||
<Setting Name="JournalFolder" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="AllowUnsigned" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="NativeNotify" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">True</Value>
|
||||
</Setting>
|
||||
<Setting Name="NativeNotifyFont" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="NativeNotifyColour" Type="System.UInt32" Scope="User">
|
||||
<Value Profile="(Default)">4294944000</Value>
|
||||
</Setting>
|
||||
<Setting Name="NativeNotifyCorner" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">0</Value>
|
||||
</Setting>
|
||||
<Setting Name="NativeNotifyScreen" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">-1</Value>
|
||||
</Setting>
|
||||
<Setting Name="TryPrimeSystemContextOnStartMonitor" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="CoreVersion" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="PluginSettings" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="VoiceNotify" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="VoiceSelected" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="VoiceVolume" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">75</Value>
|
||||
</Setting>
|
||||
<Setting Name="VoiceRate" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">0</Value>
|
||||
</Setting>
|
||||
<Setting Name="MainWindowSize" Type="System.Drawing.Size" Scope="User">
|
||||
<Value Profile="(Default)">800, 500</Value>
|
||||
</Setting>
|
||||
<Setting Name="MainWindowPosition" Type="System.Drawing.Point" Scope="User">
|
||||
<Value Profile="(Default)">100, 100</Value>
|
||||
</Setting>
|
||||
<Setting Name="NativeNotifyScale" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">100</Value>
|
||||
</Setting>
|
||||
<Setting Name="NativeNotifyTimeout" Type="System.Int32" Scope="User">
|
||||
<Value Profile="(Default)">8000</Value>
|
||||
</Setting>
|
||||
<Setting Name="StartMonitor" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="ExportFolder" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="StartReadAll" Type="System.Boolean" Scope="User">
|
||||
<Value Profile="(Default)">False</Value>
|
||||
</Setting>
|
||||
<Setting Name="UnsignedAllowed" Type="System.Collections.Specialized.StringCollection" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="Theme" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)">Dark</Value>
|
||||
</Setting>
|
||||
<Setting Name="ColumnSizing" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
<Setting Name="PluginsEnabled" Type="System.String" Scope="User">
|
||||
<Value Profile="(Default)" />
|
||||
</Setting>
|
||||
</Settings>
|
||||
</SettingsFile>
|
63
ObservatoryCore/Properties/Resources.Designer.cs
generated
63
ObservatoryCore/Properties/Resources.Designer.cs
generated
@ -1,63 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Observatory.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Observatory.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
Binary file not shown.
Before Width: | Height: | Size: 44 KiB |
@ -1,27 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
public class ColumnSizing
|
||||
{
|
||||
public string PluginName { get; set; }
|
||||
public string PluginVersion { get; set; }
|
||||
public Dictionary<string, int> ColumnWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
_columnWidth ??= new Dictionary<string, int>();
|
||||
|
||||
return _columnWidth;
|
||||
}
|
||||
|
||||
set => _columnWidth = value;
|
||||
}
|
||||
|
||||
private Dictionary<string, int>? _columnWidth;
|
||||
}
|
||||
}
|
668
ObservatoryCore/UI/CoreForm.Designer.cs
generated
668
ObservatoryCore/UI/CoreForm.Designer.cs
generated
@ -1,668 +0,0 @@
|
||||
namespace Observatory.UI
|
||||
{
|
||||
partial class CoreForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
components = new System.ComponentModel.Container();
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(CoreForm));
|
||||
CoreMenu = new MenuStrip();
|
||||
coreToolStripMenuItem = new ToolStripMenuItem();
|
||||
toolStripMenuItem1 = new ToolStripMenuItem();
|
||||
CorePanel = new Panel();
|
||||
ThemeDropdown = new ComboBox();
|
||||
ThemeLabel = new Label();
|
||||
AudioLabel = new Label();
|
||||
PopupLabel = new Label();
|
||||
PluginSettingsButton = new Button();
|
||||
VoiceSettingsPanel = new Panel();
|
||||
VoiceSpeedSlider = new TrackBar();
|
||||
VoiceVolumeSlider = new TrackBar();
|
||||
VoiceTestButton = new Button();
|
||||
VoiceCheckbox = new CheckBox();
|
||||
VoiceDropdown = new ComboBox();
|
||||
VoiceLabel = new Label();
|
||||
VoiceSpeedLabel = new Label();
|
||||
VoiceVolumeLabel = new Label();
|
||||
PopupSettingsPanel = new Panel();
|
||||
DurationSpinner = new NumericUpDown();
|
||||
ScaleSpinner = new NumericUpDown();
|
||||
LabelColour = new Label();
|
||||
TestButton = new Button();
|
||||
ColourButton = new Button();
|
||||
PopupCheckbox = new CheckBox();
|
||||
LabelDuration = new Label();
|
||||
LabelScale = new Label();
|
||||
FontDropdown = new ComboBox();
|
||||
LabelFont = new Label();
|
||||
CornerDropdown = new ComboBox();
|
||||
DisplayDropdown = new ComboBox();
|
||||
CornerLabel = new Label();
|
||||
DisplayLabel = new Label();
|
||||
PluginFolderButton = new Button();
|
||||
PluginList = new ListView();
|
||||
NameColumn = new ColumnHeader();
|
||||
TypeColumn = new ColumnHeader();
|
||||
VersionColumn = new ColumnHeader();
|
||||
StatusColumn = new ColumnHeader();
|
||||
ReadAllButton = new Button();
|
||||
ToggleMonitorButton = new Button();
|
||||
ClearButton = new Button();
|
||||
ExportButton = new Button();
|
||||
GithubLink = new LinkLabel();
|
||||
DonateLink = new LinkLabel();
|
||||
PopupColour = new ColorDialog();
|
||||
OverrideTooltip = new ToolTip(components);
|
||||
CoreMenu.SuspendLayout();
|
||||
CorePanel.SuspendLayout();
|
||||
VoiceSettingsPanel.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)VoiceSpeedSlider).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)VoiceVolumeSlider).BeginInit();
|
||||
PopupSettingsPanel.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)DurationSpinner).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)ScaleSpinner).BeginInit();
|
||||
SuspendLayout();
|
||||
//
|
||||
// CoreMenu
|
||||
//
|
||||
CoreMenu.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left;
|
||||
CoreMenu.AutoSize = false;
|
||||
CoreMenu.Dock = DockStyle.None;
|
||||
CoreMenu.Items.AddRange(new ToolStripItem[] { coreToolStripMenuItem, toolStripMenuItem1 });
|
||||
CoreMenu.LayoutStyle = ToolStripLayoutStyle.VerticalStackWithOverflow;
|
||||
CoreMenu.Location = new Point(0, 0);
|
||||
CoreMenu.Name = "CoreMenu";
|
||||
CoreMenu.Size = new Size(120, 691);
|
||||
CoreMenu.TabIndex = 0;
|
||||
//
|
||||
// coreToolStripMenuItem
|
||||
//
|
||||
coreToolStripMenuItem.Font = new Font("Segoe UI", 18F, FontStyle.Regular, GraphicsUnit.Point);
|
||||
coreToolStripMenuItem.Name = "coreToolStripMenuItem";
|
||||
coreToolStripMenuItem.Size = new Size(113, 36);
|
||||
coreToolStripMenuItem.Text = "Core";
|
||||
coreToolStripMenuItem.TextAlign = ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// toolStripMenuItem1
|
||||
//
|
||||
toolStripMenuItem1.Alignment = ToolStripItemAlignment.Right;
|
||||
toolStripMenuItem1.Font = new Font("Segoe UI", 18F, FontStyle.Regular, GraphicsUnit.Point);
|
||||
toolStripMenuItem1.Name = "toolStripMenuItem1";
|
||||
toolStripMenuItem1.Size = new Size(113, 36);
|
||||
toolStripMenuItem1.Text = "<";
|
||||
toolStripMenuItem1.TextAlign = ContentAlignment.MiddleLeft;
|
||||
//
|
||||
// CorePanel
|
||||
//
|
||||
CorePanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
CorePanel.AutoScroll = true;
|
||||
CorePanel.Controls.Add(ThemeDropdown);
|
||||
CorePanel.Controls.Add(ThemeLabel);
|
||||
CorePanel.Controls.Add(AudioLabel);
|
||||
CorePanel.Controls.Add(PopupLabel);
|
||||
CorePanel.Controls.Add(PluginSettingsButton);
|
||||
CorePanel.Controls.Add(VoiceSettingsPanel);
|
||||
CorePanel.Controls.Add(PopupSettingsPanel);
|
||||
CorePanel.Controls.Add(PluginFolderButton);
|
||||
CorePanel.Controls.Add(PluginList);
|
||||
CorePanel.Location = new Point(123, 12);
|
||||
CorePanel.Name = "CorePanel";
|
||||
CorePanel.Size = new Size(665, 679);
|
||||
CorePanel.TabIndex = 1;
|
||||
//
|
||||
// ThemeDropdown
|
||||
//
|
||||
ThemeDropdown.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||
ThemeDropdown.FormattingEnabled = true;
|
||||
ThemeDropdown.Location = new Point(124, 620);
|
||||
ThemeDropdown.Name = "ThemeDropdown";
|
||||
ThemeDropdown.Size = new Size(121, 23);
|
||||
ThemeDropdown.TabIndex = 10;
|
||||
ThemeDropdown.SelectedIndexChanged += ThemeDropdown_SelectedIndexChanged;
|
||||
//
|
||||
// ThemeLabel
|
||||
//
|
||||
ThemeLabel.AutoSize = true;
|
||||
ThemeLabel.Location = new Point(72, 623);
|
||||
ThemeLabel.Name = "ThemeLabel";
|
||||
ThemeLabel.Size = new Size(46, 15);
|
||||
ThemeLabel.TabIndex = 9;
|
||||
ThemeLabel.Text = "Theme:";
|
||||
ThemeLabel.TextAlign = ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// AudioLabel
|
||||
//
|
||||
AudioLabel.AutoSize = true;
|
||||
AudioLabel.Location = new Point(5, 435);
|
||||
AudioLabel.Name = "AudioLabel";
|
||||
AudioLabel.Size = new Size(106, 15);
|
||||
AudioLabel.TabIndex = 8;
|
||||
AudioLabel.Text = "Voice Notifications";
|
||||
//
|
||||
// PopupLabel
|
||||
//
|
||||
PopupLabel.AutoSize = true;
|
||||
PopupLabel.Location = new Point(5, 218);
|
||||
PopupLabel.Name = "PopupLabel";
|
||||
PopupLabel.Size = new Size(113, 15);
|
||||
PopupLabel.TabIndex = 7;
|
||||
PopupLabel.Text = "Popup Notifications";
|
||||
//
|
||||
// PluginSettingsButton
|
||||
//
|
||||
PluginSettingsButton.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||
PluginSettingsButton.FlatAppearance.BorderSize = 0;
|
||||
PluginSettingsButton.FlatStyle = FlatStyle.Flat;
|
||||
PluginSettingsButton.Location = new Point(396, 193);
|
||||
PluginSettingsButton.Name = "PluginSettingsButton";
|
||||
PluginSettingsButton.Size = new Size(130, 23);
|
||||
PluginSettingsButton.TabIndex = 6;
|
||||
PluginSettingsButton.Text = "Plugin Settings";
|
||||
PluginSettingsButton.UseVisualStyleBackColor = false;
|
||||
PluginSettingsButton.Click += PluginSettingsButton_Click;
|
||||
//
|
||||
// VoiceSettingsPanel
|
||||
//
|
||||
VoiceSettingsPanel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||
VoiceSettingsPanel.BorderStyle = BorderStyle.FixedSingle;
|
||||
VoiceSettingsPanel.Controls.Add(VoiceSpeedSlider);
|
||||
VoiceSettingsPanel.Controls.Add(VoiceVolumeSlider);
|
||||
VoiceSettingsPanel.Controls.Add(VoiceTestButton);
|
||||
VoiceSettingsPanel.Controls.Add(VoiceCheckbox);
|
||||
VoiceSettingsPanel.Controls.Add(VoiceDropdown);
|
||||
VoiceSettingsPanel.Controls.Add(VoiceLabel);
|
||||
VoiceSettingsPanel.Controls.Add(VoiceSpeedLabel);
|
||||
VoiceSettingsPanel.Controls.Add(VoiceVolumeLabel);
|
||||
VoiceSettingsPanel.Location = new Point(3, 444);
|
||||
VoiceSettingsPanel.Name = "VoiceSettingsPanel";
|
||||
VoiceSettingsPanel.Size = new Size(659, 170);
|
||||
VoiceSettingsPanel.TabIndex = 5;
|
||||
//
|
||||
// VoiceSpeedSlider
|
||||
//
|
||||
VoiceSpeedSlider.Location = new Point(121, 51);
|
||||
VoiceSpeedSlider.Maximum = 100;
|
||||
VoiceSpeedSlider.Name = "VoiceSpeedSlider";
|
||||
VoiceSpeedSlider.Size = new Size(120, 45);
|
||||
VoiceSpeedSlider.TabIndex = 15;
|
||||
VoiceSpeedSlider.TickFrequency = 10;
|
||||
VoiceSpeedSlider.TickStyle = TickStyle.Both;
|
||||
VoiceSpeedSlider.Value = 50;
|
||||
VoiceSpeedSlider.Scroll += VoiceSpeedSlider_Scroll;
|
||||
//
|
||||
// VoiceVolumeSlider
|
||||
//
|
||||
VoiceVolumeSlider.LargeChange = 10;
|
||||
VoiceVolumeSlider.Location = new Point(120, 0);
|
||||
VoiceVolumeSlider.Maximum = 100;
|
||||
VoiceVolumeSlider.Name = "VoiceVolumeSlider";
|
||||
VoiceVolumeSlider.Size = new Size(121, 45);
|
||||
VoiceVolumeSlider.TabIndex = 14;
|
||||
VoiceVolumeSlider.TickFrequency = 10;
|
||||
VoiceVolumeSlider.TickStyle = TickStyle.Both;
|
||||
VoiceVolumeSlider.Value = 100;
|
||||
VoiceVolumeSlider.Scroll += VoiceVolumeSlider_Scroll;
|
||||
//
|
||||
// VoiceTestButton
|
||||
//
|
||||
VoiceTestButton.FlatStyle = FlatStyle.Flat;
|
||||
VoiceTestButton.Location = new Point(190, 131);
|
||||
VoiceTestButton.Name = "VoiceTestButton";
|
||||
VoiceTestButton.Size = new Size(51, 23);
|
||||
VoiceTestButton.TabIndex = 13;
|
||||
VoiceTestButton.Text = "Test";
|
||||
VoiceTestButton.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// VoiceCheckbox
|
||||
//
|
||||
VoiceCheckbox.AutoSize = true;
|
||||
VoiceCheckbox.Location = new Point(120, 134);
|
||||
VoiceCheckbox.Name = "VoiceCheckbox";
|
||||
VoiceCheckbox.Size = new Size(68, 19);
|
||||
VoiceCheckbox.TabIndex = 11;
|
||||
VoiceCheckbox.Text = "Enabled";
|
||||
VoiceCheckbox.UseVisualStyleBackColor = true;
|
||||
VoiceCheckbox.CheckedChanged += VoiceCheckbox_CheckedChanged;
|
||||
//
|
||||
// VoiceDropdown
|
||||
//
|
||||
VoiceDropdown.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||
VoiceDropdown.FormattingEnabled = true;
|
||||
VoiceDropdown.Location = new Point(121, 102);
|
||||
VoiceDropdown.Name = "VoiceDropdown";
|
||||
VoiceDropdown.Size = new Size(121, 23);
|
||||
VoiceDropdown.TabIndex = 5;
|
||||
VoiceDropdown.SelectedIndexChanged += VoiceDropdown_SelectedIndexChanged;
|
||||
//
|
||||
// VoiceLabel
|
||||
//
|
||||
VoiceLabel.AutoSize = true;
|
||||
VoiceLabel.Location = new Point(77, 105);
|
||||
VoiceLabel.Name = "VoiceLabel";
|
||||
VoiceLabel.Size = new Size(38, 15);
|
||||
VoiceLabel.TabIndex = 4;
|
||||
VoiceLabel.Text = "Voice:";
|
||||
VoiceLabel.TextAlign = ContentAlignment.MiddleRight;
|
||||
//
|
||||
// VoiceSpeedLabel
|
||||
//
|
||||
VoiceSpeedLabel.AutoSize = true;
|
||||
VoiceSpeedLabel.Location = new Point(73, 63);
|
||||
VoiceSpeedLabel.Name = "VoiceSpeedLabel";
|
||||
VoiceSpeedLabel.Size = new Size(42, 15);
|
||||
VoiceSpeedLabel.TabIndex = 1;
|
||||
VoiceSpeedLabel.Text = "Speed:";
|
||||
VoiceSpeedLabel.TextAlign = ContentAlignment.MiddleRight;
|
||||
//
|
||||
// VoiceVolumeLabel
|
||||
//
|
||||
VoiceVolumeLabel.AutoSize = true;
|
||||
VoiceVolumeLabel.Location = new Point(64, 12);
|
||||
VoiceVolumeLabel.Name = "VoiceVolumeLabel";
|
||||
VoiceVolumeLabel.Size = new Size(50, 15);
|
||||
VoiceVolumeLabel.TabIndex = 0;
|
||||
VoiceVolumeLabel.Text = "Volume:";
|
||||
VoiceVolumeLabel.TextAlign = ContentAlignment.MiddleRight;
|
||||
//
|
||||
// PopupSettingsPanel
|
||||
//
|
||||
PopupSettingsPanel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||
PopupSettingsPanel.BorderStyle = BorderStyle.FixedSingle;
|
||||
PopupSettingsPanel.Controls.Add(DurationSpinner);
|
||||
PopupSettingsPanel.Controls.Add(ScaleSpinner);
|
||||
PopupSettingsPanel.Controls.Add(LabelColour);
|
||||
PopupSettingsPanel.Controls.Add(TestButton);
|
||||
PopupSettingsPanel.Controls.Add(ColourButton);
|
||||
PopupSettingsPanel.Controls.Add(PopupCheckbox);
|
||||
PopupSettingsPanel.Controls.Add(LabelDuration);
|
||||
PopupSettingsPanel.Controls.Add(LabelScale);
|
||||
PopupSettingsPanel.Controls.Add(FontDropdown);
|
||||
PopupSettingsPanel.Controls.Add(LabelFont);
|
||||
PopupSettingsPanel.Controls.Add(CornerDropdown);
|
||||
PopupSettingsPanel.Controls.Add(DisplayDropdown);
|
||||
PopupSettingsPanel.Controls.Add(CornerLabel);
|
||||
PopupSettingsPanel.Controls.Add(DisplayLabel);
|
||||
PopupSettingsPanel.Location = new Point(3, 227);
|
||||
PopupSettingsPanel.Name = "PopupSettingsPanel";
|
||||
PopupSettingsPanel.Size = new Size(659, 207);
|
||||
PopupSettingsPanel.TabIndex = 3;
|
||||
//
|
||||
// DurationSpinner
|
||||
//
|
||||
DurationSpinner.Increment = new decimal(new int[] { 25, 0, 0, 0 });
|
||||
DurationSpinner.Location = new Point(121, 123);
|
||||
DurationSpinner.Maximum = new decimal(new int[] { 60000, 0, 0, 0 });
|
||||
DurationSpinner.Minimum = new decimal(new int[] { 100, 0, 0, 0 });
|
||||
DurationSpinner.Name = "DurationSpinner";
|
||||
DurationSpinner.Size = new Size(120, 23);
|
||||
DurationSpinner.TabIndex = 15;
|
||||
DurationSpinner.Value = new decimal(new int[] { 8000, 0, 0, 0 });
|
||||
DurationSpinner.ValueChanged += DurationSpinner_ValueChanged;
|
||||
//
|
||||
// ScaleSpinner
|
||||
//
|
||||
ScaleSpinner.Location = new Point(121, 94);
|
||||
ScaleSpinner.Maximum = new decimal(new int[] { 500, 0, 0, 0 });
|
||||
ScaleSpinner.Minimum = new decimal(new int[] { 1, 0, 0, 0 });
|
||||
ScaleSpinner.Name = "ScaleSpinner";
|
||||
ScaleSpinner.Size = new Size(120, 23);
|
||||
ScaleSpinner.TabIndex = 14;
|
||||
ScaleSpinner.Value = new decimal(new int[] { 100, 0, 0, 0 });
|
||||
ScaleSpinner.ValueChanged += ScaleSpinner_ValueChanged;
|
||||
//
|
||||
// LabelColour
|
||||
//
|
||||
LabelColour.AutoSize = true;
|
||||
LabelColour.Location = new Point(68, 156);
|
||||
LabelColour.Name = "LabelColour";
|
||||
LabelColour.Size = new Size(46, 15);
|
||||
LabelColour.TabIndex = 13;
|
||||
LabelColour.Text = "Colour:";
|
||||
LabelColour.TextAlign = ContentAlignment.MiddleRight;
|
||||
//
|
||||
// TestButton
|
||||
//
|
||||
TestButton.FlatStyle = FlatStyle.Flat;
|
||||
TestButton.Location = new Point(190, 152);
|
||||
TestButton.Name = "TestButton";
|
||||
TestButton.Size = new Size(51, 23);
|
||||
TestButton.TabIndex = 12;
|
||||
TestButton.Text = "Test";
|
||||
TestButton.UseVisualStyleBackColor = false;
|
||||
TestButton.Click += TestButton_Click;
|
||||
//
|
||||
// ColourButton
|
||||
//
|
||||
ColourButton.FlatStyle = FlatStyle.Flat;
|
||||
ColourButton.Location = new Point(121, 152);
|
||||
ColourButton.Name = "ColourButton";
|
||||
ColourButton.Size = new Size(51, 23);
|
||||
ColourButton.TabIndex = 11;
|
||||
ColourButton.UseVisualStyleBackColor = true;
|
||||
ColourButton.Click += ColourButton_Click;
|
||||
//
|
||||
// PopupCheckbox
|
||||
//
|
||||
PopupCheckbox.AutoSize = true;
|
||||
PopupCheckbox.Location = new Point(120, 181);
|
||||
PopupCheckbox.Name = "PopupCheckbox";
|
||||
PopupCheckbox.Size = new Size(68, 19);
|
||||
PopupCheckbox.TabIndex = 10;
|
||||
PopupCheckbox.Text = "Enabled";
|
||||
PopupCheckbox.UseVisualStyleBackColor = true;
|
||||
PopupCheckbox.CheckedChanged += PopupCheckbox_CheckedChanged;
|
||||
//
|
||||
// LabelDuration
|
||||
//
|
||||
LabelDuration.AutoSize = true;
|
||||
LabelDuration.Location = new Point(32, 125);
|
||||
LabelDuration.Name = "LabelDuration";
|
||||
LabelDuration.Size = new Size(83, 15);
|
||||
LabelDuration.TabIndex = 9;
|
||||
LabelDuration.Text = "Duration (ms):";
|
||||
LabelDuration.TextAlign = ContentAlignment.MiddleRight;
|
||||
//
|
||||
// LabelScale
|
||||
//
|
||||
LabelScale.AutoSize = true;
|
||||
LabelScale.Location = new Point(57, 96);
|
||||
LabelScale.Name = "LabelScale";
|
||||
LabelScale.Size = new Size(58, 15);
|
||||
LabelScale.TabIndex = 7;
|
||||
LabelScale.Text = "Scale (%):";
|
||||
LabelScale.TextAlign = ContentAlignment.MiddleRight;
|
||||
//
|
||||
// FontDropdown
|
||||
//
|
||||
FontDropdown.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||
FontDropdown.FormattingEnabled = true;
|
||||
FontDropdown.Location = new Point(120, 65);
|
||||
FontDropdown.Name = "FontDropdown";
|
||||
FontDropdown.Size = new Size(242, 23);
|
||||
FontDropdown.TabIndex = 5;
|
||||
FontDropdown.SelectedIndexChanged += FontDropdown_SelectedIndexChanged;
|
||||
//
|
||||
// LabelFont
|
||||
//
|
||||
LabelFont.AutoSize = true;
|
||||
LabelFont.Location = new Point(80, 68);
|
||||
LabelFont.Name = "LabelFont";
|
||||
LabelFont.Size = new Size(34, 15);
|
||||
LabelFont.TabIndex = 4;
|
||||
LabelFont.Text = "Font:";
|
||||
LabelFont.TextAlign = ContentAlignment.MiddleRight;
|
||||
//
|
||||
// CornerDropdown
|
||||
//
|
||||
CornerDropdown.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||
CornerDropdown.FormattingEnabled = true;
|
||||
CornerDropdown.Items.AddRange(new object[] { "Bottom-Right", "Bottom-Left", "Top-Right", "Top-Left" });
|
||||
CornerDropdown.Location = new Point(120, 36);
|
||||
CornerDropdown.Name = "CornerDropdown";
|
||||
CornerDropdown.Size = new Size(121, 23);
|
||||
CornerDropdown.TabIndex = 3;
|
||||
CornerDropdown.SelectedIndexChanged += CornerDropdown_SelectedIndexChanged;
|
||||
//
|
||||
// DisplayDropdown
|
||||
//
|
||||
DisplayDropdown.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||
DisplayDropdown.FormattingEnabled = true;
|
||||
DisplayDropdown.Location = new Point(120, 7);
|
||||
DisplayDropdown.Name = "DisplayDropdown";
|
||||
DisplayDropdown.Size = new Size(121, 23);
|
||||
DisplayDropdown.TabIndex = 2;
|
||||
DisplayDropdown.SelectedIndexChanged += DisplayDropdown_SelectedIndexChanged;
|
||||
//
|
||||
// CornerLabel
|
||||
//
|
||||
CornerLabel.AutoSize = true;
|
||||
CornerLabel.Location = new Point(68, 39);
|
||||
CornerLabel.Name = "CornerLabel";
|
||||
CornerLabel.Size = new Size(46, 15);
|
||||
CornerLabel.TabIndex = 1;
|
||||
CornerLabel.Text = "Corner:";
|
||||
CornerLabel.TextAlign = ContentAlignment.MiddleRight;
|
||||
//
|
||||
// DisplayLabel
|
||||
//
|
||||
DisplayLabel.AutoSize = true;
|
||||
DisplayLabel.Location = new Point(66, 10);
|
||||
DisplayLabel.Name = "DisplayLabel";
|
||||
DisplayLabel.Size = new Size(48, 15);
|
||||
DisplayLabel.TabIndex = 0;
|
||||
DisplayLabel.Text = "Display:";
|
||||
DisplayLabel.TextAlign = ContentAlignment.MiddleRight;
|
||||
//
|
||||
// PluginFolderButton
|
||||
//
|
||||
PluginFolderButton.Anchor = AnchorStyles.Top | AnchorStyles.Right;
|
||||
PluginFolderButton.FlatAppearance.BorderSize = 0;
|
||||
PluginFolderButton.FlatStyle = FlatStyle.Flat;
|
||||
PluginFolderButton.Location = new Point(532, 193);
|
||||
PluginFolderButton.Name = "PluginFolderButton";
|
||||
PluginFolderButton.Size = new Size(130, 23);
|
||||
PluginFolderButton.TabIndex = 1;
|
||||
PluginFolderButton.Text = "Open Plugin Folder";
|
||||
PluginFolderButton.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// PluginList
|
||||
//
|
||||
PluginList.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||
PluginList.BorderStyle = BorderStyle.None;
|
||||
PluginList.CheckBoxes = true;
|
||||
PluginList.Columns.AddRange(new ColumnHeader[] { NameColumn, TypeColumn, VersionColumn, StatusColumn });
|
||||
PluginList.FullRowSelect = true;
|
||||
PluginList.HeaderStyle = ColumnHeaderStyle.Nonclickable;
|
||||
PluginList.ImeMode = ImeMode.NoControl;
|
||||
PluginList.Location = new Point(3, 3);
|
||||
PluginList.MultiSelect = false;
|
||||
PluginList.Name = "PluginList";
|
||||
PluginList.OwnerDraw = true;
|
||||
PluginList.Size = new Size(659, 184);
|
||||
PluginList.TabIndex = 0;
|
||||
PluginList.UseCompatibleStateImageBehavior = false;
|
||||
PluginList.View = View.Details;
|
||||
PluginList.ItemChecked += PluginList_ItemChecked;
|
||||
PluginList.Resize += PluginList_Resize;
|
||||
//
|
||||
// NameColumn
|
||||
//
|
||||
NameColumn.Text = "Plugin";
|
||||
NameColumn.Width = 180;
|
||||
//
|
||||
// TypeColumn
|
||||
//
|
||||
TypeColumn.Text = "Type";
|
||||
TypeColumn.Width = 120;
|
||||
//
|
||||
// VersionColumn
|
||||
//
|
||||
VersionColumn.Text = "Version";
|
||||
VersionColumn.Width = 120;
|
||||
//
|
||||
// StatusColumn
|
||||
//
|
||||
StatusColumn.Text = "Status";
|
||||
//
|
||||
// ReadAllButton
|
||||
//
|
||||
ReadAllButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
ReadAllButton.FlatAppearance.BorderSize = 0;
|
||||
ReadAllButton.FlatStyle = FlatStyle.Flat;
|
||||
ReadAllButton.Location = new Point(713, 698);
|
||||
ReadAllButton.Name = "ReadAllButton";
|
||||
ReadAllButton.Size = new Size(75, 23);
|
||||
ReadAllButton.TabIndex = 2;
|
||||
ReadAllButton.Text = "Read All";
|
||||
ReadAllButton.UseVisualStyleBackColor = false;
|
||||
ReadAllButton.Click += ReadAllButton_Click;
|
||||
//
|
||||
// ToggleMonitorButton
|
||||
//
|
||||
ToggleMonitorButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
ToggleMonitorButton.FlatAppearance.BorderSize = 0;
|
||||
ToggleMonitorButton.FlatStyle = FlatStyle.Flat;
|
||||
ToggleMonitorButton.Location = new Point(610, 698);
|
||||
ToggleMonitorButton.Name = "ToggleMonitorButton";
|
||||
ToggleMonitorButton.Size = new Size(97, 23);
|
||||
ToggleMonitorButton.TabIndex = 3;
|
||||
ToggleMonitorButton.Text = "Start Monitor";
|
||||
ToggleMonitorButton.UseVisualStyleBackColor = false;
|
||||
ToggleMonitorButton.Click += ToggleMonitorButton_Click;
|
||||
//
|
||||
// ClearButton
|
||||
//
|
||||
ClearButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
ClearButton.FlatAppearance.BorderSize = 0;
|
||||
ClearButton.FlatStyle = FlatStyle.Flat;
|
||||
ClearButton.Location = new Point(529, 698);
|
||||
ClearButton.Name = "ClearButton";
|
||||
ClearButton.Size = new Size(75, 23);
|
||||
ClearButton.TabIndex = 4;
|
||||
ClearButton.Text = "Clear";
|
||||
ClearButton.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// ExportButton
|
||||
//
|
||||
ExportButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
ExportButton.FlatAppearance.BorderSize = 0;
|
||||
ExportButton.FlatStyle = FlatStyle.Flat;
|
||||
ExportButton.Location = new Point(448, 698);
|
||||
ExportButton.Name = "ExportButton";
|
||||
ExportButton.Size = new Size(75, 23);
|
||||
ExportButton.TabIndex = 5;
|
||||
ExportButton.Text = "Export";
|
||||
ExportButton.UseVisualStyleBackColor = false;
|
||||
//
|
||||
// GithubLink
|
||||
//
|
||||
GithubLink.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
|
||||
GithubLink.AutoSize = true;
|
||||
GithubLink.Location = new Point(12, 694);
|
||||
GithubLink.Name = "GithubLink";
|
||||
GithubLink.Size = new Size(42, 15);
|
||||
GithubLink.TabIndex = 6;
|
||||
GithubLink.TabStop = true;
|
||||
GithubLink.Text = "github";
|
||||
GithubLink.LinkClicked += GithubLink_LinkClicked;
|
||||
//
|
||||
// DonateLink
|
||||
//
|
||||
DonateLink.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
|
||||
DonateLink.AutoSize = true;
|
||||
DonateLink.Location = new Point(12, 709);
|
||||
DonateLink.Name = "DonateLink";
|
||||
DonateLink.Size = new Size(45, 15);
|
||||
DonateLink.TabIndex = 7;
|
||||
DonateLink.TabStop = true;
|
||||
DonateLink.Text = "Donate";
|
||||
DonateLink.LinkClicked += DonateLink_LinkClicked;
|
||||
//
|
||||
// CoreForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(800, 733);
|
||||
Controls.Add(DonateLink);
|
||||
Controls.Add(GithubLink);
|
||||
Controls.Add(ExportButton);
|
||||
Controls.Add(ClearButton);
|
||||
Controls.Add(ToggleMonitorButton);
|
||||
Controls.Add(ReadAllButton);
|
||||
Controls.Add(CorePanel);
|
||||
Controls.Add(CoreMenu);
|
||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||
MainMenuStrip = CoreMenu;
|
||||
Name = "CoreForm";
|
||||
Text = "Elite Observatory Core";
|
||||
CoreMenu.ResumeLayout(false);
|
||||
CoreMenu.PerformLayout();
|
||||
CorePanel.ResumeLayout(false);
|
||||
CorePanel.PerformLayout();
|
||||
VoiceSettingsPanel.ResumeLayout(false);
|
||||
VoiceSettingsPanel.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)VoiceSpeedSlider).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)VoiceVolumeSlider).EndInit();
|
||||
PopupSettingsPanel.ResumeLayout(false);
|
||||
PopupSettingsPanel.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)DurationSpinner).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)ScaleSpinner).EndInit();
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private MenuStrip CoreMenu;
|
||||
private ToolStripMenuItem coreToolStripMenuItem;
|
||||
private Panel CorePanel;
|
||||
private Button ReadAllButton;
|
||||
private Button ToggleMonitorButton;
|
||||
private Button ClearButton;
|
||||
private Button ExportButton;
|
||||
private LinkLabel GithubLink;
|
||||
private LinkLabel DonateLink;
|
||||
private ListView PluginList;
|
||||
private ColumnHeader NameColumn;
|
||||
private ColumnHeader TypeColumn;
|
||||
private ColumnHeader VersionColumn;
|
||||
private ColumnHeader StatusColumn;
|
||||
private Button PluginFolderButton;
|
||||
private Panel PopupSettingsPanel;
|
||||
private ComboBox CornerDropdown;
|
||||
private ComboBox DisplayDropdown;
|
||||
private Label CornerLabel;
|
||||
private Label DisplayLabel;
|
||||
private NumericUpDown DurationSpinner;
|
||||
private NumericUpDown ScaleSpinner;
|
||||
private Label LabelColour;
|
||||
private Button TestButton;
|
||||
private Button ColourButton;
|
||||
private CheckBox PopupCheckbox;
|
||||
private Label LabelDuration;
|
||||
private Label LabelScale;
|
||||
private ComboBox FontDropdown;
|
||||
private Label LabelFont;
|
||||
private ColorDialog PopupColour;
|
||||
private ToolStripMenuItem toolStripMenuItem1;
|
||||
private Panel VoiceSettingsPanel;
|
||||
private TrackBar VoiceSpeedSlider;
|
||||
private TrackBar VoiceVolumeSlider;
|
||||
private Button VoiceTestButton;
|
||||
private CheckBox VoiceCheckbox;
|
||||
private ComboBox VoiceDropdown;
|
||||
private Label VoiceLabel;
|
||||
private Label VoiceSpeedLabel;
|
||||
private Label VoiceVolumeLabel;
|
||||
private Button PluginSettingsButton;
|
||||
private ToolTip OverrideTooltip;
|
||||
private Label AudioLabel;
|
||||
private Label PopupLabel;
|
||||
private Label ThemeLabel;
|
||||
private ComboBox ThemeDropdown;
|
||||
}
|
||||
}
|
@ -1,221 +0,0 @@
|
||||
using Observatory.PluginManagement;
|
||||
using Observatory.Framework.Interfaces;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
partial class CoreForm
|
||||
{
|
||||
private Dictionary<ListViewItem, IObservatoryPlugin>? ListedPlugins;
|
||||
private bool loading = true; // Suppress settings updates due to initializing the listview.
|
||||
|
||||
private void PopulatePluginList()
|
||||
{
|
||||
ListedPlugins = new();
|
||||
|
||||
foreach (var (plugin, signed) in PluginManager.GetInstance.workerPlugins)
|
||||
{
|
||||
if (!ListedPlugins.ContainsValue(plugin))
|
||||
{
|
||||
|
||||
ListViewItem item = new ListViewItem(new[] { plugin.Name, "Worker", plugin.Version, PluginStatusString(signed) });
|
||||
ListedPlugins.Add(item, plugin);
|
||||
var lvItem = PluginList.Items.Add(item);
|
||||
lvItem.Checked = true; // Start with enabled, let settings disable things.
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (plugin, signed) in PluginManager.GetInstance.notifyPlugins)
|
||||
{
|
||||
if (!ListedPlugins.ContainsValue(plugin))
|
||||
{
|
||||
ListViewItem item = new ListViewItem(new[] { plugin.Name, "Notifier", plugin.Version, PluginStatusString(signed) });
|
||||
ListedPlugins.Add(item, plugin);
|
||||
var lvItem = PluginList.Items.Add(item);
|
||||
lvItem.Checked = true; // Start with enabled, let settings disable things.
|
||||
}
|
||||
}
|
||||
|
||||
PluginsEnabledStateFromSettings();
|
||||
|
||||
PluginList.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
|
||||
PluginList.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
|
||||
|
||||
loading = false;
|
||||
}
|
||||
|
||||
private static string PluginStatusString(PluginManager.PluginStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case PluginManager.PluginStatus.Signed:
|
||||
return "Signed";
|
||||
|
||||
case PluginManager.PluginStatus.Unsigned:
|
||||
return "Unsigned";
|
||||
|
||||
case PluginManager.PluginStatus.InvalidSignature:
|
||||
return "Invalid Signature";
|
||||
|
||||
case PluginManager.PluginStatus.InvalidPlugin:
|
||||
return "Invalid Plugin";
|
||||
|
||||
case PluginManager.PluginStatus.InvalidLibrary:
|
||||
return "Invalid File";
|
||||
|
||||
case PluginManager.PluginStatus.NoCert:
|
||||
return "Unsigned Observatory (Debug build)";
|
||||
|
||||
case PluginManager.PluginStatus.SigCheckDisabled:
|
||||
return "Signature Checks Disabled";
|
||||
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreatePluginTabs()
|
||||
{
|
||||
var uiPlugins = PluginManager.GetInstance.workerPlugins.Where(p => p.plugin.PluginUI.PluginUIType != Framework.PluginUI.UIType.None);
|
||||
|
||||
PluginHelper.CreatePluginTabs(CoreMenu, uiPlugins, uiPanels);
|
||||
|
||||
foreach(ToolStripMenuItem item in CoreMenu.Items)
|
||||
{
|
||||
pluginList.Add(item.Text, item);
|
||||
}
|
||||
|
||||
CoreMenu.Width = GetExpandedMenuWidth();
|
||||
}
|
||||
|
||||
private void DisableOverriddenNotification()
|
||||
{
|
||||
var notifyPlugins = PluginManager.GetInstance.notifyPlugins;
|
||||
|
||||
var ovPopupPlugins = notifyPlugins.Where(n => n.plugin.OverridePopupNotifications);
|
||||
|
||||
if (ovPopupPlugins.Any())
|
||||
{
|
||||
PopupCheckbox.Checked = false;
|
||||
PopupCheckbox.Enabled = false;
|
||||
DisplayDropdown.Enabled = false;
|
||||
CornerDropdown.Enabled = false;
|
||||
FontDropdown.Enabled = false;
|
||||
ScaleSpinner.Enabled = false;
|
||||
DurationSpinner.Enabled = false;
|
||||
ColourButton.Enabled = false;
|
||||
TestButton.Enabled = false;
|
||||
|
||||
var pluginNames = string.Join(", ", ovPopupPlugins.Select(o => o.plugin.ShortName));
|
||||
|
||||
PopupSettingsPanel.MouseMove += (_, _) =>
|
||||
{
|
||||
OverrideTooltip.SetToolTip(PopupSettingsPanel, "Disabled by plugin: " + pluginNames);
|
||||
};
|
||||
}
|
||||
|
||||
var ovAudioPlugins = notifyPlugins.Where(n => n.plugin.OverrideAudioNotifications);
|
||||
|
||||
if (ovAudioPlugins.Any())
|
||||
{
|
||||
VoiceCheckbox.Checked = false;
|
||||
VoiceCheckbox.Enabled = false;
|
||||
VoiceVolumeSlider.Enabled = false;
|
||||
VoiceSpeedSlider.Enabled = false;
|
||||
VoiceDropdown.Enabled = false;
|
||||
VoiceTestButton.Enabled = false;
|
||||
|
||||
var pluginNames = string.Join(", ", ovAudioPlugins.Select(o => o.plugin.ShortName));
|
||||
|
||||
VoiceSettingsPanel.MouseMove += (_, _) =>
|
||||
{
|
||||
OverrideTooltip.SetToolTip(VoiceSettingsPanel, "Disabled by plugin: " + pluginNames);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private int GetExpandedMenuWidth()
|
||||
{
|
||||
int maxWidth = 0;
|
||||
foreach (ToolStripMenuItem item in CoreMenu.Items)
|
||||
{
|
||||
var itemWidth = TextRenderer.MeasureText(item.Text, item.Font);
|
||||
maxWidth = itemWidth.Width > maxWidth ? itemWidth.Width : maxWidth;
|
||||
}
|
||||
|
||||
return maxWidth + 25;
|
||||
}
|
||||
|
||||
private void PluginSettingsButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (ListedPlugins != null && PluginList.SelectedItems.Count != 0)
|
||||
{
|
||||
var plugin = ListedPlugins[PluginList.SelectedItems[0]];
|
||||
if (SettingsForms.ContainsKey(plugin))
|
||||
{
|
||||
SettingsForms[plugin].Activate();
|
||||
}
|
||||
else
|
||||
{
|
||||
SettingsForm settingsForm = new(plugin);
|
||||
SettingsForms.Add(plugin, settingsForm);
|
||||
settingsForm.FormClosed += (_, _) => SettingsForms.Remove(plugin);
|
||||
settingsForm.Show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PluginsEnabledStateFromSettings()
|
||||
{
|
||||
if (ListedPlugins == null) return;
|
||||
|
||||
string pluginsEnabledStr = Properties.Core.Default.PluginsEnabled;
|
||||
Dictionary<string, bool>? pluginsEnabled = null;
|
||||
if (!string.IsNullOrWhiteSpace(pluginsEnabledStr))
|
||||
{
|
||||
try
|
||||
{
|
||||
pluginsEnabled = JsonSerializer.Deserialize<Dictionary<string, bool>>(pluginsEnabledStr);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Failed deserialization means bad value, blow it away.
|
||||
Properties.Core.Default.PluginsEnabled = string.Empty;
|
||||
Properties.Core.Default.Save();
|
||||
}
|
||||
}
|
||||
|
||||
if (pluginsEnabled == null) return;
|
||||
|
||||
foreach (var p in ListedPlugins)
|
||||
{
|
||||
if (pluginsEnabled.ContainsKey(p.Value.Name) && !pluginsEnabled[p.Value.Name])
|
||||
{
|
||||
// Plugin is disabled.
|
||||
p.Key.Checked = false; // This triggers the listview ItemChecked event.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PluginList_ItemChecked(object sender, ItemCheckedEventArgs e)
|
||||
{
|
||||
if (ListedPlugins == null) return;
|
||||
|
||||
var plugin = ListedPlugins[e.Item];
|
||||
var enabled = e.Item.Checked;
|
||||
|
||||
PluginManager.GetInstance.SetPluginEnabled(plugin, enabled);
|
||||
|
||||
if (!loading)
|
||||
{
|
||||
Dictionary<string, bool> pluginsEnabled = ListedPlugins.ToDictionary(e => e.Value.Name, e => e.Key.Checked);
|
||||
|
||||
Properties.Core.Default.PluginsEnabled = JsonSerializer.Serialize(pluginsEnabled);
|
||||
Properties.Core.Default.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<IObservatoryPlugin, SettingsForm> SettingsForms = new();
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
using Observatory.Utils;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
partial class CoreForm
|
||||
{
|
||||
private void ColourButton_Click(object _, EventArgs e)
|
||||
{
|
||||
var selectionResult = PopupColour.ShowDialog();
|
||||
if (selectionResult == DialogResult.OK)
|
||||
{
|
||||
ColourButton.BackColor = PopupColour.Color;
|
||||
Properties.Core.Default.NativeNotifyColour = (uint)PopupColour.Color.ToArgb();
|
||||
SettingsManager.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private void PopupCheckbox_CheckedChanged(object _, EventArgs e)
|
||||
{
|
||||
Properties.Core.Default.NativeNotify = PopupCheckbox.Checked;
|
||||
SettingsManager.Save();
|
||||
}
|
||||
|
||||
private void DurationSpinner_ValueChanged(object _, EventArgs e)
|
||||
{
|
||||
Properties.Core.Default.NativeNotifyTimeout = (int)DurationSpinner.Value;
|
||||
SettingsManager.Save();
|
||||
}
|
||||
|
||||
private void ScaleSpinner_ValueChanged(object _, EventArgs e)
|
||||
{
|
||||
Properties.Core.Default.NativeNotifyScale = (int)ScaleSpinner.Value;
|
||||
SettingsManager.Save();
|
||||
}
|
||||
|
||||
private void FontDropdown_SelectedIndexChanged(object _, EventArgs e)
|
||||
{
|
||||
Properties.Core.Default.NativeNotifyFont = FontDropdown.SelectedItem.ToString();
|
||||
SettingsManager.Save();
|
||||
}
|
||||
|
||||
private void CornerDropdown_SelectedIndexChanged(object _, EventArgs e)
|
||||
{
|
||||
Properties.Core.Default.NativeNotifyCorner = CornerDropdown.SelectedIndex;
|
||||
SettingsManager.Save();
|
||||
}
|
||||
|
||||
private void DisplayDropdown_SelectedIndexChanged(object _, EventArgs e)
|
||||
{
|
||||
Properties.Core.Default.NativeNotifyScreen = DisplayDropdown.SelectedIndex - 1;
|
||||
SettingsManager.Save();
|
||||
}
|
||||
|
||||
private void VoiceVolumeSlider_Scroll(object _, EventArgs e)
|
||||
{
|
||||
Properties.Core.Default.VoiceVolume = VoiceVolumeSlider.Value;
|
||||
SettingsManager.Save();
|
||||
}
|
||||
|
||||
private void VoiceSpeedSlider_Scroll(object _, EventArgs e)
|
||||
{
|
||||
Properties.Core.Default.VoiceRate = VoiceSpeedSlider.Value;
|
||||
SettingsManager.Save();
|
||||
}
|
||||
|
||||
private void VoiceCheckbox_CheckedChanged(object _, EventArgs e)
|
||||
{
|
||||
Properties.Core.Default.VoiceNotify = VoiceCheckbox.Checked;
|
||||
SettingsManager.Save();
|
||||
}
|
||||
|
||||
private void VoiceDropdown_SelectedIndexChanged(object _, EventArgs e)
|
||||
{
|
||||
Properties.Core.Default.VoiceSelected = VoiceDropdown.SelectedItem.ToString();
|
||||
SettingsManager.Save();
|
||||
}
|
||||
|
||||
private void PopulateDropdownOptions()
|
||||
{
|
||||
var fonts = new System.Drawing.Text.InstalledFontCollection().Families;
|
||||
FontDropdown.Items.AddRange(fonts.Select(f => f.Name).ToArray());
|
||||
|
||||
DisplayDropdown.Items.Add("Primary");
|
||||
if (Screen.AllScreens.Length > 1)
|
||||
for (int i = 0; i < Screen.AllScreens.Length; i++)
|
||||
DisplayDropdown.Items.Add((i + 1).ToString());
|
||||
|
||||
var voices = new System.Speech.Synthesis.SpeechSynthesizer().GetInstalledVoices();
|
||||
foreach (var voice in voices.Select(v => v.VoiceInfo.Name))
|
||||
VoiceDropdown.Items.Add(voice);
|
||||
|
||||
}
|
||||
|
||||
private void PopulateNativeSettings()
|
||||
{
|
||||
var settings = Properties.Core.Default;
|
||||
|
||||
DisplayDropdown.SelectedIndex = settings.NativeNotifyScreen + 1;
|
||||
CornerDropdown.SelectedIndex = settings.NativeNotifyCorner;
|
||||
FontDropdown.SelectedItem = settings.NativeNotifyFont;
|
||||
ScaleSpinner.Value = settings.NativeNotifyScale;
|
||||
DurationSpinner.Value = settings.NativeNotifyTimeout;
|
||||
ColourButton.BackColor = Color.FromArgb((int)settings.NativeNotifyColour);
|
||||
PopupCheckbox.Checked = settings.NativeNotify;
|
||||
VoiceVolumeSlider.Value = settings.VoiceVolume;
|
||||
VoiceSpeedSlider.Value = settings.VoiceRate;
|
||||
VoiceDropdown.SelectedItem = settings.VoiceSelected;
|
||||
VoiceCheckbox.Checked = settings.VoiceNotify;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,296 +0,0 @@
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Interfaces;
|
||||
using Observatory.PluginManagement;
|
||||
using Observatory.Utils;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
public partial class CoreForm : Form
|
||||
{
|
||||
private readonly Dictionary<object, Panel> uiPanels;
|
||||
private ThemeManager themeManager;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
|
||||
private const int WM_SETREDRAW = 11;
|
||||
private static void SuspendDrawing(Control control)
|
||||
{
|
||||
SendMessage(control.Handle, WM_SETREDRAW, false, 0);
|
||||
}
|
||||
|
||||
private static void ResumeDrawing(Control control)
|
||||
{
|
||||
SendMessage(control.Handle, WM_SETREDRAW, true, 0);
|
||||
control.Refresh();
|
||||
}
|
||||
|
||||
public CoreForm()
|
||||
{
|
||||
DoubleBuffered = true;
|
||||
InitializeComponent();
|
||||
|
||||
PopulateDropdownOptions();
|
||||
PopulateNativeSettings();
|
||||
|
||||
ColourListHeader(ref PluginList, Color.DarkSlateGray, Color.LightGray);
|
||||
PopulatePluginList();
|
||||
FitColumns();
|
||||
string version = System.Reflection.Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0";
|
||||
Text += $" - v{version}";
|
||||
CoreMenu.SizeChanged += CoreMenu_SizeChanged;
|
||||
uiPanels = new()
|
||||
{
|
||||
{ coreToolStripMenuItem, CorePanel }
|
||||
};
|
||||
|
||||
|
||||
pluginList = new Dictionary<string, ToolStripMenuItem>();
|
||||
CreatePluginTabs();
|
||||
DisableOverriddenNotification();
|
||||
CoreMenu.ItemClicked += CoreMenu_ItemClicked;
|
||||
|
||||
themeManager = ThemeManager.GetInstance;
|
||||
themeManager.RegisterControl(this);
|
||||
|
||||
foreach (var theme in themeManager.GetThemes)
|
||||
{
|
||||
ThemeDropdown.Items.Add(theme);
|
||||
}
|
||||
ThemeDropdown.SelectedItem = themeManager.CurrentTheme;
|
||||
}
|
||||
|
||||
private void CoreMenu_SizeChanged(object? sender, EventArgs e)
|
||||
{
|
||||
CorePanel.Location = new Point(12 + CoreMenu.Width, 12);
|
||||
CorePanel.Width = Width - CoreMenu.Width - 40;
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, ToolStripMenuItem> pluginList;
|
||||
|
||||
private void ToggleMonitorButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
if ((LogMonitor.GetInstance.CurrentState & Framework.LogMonitorState.Realtime) == Framework.LogMonitorState.Realtime)
|
||||
{
|
||||
LogMonitor.GetInstance.Stop();
|
||||
ToggleMonitorButton.Text = "Start Monitor";
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMonitor.GetInstance.Start();
|
||||
ToggleMonitorButton.Text = "Stop Monitor";
|
||||
}
|
||||
}
|
||||
|
||||
private void ResizePanels(Point location, int widthChange)
|
||||
{
|
||||
CorePanel.Location = location;
|
||||
CorePanel.Width += widthChange;
|
||||
foreach (var panel in uiPanels)
|
||||
{
|
||||
if (Controls.Contains(panel.Value))
|
||||
{
|
||||
panel.Value.Location = CorePanel.Location;
|
||||
panel.Value.Size = CorePanel.Size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CoreMenu_ItemClicked(object? _, ToolStripItemClickedEventArgs e)
|
||||
{
|
||||
SuspendDrawing(this);
|
||||
if (e.ClickedItem.Text == "<")
|
||||
{
|
||||
foreach (KeyValuePair<string, ToolStripMenuItem> menuItem in pluginList)
|
||||
{
|
||||
if (menuItem.Value.Text == "<")
|
||||
menuItem.Value.Text = ">";
|
||||
else
|
||||
menuItem.Value.Text = menuItem.Key[..3];
|
||||
}
|
||||
CoreMenu.Width = 110;
|
||||
ResizePanels(new Point(CoreMenu.Width + 3, 12), 0);
|
||||
}
|
||||
else if (e.ClickedItem.Text == ">")
|
||||
{
|
||||
foreach (KeyValuePair<string, ToolStripMenuItem> menuItem in pluginList)
|
||||
{
|
||||
if (menuItem.Value.Text == ">")
|
||||
menuItem.Value.Text = "<";
|
||||
else
|
||||
menuItem.Value.Text = menuItem.Key;
|
||||
}
|
||||
CoreMenu.Width = GetExpandedMenuWidth();
|
||||
ResizePanels(new Point(CoreMenu.Width + 3, 12), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var panel in uiPanels.Where(p => p.Key != e.ClickedItem))
|
||||
{
|
||||
panel.Value.Visible = false;
|
||||
}
|
||||
|
||||
if (!Controls.Contains(uiPanels[e.ClickedItem]))
|
||||
{
|
||||
uiPanels[e.ClickedItem].Location = CorePanel.Location;
|
||||
uiPanels[e.ClickedItem].Size = CorePanel.Size;
|
||||
uiPanels[e.ClickedItem].BackColor = CorePanel.BackColor;
|
||||
uiPanels[e.ClickedItem].Parent = CorePanel.Parent;
|
||||
Controls.Add(uiPanels[e.ClickedItem]);
|
||||
}
|
||||
uiPanels[e.ClickedItem].Visible = true;
|
||||
|
||||
SetClickedItem(e.ClickedItem);
|
||||
}
|
||||
ResumeDrawing(this);
|
||||
}
|
||||
|
||||
private void SetClickedItem(ToolStripItem item)
|
||||
{
|
||||
foreach (ToolStripItem menuItem in CoreMenu.Items)
|
||||
{
|
||||
bool bold = menuItem == item;
|
||||
menuItem.Font = new Font(menuItem.Font, bold ? FontStyle.Bold : FontStyle.Regular);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ColourListHeader(ref ListView list, Color backColor, Color foreColor)
|
||||
{
|
||||
list.OwnerDraw = true;
|
||||
|
||||
list.DrawColumnHeader +=
|
||||
new DrawListViewColumnHeaderEventHandler
|
||||
(
|
||||
(sender, e) => HeaderDraw(sender, e, backColor, foreColor)
|
||||
);
|
||||
list.DrawItem += new DrawListViewItemEventHandler(BodyDraw);
|
||||
}
|
||||
|
||||
private static void HeaderDraw(object? _, DrawListViewColumnHeaderEventArgs e, Color backColor, Color foreColor)
|
||||
{
|
||||
using (SolidBrush backBrush = new(backColor))
|
||||
{
|
||||
e.Graphics.FillRectangle(backBrush, e.Bounds);
|
||||
}
|
||||
|
||||
using (Pen borderBrush = new(Color.Black))
|
||||
{
|
||||
e.Graphics.DrawLine(borderBrush, e.Bounds.Left, e.Bounds.Top, e.Bounds.Left, e.Bounds.Bottom);
|
||||
e.Graphics.DrawLine(borderBrush, e.Bounds.Right, e.Bounds.Top, e.Bounds.Right, e.Bounds.Bottom);
|
||||
}
|
||||
|
||||
if (e.Font != null && e.Header != null)
|
||||
using (SolidBrush foreBrush = new(foreColor))
|
||||
{
|
||||
var format = new StringFormat
|
||||
{
|
||||
Alignment = (StringAlignment)e.Header.TextAlign,
|
||||
LineAlignment = StringAlignment.Center
|
||||
};
|
||||
|
||||
var paddedBounds = new Rectangle(e.Bounds.X + 2, e.Bounds.Y + 2, e.Bounds.Width - 4, e.Bounds.Height - 4);
|
||||
|
||||
e.Graphics.DrawString(e.Header?.Text, e.Font, foreBrush, paddedBounds, format);
|
||||
}
|
||||
}
|
||||
|
||||
private static void BodyDraw(object? _, DrawListViewItemEventArgs e)
|
||||
{
|
||||
e.DrawDefault = true;
|
||||
}
|
||||
|
||||
private void PluginList_Resize(object sender, EventArgs e)
|
||||
{
|
||||
FitColumns();
|
||||
}
|
||||
|
||||
private void FitColumns()
|
||||
{
|
||||
int totalWidth = 0;
|
||||
foreach (ColumnHeader col in PluginList.Columns)
|
||||
totalWidth += col.Width;
|
||||
|
||||
PluginList.Columns[3].Width += PluginList.Width - totalWidth - 1 - SystemInformation.VerticalScrollBarWidth;
|
||||
}
|
||||
|
||||
private void ReadAllButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
var readAllDialogue = new ReadAllForm();
|
||||
ThemeManager.GetInstance.RegisterControl(readAllDialogue);
|
||||
readAllDialogue.StartPosition = FormStartPosition.Manual;
|
||||
readAllDialogue.Location = Point.Add(Location, new Size(100, 100));
|
||||
SuspendDrawing(this);
|
||||
SuspendSorting();
|
||||
readAllDialogue.ShowDialog();
|
||||
ResumeSorting();
|
||||
ResumeDrawing(this);
|
||||
}
|
||||
|
||||
private Dictionary<PluginListView, object> PluginComparer;
|
||||
|
||||
private void SuspendSorting()
|
||||
{
|
||||
PluginComparer = new();
|
||||
foreach (var panel in uiPanels.Values)
|
||||
{
|
||||
foreach (var control in panel.Controls)
|
||||
{
|
||||
if (control?.GetType() == typeof(PluginListView))
|
||||
{
|
||||
var listView = (PluginListView)control;
|
||||
PluginComparer.Add(listView, listView.ListViewItemSorter);
|
||||
listView.ListViewItemSorter = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResumeSorting()
|
||||
{
|
||||
if (PluginComparer?.Any() ?? false)
|
||||
foreach (var panel in PluginComparer.Keys)
|
||||
{
|
||||
panel.ListViewItemSorter = (IObservatoryComparer)PluginComparer[panel];
|
||||
}
|
||||
PluginComparer?.Clear();
|
||||
}
|
||||
|
||||
private Observatory.NativeNotification.NativePopup? nativePopup;
|
||||
|
||||
private void TestButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
NotificationArgs args = new()
|
||||
{
|
||||
Title = "Test Popup Notification",
|
||||
Detail = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec at elit maximus, ornare dui nec, accumsan velit. Vestibulum fringilla elit."
|
||||
};
|
||||
|
||||
nativePopup ??= new Observatory.NativeNotification.NativePopup();
|
||||
|
||||
nativePopup.InvokeNativeNotification(args);
|
||||
}
|
||||
|
||||
private void GithubLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||
{
|
||||
OpenURL("https://github.com/Xjph/ObservatoryCore");
|
||||
}
|
||||
|
||||
private void DonateLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||
{
|
||||
OpenURL("https://www.paypal.com/paypalme/eliteobservatory");
|
||||
}
|
||||
|
||||
private void OpenURL(string url)
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||
}
|
||||
|
||||
private void ThemeDropdown_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
themeManager.CurrentTheme = ThemeDropdown.SelectedItem.ToString() ?? themeManager.CurrentTheme;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,147 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Observatory.Framework.Interfaces;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
internal class DefaultSorter : IObservatoryComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the column to be sorted
|
||||
/// </summary>
|
||||
private int ColumnToSort;
|
||||
/// <summary>
|
||||
/// Specifies the order in which to sort (i.e. 'Ascending').
|
||||
/// </summary>
|
||||
private int OrderOfSort;
|
||||
/// <summary>
|
||||
/// Case insensitive comparer object
|
||||
/// </summary>
|
||||
private CaseInsensitiveComparer ObjectCompare;
|
||||
|
||||
/// <summary>
|
||||
/// Class constructor. Initializes various elements
|
||||
/// </summary>
|
||||
public DefaultSorter()
|
||||
{
|
||||
// Initialize the column to '0'
|
||||
ColumnToSort = 0;
|
||||
|
||||
// Initialize the sort order to 'none'
|
||||
OrderOfSort = 0;
|
||||
|
||||
// Initialize the CaseInsensitiveComparer object
|
||||
ObjectCompare = new CaseInsensitiveComparer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is inherited from the IComparer interface. It compares the two objects passed using a case insensitive comparison.
|
||||
/// </summary>
|
||||
/// <param name="x">First object to be compared</param>
|
||||
/// <param name="y">Second object to be compared</param>
|
||||
/// <returns>The result of the comparison. "0" if equal, negative if 'x' is less than 'y' and positive if 'x' is greater than 'y'</returns>
|
||||
public int Compare(object? x, object? y)
|
||||
{
|
||||
int compareResult;
|
||||
|
||||
ListViewItem? listviewX = (ListViewItem?)x;
|
||||
ListViewItem? listviewY = (ListViewItem?)y;
|
||||
|
||||
if (OrderOfSort == 0)
|
||||
return 0;
|
||||
|
||||
// Compare the two items
|
||||
compareResult = NaturalCompare(listviewX?.SubItems[ColumnToSort].Text, listviewY?.SubItems[ColumnToSort].Text);
|
||||
|
||||
// Calculate correct return value based on object comparison
|
||||
if (OrderOfSort == 1)
|
||||
{
|
||||
// Ascending sort is selected, return normal result of compare operation
|
||||
return compareResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Descending sort is selected, return negative result of compare operation
|
||||
return (-compareResult);
|
||||
}
|
||||
}
|
||||
|
||||
private static int NaturalCompare(string? x, string? y)
|
||||
{
|
||||
for (int i = 0; i <= x?.Length && i <= y?.Length; i++)
|
||||
{
|
||||
// If we've reached the end of the string without finding a difference
|
||||
// the longer string is "greater".
|
||||
if (i == x.Length || i == y.Length)
|
||||
return x.Length > y.Length ? 1 : y.Length > x.Length ? -1 : 0;
|
||||
|
||||
// We've found a number in the same place in both strings.
|
||||
if (Char.IsDigit(x[i]) && Char.IsDigit(y[i]))
|
||||
{
|
||||
// Walk ahead and get the full numbers.
|
||||
string xNum = new(x[i..].TakeWhile(c => Char.IsDigit(c)).ToArray());
|
||||
string yNum = new(y[i..].TakeWhile(c => Char.IsDigit(c)).ToArray());
|
||||
|
||||
// Pad with zeroes to equal lengths.
|
||||
int numLength = Math.Max(xNum.Length, yNum.Length);
|
||||
string xNumPadded = xNum.PadLeft(numLength, '0');
|
||||
string yNumPadded = yNum.PadLeft(numLength, '0');
|
||||
|
||||
// Now that they're the same length a direct compare works.
|
||||
int result = xNumPadded.CompareTo(yNumPadded);
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The numbers are identical, skip them and keep moving.
|
||||
i += numLength - 1;
|
||||
}
|
||||
}
|
||||
// Check if we have unequal letters.
|
||||
else if (x[i] != y[i])
|
||||
{
|
||||
// Straight compare and return.
|
||||
return x[i] > y[i] ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
// If we somehow make it here, return equal result.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of the column to which to apply the sorting operation (Defaults to '0').
|
||||
/// </summary>
|
||||
public int SortColumn
|
||||
{
|
||||
set
|
||||
{
|
||||
ColumnToSort = value;
|
||||
}
|
||||
get
|
||||
{
|
||||
return ColumnToSort;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the order of sorting to apply (for example, 'Ascending' or 'Descending').
|
||||
/// </summary>
|
||||
public int Order
|
||||
{
|
||||
set
|
||||
{
|
||||
OrderOfSort = value;
|
||||
}
|
||||
get
|
||||
{
|
||||
return OrderOfSort;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,310 +0,0 @@
|
||||
// Source: https://stackoverflow.com/questions/51578104/how-to-create-a-semi-transparent-or-blurred-backcolor-in-a-windows-form
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
[SuppressUnmanagedCodeSecurity]
|
||||
public class DwmHelper
|
||||
{
|
||||
public const int WM_DWMCOMPOSITIONCHANGED = 0x031E;
|
||||
|
||||
public struct MARGINS
|
||||
{
|
||||
public int leftWidth;
|
||||
public int rightWidth;
|
||||
public int topHeight;
|
||||
public int bottomHeight;
|
||||
|
||||
public MARGINS(int LeftWidth, int RightWidth, int TopHeight, int BottomHeight)
|
||||
{
|
||||
leftWidth = LeftWidth;
|
||||
rightWidth = RightWidth;
|
||||
topHeight = TopHeight;
|
||||
bottomHeight = BottomHeight;
|
||||
}
|
||||
|
||||
public void NoMargins()
|
||||
{
|
||||
leftWidth = 0;
|
||||
rightWidth = 0;
|
||||
topHeight = 0;
|
||||
bottomHeight = 0;
|
||||
}
|
||||
|
||||
public void SheetOfGlass()
|
||||
{
|
||||
leftWidth = -1;
|
||||
rightWidth = -1;
|
||||
topHeight = -1;
|
||||
bottomHeight = -1;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum DWM_BB
|
||||
{
|
||||
Enable = 1,
|
||||
BlurRegion = 2,
|
||||
TransitionOnMaximized = 4
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
||||
public enum DWMWINDOWATTRIBUTE : uint
|
||||
{
|
||||
NCRenderingEnabled = 1, //Get atttribute
|
||||
NCRenderingPolicy, //Enable or disable non-client rendering
|
||||
TransitionsForceDisabled,
|
||||
AllowNCPaint,
|
||||
CaptionButtonBounds, //Get atttribute
|
||||
NonClientRtlLayout,
|
||||
ForceIconicRepresentation,
|
||||
Flip3DPolicy,
|
||||
ExtendedFrameBounds, //Get atttribute
|
||||
HasIconicBitmap,
|
||||
DisallowPeek,
|
||||
ExcludedFromPeek,
|
||||
Cloak,
|
||||
Cloaked, //Get atttribute. Returns a DWMCLOACKEDREASON
|
||||
FreezeRepresentation,
|
||||
PassiveUpdateMode,
|
||||
UseHostBackDropBrush,
|
||||
AccentPolicy = 19, // Win 10 (undocumented)
|
||||
ImmersiveDarkMode = 20, // Win 11 22000
|
||||
WindowCornerPreference = 33, // Win 11 22000
|
||||
BorderColor, // Win 11 22000
|
||||
CaptionColor, // Win 11 22000
|
||||
TextColor, // Win 11 22000
|
||||
VisibleFrameBorderThickness, // Win 11 22000
|
||||
SystemBackdropType // Win 11 22621
|
||||
}
|
||||
|
||||
public enum DWMCLOACKEDREASON : uint
|
||||
{
|
||||
DWM_CLOAKED_APP = 0x0000001, //cloaked by its owner application.
|
||||
DWM_CLOAKED_SHELL = 0x0000002, //cloaked by the Shell.
|
||||
DWM_CLOAKED_INHERITED = 0x0000004 //inherited from its owner window.
|
||||
}
|
||||
|
||||
public enum DWMNCRENDERINGPOLICY : uint
|
||||
{
|
||||
UseWindowStyle, // Enable/disable non-client rendering based on window style
|
||||
Disabled, // Disabled non-client rendering; window style is ignored
|
||||
Enabled, // Enabled non-client rendering; window style is ignored
|
||||
};
|
||||
|
||||
public enum DWMACCENTSTATE
|
||||
{
|
||||
ACCENT_DISABLED = 0,
|
||||
ACCENT_ENABLE_GRADIENT = 1,
|
||||
ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
|
||||
ACCENT_ENABLE_BLURBEHIND = 3,
|
||||
ACCENT_INVALID_STATE = 4
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum CompositionAction : uint
|
||||
{
|
||||
DWM_EC_DISABLECOMPOSITION = 0,
|
||||
DWM_EC_ENABLECOMPOSITION = 1
|
||||
}
|
||||
|
||||
// Values designating how Flip3D treats a given window.
|
||||
enum DWMFLIP3DWINDOWPOLICY : uint
|
||||
{
|
||||
Default, // Hide or include the window in Flip3D based on window style and visibility.
|
||||
ExcludeBelow, // Display the window under Flip3D and disabled.
|
||||
ExcludeAbove, // Display the window above Flip3D and enabled.
|
||||
};
|
||||
|
||||
public enum ThumbProperties_dwFlags : uint
|
||||
{
|
||||
RectDestination = 0x00000001,
|
||||
RectSource = 0x00000002,
|
||||
Opacity = 0x00000004,
|
||||
Visible = 0x00000008,
|
||||
SourceClientAreaOnly = 0x00000010
|
||||
}
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct AccentPolicy
|
||||
{
|
||||
public DWMACCENTSTATE AccentState;
|
||||
public int AccentFlags;
|
||||
public int GradientColor;
|
||||
public int AnimationId;
|
||||
|
||||
public AccentPolicy(DWMACCENTSTATE accentState, int accentFlags, int gradientColor, int animationId)
|
||||
{
|
||||
AccentState = accentState;
|
||||
AccentFlags = accentFlags;
|
||||
GradientColor = gradientColor;
|
||||
AnimationId = animationId;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct DWM_BLURBEHIND
|
||||
{
|
||||
public DWM_BB dwFlags;
|
||||
public int fEnable;
|
||||
public IntPtr hRgnBlur;
|
||||
public int fTransitionOnMaximized;
|
||||
|
||||
public DWM_BLURBEHIND(bool enabled)
|
||||
{
|
||||
dwFlags = DWM_BB.Enable;
|
||||
fEnable = (enabled) ? 1 : 0;
|
||||
hRgnBlur = IntPtr.Zero;
|
||||
fTransitionOnMaximized = 0;
|
||||
}
|
||||
|
||||
public Region Region => Region.FromHrgn(hRgnBlur);
|
||||
|
||||
public bool TransitionOnMaximized
|
||||
{
|
||||
get => fTransitionOnMaximized > 0;
|
||||
set
|
||||
{
|
||||
fTransitionOnMaximized = (value) ? 1 : 0;
|
||||
dwFlags |= DWM_BB.TransitionOnMaximized;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRegion(Graphics graphics, Region region)
|
||||
{
|
||||
hRgnBlur = region.GetHrgn(graphics);
|
||||
dwFlags |= DWM_BB.BlurRegion;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct WinCompositionAttrData
|
||||
{
|
||||
public DWMWINDOWATTRIBUTE Attribute;
|
||||
public IntPtr Data; //Will point to an AccentPolicy struct, where Attribute will be DWMWINDOWATTRIBUTE.AccentPolicy
|
||||
public int SizeOfData;
|
||||
|
||||
public WinCompositionAttrData(DWMWINDOWATTRIBUTE attribute, IntPtr data, int sizeOfData)
|
||||
{
|
||||
Attribute = attribute;
|
||||
Data = data;
|
||||
SizeOfData = sizeOfData;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetBlurBehindPolicyAccentFlags()
|
||||
{
|
||||
int drawLeftBorder = 20;
|
||||
int drawTopBorder = 40;
|
||||
int drawRightBorder = 80;
|
||||
int drawBottomBorder = 100;
|
||||
return (drawLeftBorder | drawTopBorder | drawRightBorder | drawBottomBorder);
|
||||
}
|
||||
|
||||
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa969508(v=vs.85).aspx
|
||||
[DllImport("dwmapi.dll")]
|
||||
internal static extern int DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind);
|
||||
|
||||
[DllImport("dwmapi.dll", PreserveSig = false)]
|
||||
public static extern void DwmEnableComposition(CompositionAction uCompositionAction);
|
||||
|
||||
//https://msdn.microsoft.com/it-it/library/windows/desktop/aa969512(v=vs.85).aspx
|
||||
[DllImport("dwmapi.dll")]
|
||||
internal static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMarInset);
|
||||
|
||||
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa969515(v=vs.85).aspx
|
||||
[DllImport("dwmapi.dll")]
|
||||
internal static extern int DwmGetWindowAttribute(IntPtr hwnd, DWMWINDOWATTRIBUTE attr, ref int attrValue, int attrSize);
|
||||
|
||||
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa969524(v=vs.85).aspx
|
||||
[DllImport("dwmapi.dll")]
|
||||
internal static extern int DwmSetWindowAttribute(IntPtr hwnd, DWMWINDOWATTRIBUTE attr, ref int attrValue, int attrSize);
|
||||
|
||||
[DllImport("User32.dll", SetLastError = true)]
|
||||
internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WinCompositionAttrData data);
|
||||
|
||||
[DllImport("dwmapi.dll")]
|
||||
internal static extern int DwmIsCompositionEnabled(ref int pfEnabled);
|
||||
|
||||
public static bool IsCompositionEnabled()
|
||||
{
|
||||
int pfEnabled = 0;
|
||||
int result = DwmIsCompositionEnabled(ref pfEnabled);
|
||||
return (pfEnabled == 1) ? true : false;
|
||||
}
|
||||
|
||||
public static bool IsNonClientRenderingEnabled(IntPtr hWnd)
|
||||
{
|
||||
int gwaEnabled = 0;
|
||||
int result = DwmGetWindowAttribute(hWnd, DWMWINDOWATTRIBUTE.NCRenderingEnabled, ref gwaEnabled, sizeof(int));
|
||||
return gwaEnabled == 1;
|
||||
}
|
||||
|
||||
public static bool WindowSetAttribute(IntPtr hWnd, DWMWINDOWATTRIBUTE attribute, int attributeValue)
|
||||
{
|
||||
int result = DwmSetWindowAttribute(hWnd, attribute, ref attributeValue, sizeof(int));
|
||||
return (result == 0);
|
||||
}
|
||||
|
||||
public static void Windows10EnableBlurBehind(IntPtr hWnd)
|
||||
{
|
||||
DWMNCRENDERINGPOLICY policy = DWMNCRENDERINGPOLICY.Enabled;
|
||||
WindowSetAttribute(hWnd, DWMWINDOWATTRIBUTE.NCRenderingPolicy, (int)policy);
|
||||
|
||||
AccentPolicy accPolicy = new AccentPolicy()
|
||||
{
|
||||
AccentState = DWMACCENTSTATE.ACCENT_ENABLE_BLURBEHIND,
|
||||
};
|
||||
|
||||
int accentSize = Marshal.SizeOf(accPolicy);
|
||||
IntPtr accentPtr = Marshal.AllocHGlobal(accentSize);
|
||||
Marshal.StructureToPtr(accPolicy, accentPtr, false);
|
||||
var data = new WinCompositionAttrData(DWMWINDOWATTRIBUTE.AccentPolicy, accentPtr, accentSize);
|
||||
|
||||
SetWindowCompositionAttribute(hWnd, ref data);
|
||||
Marshal.FreeHGlobal(accentPtr);
|
||||
}
|
||||
|
||||
public static bool WindowEnableBlurBehind(IntPtr hWnd)
|
||||
{
|
||||
DWMNCRENDERINGPOLICY policy = DWMNCRENDERINGPOLICY.Enabled;
|
||||
WindowSetAttribute(hWnd, DWMWINDOWATTRIBUTE.NCRenderingPolicy, (int)policy);
|
||||
|
||||
DWM_BLURBEHIND dwm_BB = new DWM_BLURBEHIND(true);
|
||||
int result = DwmEnableBlurBehindWindow(hWnd, ref dwm_BB);
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
public static bool WindowExtendIntoClientArea(IntPtr hWnd, MARGINS margins)
|
||||
{
|
||||
// Extend frame on the bottom of client area
|
||||
int result = DwmExtendFrameIntoClientArea(hWnd, ref margins);
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
public static bool WindowBorderlessDropShadow(IntPtr hWnd, int shadowSize)
|
||||
{
|
||||
MARGINS margins = new MARGINS(0, shadowSize, 0, shadowSize);
|
||||
int result = DwmExtendFrameIntoClientArea(hWnd, ref margins);
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
public static bool WindowSheetOfGlass(IntPtr hWnd)
|
||||
{
|
||||
MARGINS margins = new MARGINS();
|
||||
|
||||
//Margins set to All:-1 - Sheet Of Glass effect
|
||||
margins.SheetOfGlass();
|
||||
int result = DwmExtendFrameIntoClientArea(hWnd, ref margins);
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
public static bool WindowDisableRendering(IntPtr hWnd)
|
||||
{
|
||||
int ncrp = (int)DWMNCRENDERINGPOLICY.Disabled;
|
||||
// Disable non-client area rendering on the window.
|
||||
int result = DwmSetWindowAttribute(hWnd, DWMWINDOWATTRIBUTE.NCRenderingPolicy, ref ncrp, sizeof(int));
|
||||
return result == 0;
|
||||
}
|
||||
}
|
88
ObservatoryCore/UI/NotificationForm.Designer.cs
generated
88
ObservatoryCore/UI/NotificationForm.Designer.cs
generated
@ -1,88 +0,0 @@
|
||||
namespace Observatory.UI
|
||||
{
|
||||
partial class NotificationForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
Title = new Label();
|
||||
Body = new Label();
|
||||
SuspendLayout();
|
||||
//
|
||||
// Title
|
||||
//
|
||||
Title.Font = new Font("Segoe UI", 18F, FontStyle.Regular, GraphicsUnit.Point);
|
||||
Title.ForeColor = Color.OrangeRed;
|
||||
Title.Location = new Point(5, 5);
|
||||
Title.MaximumSize = new Size(345, 45);
|
||||
Title.Name = "Title";
|
||||
Title.Size = new Size(338, 35);
|
||||
Title.TabIndex = 0;
|
||||
Title.Text = "Title";
|
||||
Title.UseCompatibleTextRendering = true;
|
||||
//
|
||||
// Body
|
||||
//
|
||||
Body.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
|
||||
Body.AutoSize = true;
|
||||
Body.Font = new Font("Segoe UI", 14.25F, FontStyle.Regular, GraphicsUnit.Point);
|
||||
Body.ForeColor = Color.OrangeRed;
|
||||
Body.Location = new Point(12, 40);
|
||||
Body.MaximumSize = new Size(320, 85);
|
||||
Body.Name = "Body";
|
||||
Body.Size = new Size(51, 31);
|
||||
Body.TabIndex = 1;
|
||||
Body.Text = "Body";
|
||||
Body.UseCompatibleTextRendering = true;
|
||||
//
|
||||
// NotificationForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
BackColor = Color.FromArgb(64, 64, 64);
|
||||
ClientSize = new Size(355, 145);
|
||||
ControlBox = false;
|
||||
Controls.Add(Body);
|
||||
Controls.Add(Title);
|
||||
Enabled = false;
|
||||
FormBorderStyle = FormBorderStyle.None;
|
||||
MaximizeBox = false;
|
||||
MinimizeBox = false;
|
||||
Name = "NotificationForm";
|
||||
ShowIcon = false;
|
||||
ShowInTaskbar = false;
|
||||
Text = "NotificationForm";
|
||||
TransparencyKey = Color.FromArgb(64, 64, 64);
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Label Title;
|
||||
private Label Body;
|
||||
}
|
||||
}
|
@ -1,262 +0,0 @@
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Files.Journal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
public partial class NotificationForm : Form
|
||||
{
|
||||
private Color _color;
|
||||
private readonly Guid _guid;
|
||||
private readonly System.Timers.Timer _timer;
|
||||
private bool _defaultPosition = true;
|
||||
private Point _originalLocation;
|
||||
|
||||
protected override bool ShowWithoutActivation => true;
|
||||
protected override CreateParams CreateParams
|
||||
{
|
||||
get
|
||||
{
|
||||
CreateParams cp = base.CreateParams;
|
||||
cp.ExStyle |= 0x00000008; // WS_EX_TOPMOST
|
||||
return cp;
|
||||
}
|
||||
}
|
||||
|
||||
public NotificationForm(Guid guid, NotificationArgs args)
|
||||
{
|
||||
_guid = guid;
|
||||
_color = Color.FromArgb((int)Properties.Core.Default.NativeNotifyColour);
|
||||
CreationTime = DateTime.Now;
|
||||
InitializeComponent();
|
||||
|
||||
Title.Paint += DrawText;
|
||||
Body.Paint += DrawText;
|
||||
|
||||
if (System.Environment.OSVersion.Version.Major >= 6 && DwmHelper.IsCompositionEnabled())
|
||||
{
|
||||
if (Environment.OSVersion.Version.Major > 6)
|
||||
{
|
||||
DwmHelper.Windows10EnableBlurBehind(Handle);
|
||||
}
|
||||
else
|
||||
{
|
||||
DwmHelper.WindowEnableBlurBehind(Handle);
|
||||
}
|
||||
|
||||
// For some reason this causes the window to become all white on my own
|
||||
// PC. Looks very similar to strange system-specific all-white behaviour
|
||||
// of Avalonia.
|
||||
// DwmHelper.WindowBorderlessDropShadow(Handle, 2);
|
||||
}
|
||||
|
||||
|
||||
Title.ForeColor = _color;
|
||||
Title.Text = args.Title;
|
||||
Title.Font = new Font(Properties.Core.Default.NativeNotifyFont, 18);
|
||||
Body.ForeColor = _color;
|
||||
Body.Text = args.Detail;
|
||||
Body.Font = new Font(Properties.Core.Default.NativeNotifyFont, 14);
|
||||
Paint += DrawBorder;
|
||||
|
||||
AdjustPosition(args.XPos / 100, args.YPos / 100);
|
||||
|
||||
_timer = new();
|
||||
_timer.Elapsed += CloseNotification;
|
||||
if (args.Timeout != 0)
|
||||
{
|
||||
_timer.Interval = args.Timeout == -1 ? Properties.Core.Default.NativeNotifyTimeout : args.Timeout;
|
||||
_timer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void NotificationForm_FormClosed(object? sender, FormClosedEventArgs e)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public DateTime CreationTime { get; private init; }
|
||||
|
||||
public void Update(NotificationArgs notificationArgs)
|
||||
{
|
||||
// Catch Cross-thread access and invoke
|
||||
try
|
||||
{
|
||||
Title.Text = notificationArgs.Title;
|
||||
Body.Text = notificationArgs.Detail;
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
Invoke(() =>
|
||||
{
|
||||
Title.Text = notificationArgs.Title;
|
||||
Body.Text = notificationArgs.Detail;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Notification Update Failure, please inform Vithigar. Details: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AdjustPosition(double x = -1.0, double y = -1.0)
|
||||
{
|
||||
int screen = Properties.Core.Default.NativeNotifyScreen;
|
||||
int corner = Properties.Core.Default.NativeNotifyCorner;
|
||||
Rectangle screenBounds;
|
||||
|
||||
if (screen == -1 || screen > Screen.AllScreens.Length)
|
||||
if (Screen.AllScreens.Length == 1)
|
||||
screenBounds = Screen.GetBounds(this);
|
||||
else
|
||||
screenBounds = Screen.PrimaryScreen.Bounds;
|
||||
else
|
||||
screenBounds = Screen.AllScreens[screen - 1].Bounds;
|
||||
|
||||
if (x >= 0 && y >= 0)
|
||||
{
|
||||
_defaultPosition = false;
|
||||
int xLocation = Convert.ToInt32(screenBounds.Width * x);
|
||||
int yLocation = Convert.ToInt32(screenBounds.Height * y);
|
||||
Location = Point.Add(screenBounds.Location, new Size(xLocation, yLocation));
|
||||
}
|
||||
else
|
||||
{
|
||||
_defaultPosition = true;
|
||||
switch (corner)
|
||||
{
|
||||
default:
|
||||
case 0:
|
||||
Location = Point.Add(
|
||||
new Point(screenBounds.Right, screenBounds.Bottom),
|
||||
new Size(-(Width + 50), -(Height + 50)));
|
||||
break;
|
||||
case 1:
|
||||
Location = Point.Add(
|
||||
new Point(screenBounds.Left, screenBounds.Bottom),
|
||||
new Size(50, -(Height + 50)));
|
||||
break;
|
||||
case 2:
|
||||
Location = Point.Add(
|
||||
new Point(screenBounds.Right, screenBounds.Top),
|
||||
new Size(-(Width + 50), 50));
|
||||
break;
|
||||
case 3:
|
||||
Location = Point.Add(
|
||||
new Point(screenBounds.Left, screenBounds.Top),
|
||||
new Size(50, 00));
|
||||
break;
|
||||
}
|
||||
_originalLocation = new Point(Location.X, Location.Y);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBorder(object? sender, PaintEventArgs e)
|
||||
{
|
||||
using (Pen pen = new Pen(_color))
|
||||
{
|
||||
pen.Width = 6;
|
||||
e.Graphics.DrawLine(pen, 0, 0, Width, 0);
|
||||
e.Graphics.DrawLine(pen, 0, 0, 0, Height);
|
||||
e.Graphics.DrawLine(pen, 0, Height, Width, Height);
|
||||
e.Graphics.DrawLine(pen, Width, 0, Width, Height);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m)
|
||||
{
|
||||
|
||||
switch (m.Msg)
|
||||
{
|
||||
case DwmHelper.WM_DWMCOMPOSITIONCHANGED:
|
||||
if (System.Environment.OSVersion.Version.Major >= 6 && DwmHelper.IsCompositionEnabled())
|
||||
{
|
||||
var policy = DwmHelper.DWMNCRENDERINGPOLICY.Enabled;
|
||||
DwmHelper.WindowSetAttribute(Handle, DwmHelper.DWMWINDOWATTRIBUTE.NCRenderingPolicy, (int)policy);
|
||||
DwmHelper.WindowBorderlessDropShadow(Handle, 2);
|
||||
m.Result = IntPtr.Zero;
|
||||
}
|
||||
break;
|
||||
case 0x0084:
|
||||
m.Result = (IntPtr)(-1);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
base.WndProc(ref m);
|
||||
}
|
||||
|
||||
private void DrawText(object? sender, PaintEventArgs e)
|
||||
{
|
||||
if (sender != null)
|
||||
{
|
||||
var label = (Label)sender;
|
||||
e.Graphics.Clear(Color.FromArgb(64, 64, 64));
|
||||
using (var sf = new StringFormat())
|
||||
using (var brush = new SolidBrush(label.ForeColor))
|
||||
{
|
||||
sf.Alignment = sf.LineAlignment = StringAlignment.Near;
|
||||
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
|
||||
e.Graphics.DrawString(label.Text, label.Font, brush, label.ClientRectangle, sf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Guid Guid { get => _guid; }
|
||||
|
||||
public void AdjustOffset(bool increase)
|
||||
{
|
||||
if (_defaultPosition)
|
||||
{
|
||||
if (increase || Location != _originalLocation)
|
||||
{
|
||||
var corner = Properties.Core.Default.NativeNotifyCorner;
|
||||
|
||||
if ((corner >= 2 && increase) || (corner <= 1 && !increase))
|
||||
{
|
||||
Location = new Point(Location.X, Location.Y + Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
Location = new Point(Location.X, Location.Y - Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseNotification(object? sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
// Catch Cross-thread access and invoke
|
||||
try
|
||||
{
|
||||
Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
Invoke(() => Close());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Notification Close Failure, please inform Vithigar. Details: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
_timer.Stop();
|
||||
_timer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
@ -1,293 +0,0 @@
|
||||
using Observatory.Framework.Interfaces;
|
||||
using Observatory.Framework;
|
||||
using System.Collections;
|
||||
using Observatory.PluginManagement;
|
||||
using Observatory.Utils;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Data.Common;
|
||||
using System.ComponentModel.Design.Serialization;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
internal class PluginHelper
|
||||
{
|
||||
internal static List<string> CreatePluginTabs(MenuStrip menu, IEnumerable<(IObservatoryWorker plugin, PluginManagement.PluginManager.PluginStatus signed)> plugins, Dictionary<object, Panel> uiPanels)
|
||||
{
|
||||
List<string> pluginList = new List<string>();
|
||||
foreach (var plugin in plugins.OrderBy(p => p.plugin.ShortName))
|
||||
{
|
||||
AddPlugin(menu, plugin.plugin, plugin.signed, uiPanels);
|
||||
pluginList.Add(plugin.plugin.ShortName);
|
||||
}
|
||||
return pluginList;
|
||||
}
|
||||
|
||||
internal static List<string> CreatePluginTabs(MenuStrip menu, IEnumerable<(IObservatoryNotifier plugin, PluginManagement.PluginManager.PluginStatus signed)> plugins, Dictionary<object, Panel> uiPanels)
|
||||
{
|
||||
List<string> pluginList = new List<string>();
|
||||
foreach (var plugin in plugins.OrderBy(p => p.plugin.ShortName))
|
||||
{
|
||||
AddPlugin(menu, plugin.plugin, plugin.signed, uiPanels);
|
||||
pluginList.Add(plugin.plugin.ShortName);
|
||||
}
|
||||
return pluginList;
|
||||
}
|
||||
|
||||
private static void AddPlugin(MenuStrip menu, IObservatoryPlugin plugin, PluginManagement.PluginManager.PluginStatus signed, Dictionary<object, Panel> uiPanels)
|
||||
{
|
||||
var newItem = new ToolStripMenuItem()
|
||||
{
|
||||
Text = plugin.ShortName,
|
||||
BackColor = menu.Items[0].BackColor,
|
||||
ForeColor = menu.Items[0].ForeColor,
|
||||
Font = menu.Items[0].Font,
|
||||
TextAlign = menu.Items[0].TextAlign
|
||||
};
|
||||
ThemeManager.GetInstance.RegisterControl(newItem);
|
||||
menu.Items.Add(newItem);
|
||||
|
||||
if (plugin.PluginUI.PluginUIType == Framework.PluginUI.UIType.Basic)
|
||||
uiPanels.Add(newItem, CreateBasicUI(plugin));
|
||||
else if (plugin.PluginUI.PluginUIType == Framework.PluginUI.UIType.Panel)
|
||||
uiPanels.Add(newItem, (Panel)plugin.PluginUI.UI);
|
||||
}
|
||||
|
||||
private static Panel CreateBasicUI(IObservatoryPlugin plugin)
|
||||
{
|
||||
Panel panel = new()
|
||||
{
|
||||
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom | AnchorStyles.Top
|
||||
};
|
||||
plugin.PluginUI.UI = panel;
|
||||
|
||||
IObservatoryComparer columnSorter;
|
||||
if (plugin.ColumnSorter != null)
|
||||
columnSorter = plugin.ColumnSorter;
|
||||
else
|
||||
columnSorter = new DefaultSorter();
|
||||
|
||||
PluginListView listView = new()
|
||||
{
|
||||
View = View.Details,
|
||||
Location = new Point(0, 0),
|
||||
Size = panel.Size,
|
||||
Dock = DockStyle.Fill,
|
||||
BackColor = Color.FromArgb(64, 64, 64),
|
||||
ForeColor = Color.LightGray,
|
||||
ListViewItemSorter = columnSorter,
|
||||
Font = new Font(new FontFamily("Segoe UI"), 10, FontStyle.Regular)
|
||||
};
|
||||
panel.Controls.Add(listView);
|
||||
|
||||
string colSize = Properties.Core.Default.ColumnSizing;
|
||||
List<ColumnSizing>? columnSizing = null;
|
||||
if (!string.IsNullOrWhiteSpace(colSize))
|
||||
{
|
||||
try
|
||||
{
|
||||
columnSizing = JsonSerializer.Deserialize<List<ColumnSizing>>(colSize);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Failed deserialization means bad value, blow it away.
|
||||
Properties.Core.Default.ColumnSizing = string.Empty;
|
||||
Properties.Core.Default.Save();
|
||||
}
|
||||
}
|
||||
|
||||
columnSizing ??= new List<ColumnSizing>();
|
||||
// Is losing column sizes between versions acceptable?
|
||||
ColumnSizing pluginColumnSizing = columnSizing
|
||||
.Where(c => c.PluginName == plugin.Name && c.PluginVersion == plugin.Version)
|
||||
.FirstOrDefault(new ColumnSizing() { PluginName = plugin.Name, PluginVersion = plugin.Version });
|
||||
|
||||
if (!columnSizing.Contains(pluginColumnSizing))
|
||||
{
|
||||
columnSizing.Add(pluginColumnSizing);
|
||||
}
|
||||
|
||||
foreach (var property in plugin.PluginUI.DataGrid.First().GetType().GetProperties())
|
||||
{
|
||||
// https://stackoverflow.com/questions/5796383/insert-spaces-between-words-on-a-camel-cased-token
|
||||
string columnLabel = Regex.Replace(
|
||||
Regex.Replace(
|
||||
property.Name,
|
||||
@"(\P{Ll})(\P{Ll}\p{Ll})",
|
||||
"$1 $2"
|
||||
),
|
||||
@"(\p{Ll})(\P{Ll})",
|
||||
"$1 $2"
|
||||
);
|
||||
|
||||
int width;
|
||||
|
||||
if (pluginColumnSizing.ColumnWidth.ContainsKey(columnLabel))
|
||||
{
|
||||
width = pluginColumnSizing.ColumnWidth[columnLabel];
|
||||
}
|
||||
else
|
||||
{
|
||||
var widthAttrib = property.GetCustomAttribute<ColumnSuggestedWidth>();
|
||||
|
||||
width = widthAttrib == null
|
||||
// Rough approximation of width by label length if none specified.
|
||||
? columnLabel.Length * 10
|
||||
: widthAttrib.Width;
|
||||
|
||||
pluginColumnSizing.ColumnWidth.Add(columnLabel, width);
|
||||
}
|
||||
|
||||
listView.Columns.Add(columnLabel, width);
|
||||
|
||||
}
|
||||
|
||||
Properties.Core.Default.ColumnSizing = JsonSerializer.Serialize(columnSizing);
|
||||
Properties.Core.Default.Save();
|
||||
|
||||
// Oddly, the listview resize event often fires after the column size change but
|
||||
// with stale (default?!) column width values.
|
||||
// Still need a resize handler to avoid the ugliness of the rightmost column
|
||||
// leaving gaps, but preventing saving the width changes there should stop the
|
||||
// stale resize event from overwriting with bad data.
|
||||
// Using a higher-order function here to create two different versions of the
|
||||
// event handler for these purposes.
|
||||
var handleColSize = (bool saveProps) =>
|
||||
(object? sender, EventArgs e) =>
|
||||
{
|
||||
int colTotalWidth = 0;
|
||||
ColumnHeader? rightmost = null;
|
||||
foreach (ColumnHeader column in listView.Columns)
|
||||
{
|
||||
colTotalWidth += column.Width;
|
||||
if (rightmost == null || column.DisplayIndex > rightmost.DisplayIndex)
|
||||
rightmost = column;
|
||||
|
||||
if (saveProps)
|
||||
{
|
||||
if (pluginColumnSizing.ColumnWidth.ContainsKey(column.Text))
|
||||
pluginColumnSizing.ColumnWidth[column.Text] = column.Width;
|
||||
else
|
||||
pluginColumnSizing.ColumnWidth.Add(column.Text, column.Width);
|
||||
}
|
||||
}
|
||||
|
||||
if (rightmost != null && colTotalWidth < listView.Width)
|
||||
{
|
||||
rightmost.Width = listView.Width - (colTotalWidth - rightmost.Width);
|
||||
|
||||
if (saveProps)
|
||||
pluginColumnSizing.ColumnWidth[rightmost.Text] = rightmost.Width;
|
||||
}
|
||||
|
||||
if (saveProps)
|
||||
{
|
||||
Properties.Core.Default.ColumnSizing = JsonSerializer.Serialize(columnSizing);
|
||||
Properties.Core.Default.Save();
|
||||
}
|
||||
};
|
||||
|
||||
listView.ColumnWidthChanged += handleColSize(true).Invoke;
|
||||
listView.Resize += handleColSize(false).Invoke;
|
||||
|
||||
listView.ColumnClick += (sender, e) =>
|
||||
{
|
||||
if (e.Column == columnSorter.SortColumn)
|
||||
{
|
||||
// Reverse the current sort direction for this column.
|
||||
if (columnSorter.Order == 1)
|
||||
{
|
||||
columnSorter.Order = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
columnSorter.Order = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the column number that is to be sorted; default to ascending.
|
||||
columnSorter.SortColumn = e.Column;
|
||||
columnSorter.Order = 1;
|
||||
}
|
||||
listView.Sort();
|
||||
};
|
||||
|
||||
plugin.PluginUI.DataGrid.CollectionChanged += (sender, e) =>
|
||||
{
|
||||
var updateGrid = () =>
|
||||
{
|
||||
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add &&
|
||||
e.NewItems != null)
|
||||
{
|
||||
foreach (var newItem in e.NewItems)
|
||||
{
|
||||
ListViewItem newListItem = new();
|
||||
foreach (var property in newItem.GetType().GetProperties())
|
||||
{
|
||||
newListItem.SubItems.Add(property.GetValue(newItem)?.ToString());
|
||||
}
|
||||
newListItem.SubItems.RemoveAt(0);
|
||||
listView.Items.Add(newListItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove &&
|
||||
e.OldItems != null)
|
||||
{
|
||||
foreach (var oldItem in e.OldItems)
|
||||
{
|
||||
ListViewItem oldListItem = new();
|
||||
foreach (var property in oldItem.GetType().GetProperties())
|
||||
{
|
||||
oldListItem.SubItems.Add(property.GetValue(oldItem)?.ToString());
|
||||
}
|
||||
oldListItem.SubItems.RemoveAt(0);
|
||||
|
||||
var itemToRemove = listView.Items.Cast<ListViewItem>().Where(i => i.SubItems.Cast<string>().SequenceEqual(oldListItem.SubItems.Cast<string>())).First();
|
||||
if (itemToRemove != null)
|
||||
{
|
||||
listView.Items.Remove(itemToRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
listView.Items.Clear();
|
||||
foreach (var item in plugin.PluginUI.DataGrid)
|
||||
{
|
||||
ListViewItem listItem = new();
|
||||
foreach (var property in item.GetType().GetProperties())
|
||||
{
|
||||
listItem.SubItems.Add(property.GetValue(item)?.ToString());
|
||||
}
|
||||
listItem.SubItems.RemoveAt(0);
|
||||
listView.Items.Add(listItem);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (listView.Created)
|
||||
{
|
||||
listView.Invoke(updateGrid);
|
||||
}
|
||||
else
|
||||
{
|
||||
updateGrid();
|
||||
}
|
||||
};
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
internal static Panel CreatePluginSettings(IObservatoryPlugin plugin)
|
||||
{
|
||||
Panel panel = new Panel();
|
||||
|
||||
return panel;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Permissions;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
internal class PluginListView : ListView
|
||||
{
|
||||
public PluginListView()
|
||||
{
|
||||
OwnerDraw = true;
|
||||
GridLines = false;
|
||||
DrawItem += PluginListView_DrawItem;
|
||||
DrawSubItem += PluginListView_DrawSubItem;
|
||||
DrawColumnHeader += PluginListView_DrawColumnHeader;
|
||||
|
||||
|
||||
DoubleBuffered = true;
|
||||
base.GridLines = false;//We should prevent the default drawing of gridlines.
|
||||
}
|
||||
|
||||
// Stash for performance when doing large UI updates.
|
||||
private IComparer? comparer = null;
|
||||
|
||||
public void SuspendDrawing()
|
||||
{
|
||||
BeginUpdate();
|
||||
comparer = ListViewItemSorter;
|
||||
}
|
||||
|
||||
public void ResumeDrawing()
|
||||
{
|
||||
if (comparer != null)
|
||||
{
|
||||
ListViewItemSorter = comparer;
|
||||
comparer = null;
|
||||
}
|
||||
EndUpdate();
|
||||
}
|
||||
|
||||
private static void DrawBorder(Graphics graphics, Pen pen, Rectangle bounds, bool header = false)
|
||||
{
|
||||
|
||||
Point topRight = new(bounds.Right, bounds.Top);
|
||||
Point bottomRight = new(bounds.Right, bounds.Bottom);
|
||||
|
||||
graphics.DrawLine(pen, topRight, bottomRight);
|
||||
|
||||
if (header)
|
||||
{
|
||||
Point bottomLeft = new(bounds.Left, bounds.Bottom);
|
||||
// Point topLeft = new(bounds.Left, bounds.Top);
|
||||
// graphics.DrawLine(pen, topLeft, topRight);
|
||||
// graphics.DrawLine(pen, topLeft, bottomLeft);
|
||||
graphics.DrawLine(pen, bottomLeft, bottomRight);
|
||||
}
|
||||
}
|
||||
|
||||
private void PluginListView_DrawColumnHeader(object? sender, DrawListViewColumnHeaderEventArgs e)
|
||||
{
|
||||
using (var g = e.Graphics)
|
||||
if (g != null)
|
||||
{
|
||||
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
|
||||
Pen pen = new(new SolidBrush(Color.LightGray));
|
||||
DrawBorder(g, pen, e.Bounds);
|
||||
using (var font = new Font(this.Font, FontStyle.Bold))
|
||||
{
|
||||
Brush textBrush = new SolidBrush(ForeColor);
|
||||
g.DrawString(e.Header?.Text, font, textBrush, e.Bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PluginListView_DrawSubItem(object? sender, DrawListViewSubItemEventArgs e)
|
||||
{
|
||||
using (var g = e.Graphics)
|
||||
if (g != null)
|
||||
{
|
||||
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
|
||||
Pen pen = new(new SolidBrush(Color.LightGray));
|
||||
DrawBorder(g, pen, e.Bounds, false);
|
||||
|
||||
e.DrawText();
|
||||
}
|
||||
}
|
||||
|
||||
private void PluginListView_DrawItem(object? sender, DrawListViewItemEventArgs e)
|
||||
{
|
||||
var offsetColor = (int value) =>
|
||||
{
|
||||
if (value > 127)
|
||||
{
|
||||
return value - 20;
|
||||
}
|
||||
else
|
||||
{
|
||||
return value + 20;
|
||||
}
|
||||
};
|
||||
|
||||
using (var g = e.Graphics)
|
||||
{
|
||||
if (e.ItemIndex % 2 == 0)
|
||||
{
|
||||
e.Item.BackColor = BackColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
e.Item.BackColor = Color.FromArgb(offsetColor(BackColor.R), offsetColor(BackColor.G), offsetColor(BackColor.B));
|
||||
}
|
||||
|
||||
if (g != null)
|
||||
{
|
||||
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
|
||||
Pen pen = new(new SolidBrush(Color.LightGray));
|
||||
e.DrawBackground();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
84
ObservatoryCore/UI/ReadAllProgress.Designer.cs
generated
84
ObservatoryCore/UI/ReadAllProgress.Designer.cs
generated
@ -1,84 +0,0 @@
|
||||
namespace Observatory.UI
|
||||
{
|
||||
partial class ReadAllForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
ReadAllProgress = new ProgressBar();
|
||||
JournalLabel = new Label();
|
||||
CancelButton = new Button();
|
||||
SuspendLayout();
|
||||
//
|
||||
// ReadAllProgress
|
||||
//
|
||||
ReadAllProgress.Location = new Point(12, 27);
|
||||
ReadAllProgress.Name = "ReadAllProgress";
|
||||
ReadAllProgress.Size = new Size(371, 23);
|
||||
ReadAllProgress.Step = 1;
|
||||
ReadAllProgress.TabIndex = 0;
|
||||
//
|
||||
// JournalLabel
|
||||
//
|
||||
JournalLabel.AutoSize = true;
|
||||
JournalLabel.Location = new Point(12, 9);
|
||||
JournalLabel.Name = "JournalLabel";
|
||||
JournalLabel.Size = new Size(45, 15);
|
||||
JournalLabel.TabIndex = 1;
|
||||
JournalLabel.Text = "foo.log";
|
||||
//
|
||||
// CancelButton
|
||||
//
|
||||
CancelButton.Location = new Point(308, 56);
|
||||
CancelButton.Name = "CancelButton";
|
||||
CancelButton.Size = new Size(75, 23);
|
||||
CancelButton.TabIndex = 2;
|
||||
CancelButton.Text = "Cancel";
|
||||
CancelButton.UseVisualStyleBackColor = true;
|
||||
CancelButton.Click += CancelButton_Click;
|
||||
//
|
||||
// ReadAllForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(395, 86);
|
||||
Controls.Add(CancelButton);
|
||||
Controls.Add(JournalLabel);
|
||||
Controls.Add(ReadAllProgress);
|
||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||
Name = "ReadAllForm";
|
||||
Text = "Read All In Progress...";
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private ProgressBar ReadAllProgress;
|
||||
private Label JournalLabel;
|
||||
private Button CancelButton;
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
using Observatory.Utils;
|
||||
using System.Text;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
public partial class ReadAllForm : Form
|
||||
{
|
||||
private CancellationTokenSource ReadAllCancel;
|
||||
|
||||
private byte[] eggBytes =
|
||||
{
|
||||
0x52, 0x65, 0x74, 0x69,
|
||||
0x63, 0x75, 0x6C, 0x61,
|
||||
0x74, 0x69, 0x6E, 0x67,
|
||||
0x20, 0x53, 0x70, 0x6C,
|
||||
0x69, 0x6E, 0x65, 0x73,
|
||||
0x2E, 0x2E, 0x2E
|
||||
};
|
||||
|
||||
public ReadAllForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
if (new Random().Next(1, 20) == 20)
|
||||
Text = Encoding.UTF8.GetString(eggBytes);
|
||||
|
||||
var ReadAllJournals = LogMonitor.GetInstance.ReadAllGenerator(out int fileCount);
|
||||
int progressCount = 0;
|
||||
ReadAllCancel = new CancellationTokenSource();
|
||||
HandleCreated += (_,_) =>
|
||||
Task.Run(() =>
|
||||
{
|
||||
foreach (var journal in ReadAllJournals())
|
||||
{
|
||||
if (ReadAllCancel.IsCancellationRequested)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
progressCount++;
|
||||
Invoke(() =>
|
||||
{
|
||||
JournalLabel.Text = journal.ToString();
|
||||
ReadAllProgress.Value = (progressCount * 100) / fileCount;
|
||||
});
|
||||
}
|
||||
Invoke(()=>Close());
|
||||
});
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
ReadAllCancel.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
62
ObservatoryCore/UI/SettingsForm.Designer.cs
generated
62
ObservatoryCore/UI/SettingsForm.Designer.cs
generated
@ -1,62 +0,0 @@
|
||||
namespace Observatory.UI
|
||||
{
|
||||
partial class SettingsForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
PluginSettingsCloseButton = new Button();
|
||||
SuspendLayout();
|
||||
//
|
||||
// PluginSettingsCloseButton
|
||||
//
|
||||
PluginSettingsCloseButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
PluginSettingsCloseButton.Location = new Point(339, 5);
|
||||
PluginSettingsCloseButton.Name = "PluginSettingsCloseButton";
|
||||
PluginSettingsCloseButton.Size = new Size(75, 23);
|
||||
PluginSettingsCloseButton.TabIndex = 0;
|
||||
PluginSettingsCloseButton.Text = "Close";
|
||||
PluginSettingsCloseButton.UseVisualStyleBackColor = true;
|
||||
PluginSettingsCloseButton.Click += PluginSettingsCloseButton_Click;
|
||||
//
|
||||
// SettingsForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(426, 40);
|
||||
Controls.Add(PluginSettingsCloseButton);
|
||||
FormBorderStyle = FormBorderStyle.FixedSingle;
|
||||
Name = "SettingsForm";
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
Text = "SettingsForm";
|
||||
ResumeLayout(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Button PluginSettingsCloseButton;
|
||||
}
|
||||
}
|
@ -1,452 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Observatory.Assets;
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Interfaces;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
public partial class SettingsForm : Form
|
||||
{
|
||||
private readonly IObservatoryPlugin _plugin;
|
||||
private readonly List<int> _colHeight = new List<int>();
|
||||
private int _colWidth = 400;
|
||||
|
||||
public SettingsForm(IObservatoryPlugin plugin)
|
||||
{
|
||||
InitializeComponent();
|
||||
_plugin = plugin;
|
||||
|
||||
// Filtered to only settings without SettingIgnore attribute
|
||||
var attrib = _plugin.Settings.GetType().GetCustomAttribute<SettingSuggestedColumnWidth>();
|
||||
if (attrib != null && attrib.Width > 0)
|
||||
{
|
||||
int minScreenWidth = Screen.AllScreens.Min(s => s.Bounds.Width);
|
||||
_colWidth = Math.Min(attrib.Width, minScreenWidth / 2);
|
||||
}
|
||||
var settings = PluginManagement.PluginManager.GetSettingDisplayNames(plugin.Settings).Where(s => !Attribute.IsDefined(s.Key, typeof(SettingIgnore)));
|
||||
CreateControls(settings);
|
||||
|
||||
|
||||
Text = plugin.Name + " Settings";
|
||||
Icon = Resources.EOCIcon_Presized;
|
||||
ThemeManager.GetInstance.RegisterControl(this);
|
||||
}
|
||||
|
||||
private void CreateControls(IEnumerable<KeyValuePair<PropertyInfo, string>> settings)
|
||||
{
|
||||
bool recentHalfCol = false;
|
||||
|
||||
int settingsHeight = 0;
|
||||
|
||||
var trackBottomEdge = (Control control) =>
|
||||
{
|
||||
var controlBottom = control.Location.Y + control.Height;
|
||||
if (controlBottom > settingsHeight)
|
||||
settingsHeight = controlBottom;
|
||||
};
|
||||
|
||||
|
||||
foreach (var setting in settings)
|
||||
{
|
||||
// Reset the column tracking for checkboxes if this isn't a checkbox or explicitly requested
|
||||
// to start a new grouping of settings.
|
||||
int addedHeight = 35;
|
||||
var newGroup = Attribute.GetCustomAttribute(setting.Key, typeof(SettingNewGroup)) as SettingNewGroup;
|
||||
|
||||
if (setting.Key.PropertyType.Name != "Boolean" || newGroup != null)
|
||||
{
|
||||
if (recentHalfCol) _colHeight.Add(addedHeight);
|
||||
recentHalfCol = false;
|
||||
|
||||
if (newGroup != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(newGroup.Label))
|
||||
{
|
||||
var label = CreateGroupLabel(newGroup.Label);
|
||||
label.Location = GetSettingPosition();
|
||||
|
||||
Controls.Add(label);
|
||||
trackBottomEdge(label);
|
||||
_colHeight.Add(label.Height);
|
||||
}
|
||||
else
|
||||
_colHeight.Add(10);
|
||||
}
|
||||
}
|
||||
|
||||
switch (setting.Key.GetValue(_plugin.Settings))
|
||||
{
|
||||
case bool:
|
||||
var checkBox = CreateBoolSetting(setting);
|
||||
addedHeight = recentHalfCol ? addedHeight : 0;
|
||||
checkBox.Location = GetSettingPosition(recentHalfCol);
|
||||
|
||||
recentHalfCol = !recentHalfCol;
|
||||
|
||||
Controls.Add(checkBox);
|
||||
trackBottomEdge(checkBox);
|
||||
break;
|
||||
case string:
|
||||
var stringLabel = CreateSettingLabel(setting.Value);
|
||||
var textBox = CreateStringSetting(setting.Key);
|
||||
stringLabel.Location = GetSettingPosition();
|
||||
textBox.Location = GetSettingPosition(true);
|
||||
|
||||
Controls.Add(stringLabel);
|
||||
Controls.Add(textBox);
|
||||
trackBottomEdge(textBox);
|
||||
break;
|
||||
case FileInfo:
|
||||
var fileLabel = CreateSettingLabel(setting.Value);
|
||||
var pathTextBox = CreateFilePathSetting(setting.Key);
|
||||
var pathButton = CreateFileBrowseSetting(setting.Key, pathTextBox);
|
||||
|
||||
fileLabel.Location = GetSettingPosition();
|
||||
pathTextBox.Location = GetSettingPosition(true);
|
||||
_colHeight.Add(addedHeight);
|
||||
pathButton.Location = GetSettingPosition(true);
|
||||
|
||||
Controls.Add(fileLabel);
|
||||
Controls.Add(pathTextBox);
|
||||
Controls.Add(pathButton);
|
||||
trackBottomEdge(pathButton);
|
||||
break;
|
||||
case int:
|
||||
// We have two options for integer values:
|
||||
// 1) A slider (explicit by way of the SettingNumericUseSlider attribute and bounded to 0..100 by default)
|
||||
// 2) A numeric up/down (default otherwise, and is unbounded by default).
|
||||
// Bounds for both can be set via the SettingNumericBounds attribute, only the up/down uses Increment.
|
||||
var intLabel = CreateSettingLabel(setting.Value);
|
||||
Control intControl;
|
||||
if (Attribute.IsDefined(setting.Key, typeof(SettingNumericUseSlider)))
|
||||
{
|
||||
intControl = CreateSettingTrackbar(setting.Key);
|
||||
}
|
||||
else
|
||||
{
|
||||
intControl = CreateSettingNumericUpDownForInt(setting.Key);
|
||||
}
|
||||
intLabel.Location = GetSettingPosition();
|
||||
intControl.Location = GetSettingPosition(true);
|
||||
|
||||
addedHeight = intControl.Height + 2;
|
||||
intLabel.Height = intControl.Height;
|
||||
intLabel.TextAlign = ContentAlignment.MiddleRight;
|
||||
|
||||
Controls.Add(intLabel);
|
||||
Controls.Add(intControl);
|
||||
trackBottomEdge(intControl);
|
||||
break;
|
||||
case double:
|
||||
// We have one options for double values:
|
||||
// 1) A numeric up/down (default otherwise, and is unbounded by default).
|
||||
// Bounds can be set via the SettingNumericBounds attribute.
|
||||
var doubleLabel = CreateSettingLabel(setting.Value);
|
||||
Control doubleControl = CreateSettingNumericUpDownForDouble(setting.Key);
|
||||
doubleLabel.Location = GetSettingPosition();
|
||||
doubleControl.Location = GetSettingPosition(true);
|
||||
|
||||
addedHeight = doubleControl.Height + 2;
|
||||
doubleLabel.Height = doubleControl.Height;
|
||||
doubleLabel.TextAlign = ContentAlignment.MiddleRight;
|
||||
|
||||
Controls.Add(doubleLabel);
|
||||
Controls.Add(doubleControl);
|
||||
trackBottomEdge(doubleControl);
|
||||
break;
|
||||
case Action action:
|
||||
var button = CreateSettingButton(setting.Value, action);
|
||||
|
||||
button.Location = GetSettingPosition();
|
||||
|
||||
Controls.Add(button);
|
||||
addedHeight = button.Height;
|
||||
trackBottomEdge(button);
|
||||
break;
|
||||
case Dictionary<string, object> dictSetting:
|
||||
var dictLabel = CreateSettingLabel(setting.Value);
|
||||
var dropdown = CreateSettingDropdown(setting.Key, dictSetting);
|
||||
|
||||
dictLabel.Location = GetSettingPosition();
|
||||
dropdown.Location = GetSettingPosition(true);
|
||||
Controls.Add(dictLabel);
|
||||
Controls.Add(dropdown);
|
||||
trackBottomEdge(dropdown);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
_colHeight.Add(addedHeight);
|
||||
}
|
||||
Height = settingsHeight + 160;
|
||||
Width = _colWidth * 2 + 80;
|
||||
}
|
||||
|
||||
private Point GetSettingPosition(bool secondCol = false)
|
||||
{
|
||||
return new Point(20 + (secondCol ? _colWidth + 20 : 0), 15 + _colHeight.Sum());
|
||||
}
|
||||
|
||||
|
||||
private Label CreateSettingLabel(string settingName)
|
||||
{
|
||||
Label label = new()
|
||||
{
|
||||
Text = settingName + ": ",
|
||||
TextAlign = System.Drawing.ContentAlignment.MiddleRight,
|
||||
Width = _colWidth,
|
||||
ForeColor = Color.LightGray
|
||||
};
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
private Label CreateGroupLabel(string groupLabel)
|
||||
{
|
||||
Label label = new()
|
||||
{
|
||||
Text = groupLabel,
|
||||
TextAlign = System.Drawing.ContentAlignment.MiddleLeft,
|
||||
Width = _colWidth * 2,
|
||||
ForeColor = Color.LightGray,
|
||||
};
|
||||
label.Font = new Font(label.Font.FontFamily, label.Font.Size + 1, FontStyle.Bold);
|
||||
label.Height += 10; // Add spacing.
|
||||
return label;
|
||||
}
|
||||
|
||||
private ComboBox CreateSettingDropdown(PropertyInfo setting, Dictionary<string, object> dropdownItems)
|
||||
{
|
||||
var backingValueName = (SettingBackingValue?)Attribute.GetCustomAttribute(setting, typeof(SettingBackingValue));
|
||||
|
||||
var backingValue = from s in PluginManagement.PluginManager.GetSettingDisplayNames(_plugin.Settings)
|
||||
where s.Value == backingValueName?.BackingProperty
|
||||
select s.Key;
|
||||
|
||||
if (backingValue.Count() != 1)
|
||||
throw new($"{_plugin.ShortName}: Dictionary settings must have exactly one backing value.");
|
||||
|
||||
ComboBox comboBox = new()
|
||||
{
|
||||
Width = _colWidth,
|
||||
DropDownStyle = ComboBoxStyle.DropDownList
|
||||
};
|
||||
|
||||
comboBox.Items.AddRange(dropdownItems.OrderBy(s => s.Key).Select(s => s.Key).ToArray());
|
||||
|
||||
string? currentSelection = backingValue.First().GetValue(_plugin.Settings)?.ToString();
|
||||
|
||||
if (currentSelection?.Length > 0)
|
||||
{
|
||||
comboBox.SelectedItem = currentSelection;
|
||||
}
|
||||
|
||||
comboBox.SelectedValueChanged += (sender, e) =>
|
||||
{
|
||||
backingValue.First().SetValue(_plugin.Settings, comboBox.SelectedItem.ToString());
|
||||
SaveSettings();
|
||||
};
|
||||
|
||||
return comboBox;
|
||||
}
|
||||
|
||||
private Button CreateSettingButton(string settingName, Action action)
|
||||
{
|
||||
Button button = new()
|
||||
{
|
||||
Text = settingName,
|
||||
Width = Convert.ToInt32(_colWidth * 0.8),
|
||||
Height = 35,
|
||||
};
|
||||
|
||||
button.Click += (sender, e) =>
|
||||
{
|
||||
action.Invoke();
|
||||
SaveSettings();
|
||||
};
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private TrackBar CreateSettingTrackbar(PropertyInfo setting)
|
||||
{
|
||||
SettingNumericBounds? bounds = (SettingNumericBounds?)Attribute.GetCustomAttribute(setting, typeof(SettingNumericBounds));
|
||||
|
||||
var minBound = Convert.ToInt32(bounds?.Minimum ?? 0);
|
||||
var maxBound = Convert.ToInt32(bounds?.Maximum ?? 100);
|
||||
|
||||
var tickFrequency = maxBound - minBound >= 20 ? (maxBound - minBound) / 10 : 1;
|
||||
|
||||
TrackBar trackBar = new()
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
TickStyle = TickStyle.Both,
|
||||
TickFrequency = tickFrequency,
|
||||
Width = _colWidth,
|
||||
Minimum = minBound,
|
||||
Maximum = maxBound,
|
||||
};
|
||||
|
||||
trackBar.Value = (int?)setting.GetValue(_plugin.Settings) ?? 0;
|
||||
|
||||
trackBar.ValueChanged += (sender, e) =>
|
||||
{
|
||||
setting.SetValue(_plugin.Settings, trackBar.Value);
|
||||
SaveSettings();
|
||||
};
|
||||
|
||||
return trackBar;
|
||||
}
|
||||
|
||||
private NumericUpDown CreateSettingNumericUpDownForInt(PropertyInfo setting)
|
||||
{
|
||||
SettingNumericBounds? bounds = (SettingNumericBounds?)Attribute.GetCustomAttribute(setting, typeof(SettingNumericBounds));
|
||||
NumericUpDown numericUpDown = new()
|
||||
{
|
||||
Width = _colWidth,
|
||||
Minimum = Convert.ToInt32(bounds?.Minimum ?? Int32.MinValue),
|
||||
Maximum = Convert.ToInt32(bounds?.Maximum ?? Int32.MaxValue),
|
||||
Increment = Convert.ToInt32(bounds?.Increment ?? 1)
|
||||
};
|
||||
|
||||
numericUpDown.Value = (int?)setting.GetValue(_plugin.Settings) ?? 0;
|
||||
numericUpDown.ValueChanged += (sender, e) =>
|
||||
{
|
||||
setting.SetValue(_plugin.Settings, Convert.ToInt32(numericUpDown.Value));
|
||||
SaveSettings();
|
||||
};
|
||||
|
||||
return numericUpDown;
|
||||
}
|
||||
|
||||
private NumericUpDown CreateSettingNumericUpDownForDouble(PropertyInfo setting)
|
||||
{
|
||||
SettingNumericBounds? bounds = (SettingNumericBounds?)Attribute.GetCustomAttribute(setting, typeof(SettingNumericBounds));
|
||||
NumericUpDown numericUpDown = new()
|
||||
{
|
||||
Width = _colWidth,
|
||||
Minimum = Convert.ToDecimal(bounds?.Minimum ?? Double.MinValue),
|
||||
Maximum = Convert.ToDecimal(bounds?.Maximum ?? Double.MaxValue),
|
||||
Increment = Convert.ToDecimal(bounds?.Increment ?? 1.0),
|
||||
DecimalPlaces = bounds?.Precision ?? 1,
|
||||
};
|
||||
|
||||
numericUpDown.Value = Convert.ToDecimal(setting.GetValue(_plugin.Settings) ?? 0);
|
||||
numericUpDown.ValueChanged += (sender, e) =>
|
||||
{
|
||||
setting.SetValue(_plugin.Settings, Convert.ToDouble(numericUpDown.Value));
|
||||
SaveSettings();
|
||||
};
|
||||
|
||||
return numericUpDown;
|
||||
}
|
||||
|
||||
private CheckBox CreateBoolSetting(KeyValuePair<PropertyInfo, string> setting)
|
||||
{
|
||||
CheckBox checkBox = new()
|
||||
{
|
||||
Text = setting.Value,
|
||||
TextAlign = System.Drawing.ContentAlignment.MiddleLeft,
|
||||
Checked = (bool?)setting.Key.GetValue(_plugin.Settings) ?? false,
|
||||
Height = 30,
|
||||
Width = _colWidth,
|
||||
ForeColor = Color.LightGray
|
||||
};
|
||||
|
||||
checkBox.CheckedChanged += (sender, e) =>
|
||||
{
|
||||
setting.Key.SetValue(_plugin.Settings, checkBox.Checked);
|
||||
SaveSettings();
|
||||
};
|
||||
|
||||
return checkBox;
|
||||
}
|
||||
|
||||
private TextBox CreateStringSetting(PropertyInfo setting)
|
||||
{
|
||||
TextBox textBox = new()
|
||||
{
|
||||
Text = (setting.GetValue(_plugin.Settings) ?? String.Empty).ToString(),
|
||||
Width = _colWidth,
|
||||
};
|
||||
|
||||
textBox.TextChanged += (object? sender, EventArgs e) =>
|
||||
{
|
||||
setting.SetValue(_plugin.Settings, textBox.Text);
|
||||
SaveSettings();
|
||||
};
|
||||
|
||||
return textBox;
|
||||
}
|
||||
|
||||
private TextBox CreateFilePathSetting(PropertyInfo setting)
|
||||
{
|
||||
var fileInfo = (FileInfo?)setting.GetValue(_plugin.Settings);
|
||||
|
||||
TextBox textBox = new()
|
||||
{
|
||||
Text = fileInfo?.FullName ?? string.Empty,
|
||||
Width = _colWidth,
|
||||
};
|
||||
|
||||
textBox.TextChanged += (object? sender, EventArgs e) =>
|
||||
{
|
||||
setting.SetValue(_plugin.Settings, new FileInfo(textBox.Text));
|
||||
SaveSettings();
|
||||
};
|
||||
|
||||
return textBox;
|
||||
}
|
||||
|
||||
private Button CreateFileBrowseSetting(PropertyInfo setting, TextBox textBox)
|
||||
{
|
||||
Button button = new()
|
||||
{
|
||||
Text = "Browse",
|
||||
Height = 35,
|
||||
Width = _colWidth / 2,
|
||||
};
|
||||
|
||||
button.Click += (object? sender, EventArgs e) =>
|
||||
{
|
||||
var currentDir = ((FileInfo?)setting.GetValue(_plugin.Settings))?.DirectoryName;
|
||||
|
||||
OpenFileDialog ofd = new OpenFileDialog()
|
||||
{
|
||||
Title = "Select File...",
|
||||
Filter = "Lua files (*.lua)|*.lua|All files (*.*)|*.*",
|
||||
FilterIndex = 0,
|
||||
InitialDirectory = currentDir ?? Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
|
||||
};
|
||||
|
||||
var browseResult = ofd.ShowDialog();
|
||||
|
||||
if (browseResult == DialogResult.OK)
|
||||
{
|
||||
textBox.Text = ofd.FileName;
|
||||
}
|
||||
};
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
PluginManagement.PluginManager.GetInstance.SaveSettings(_plugin, _plugin.Settings);
|
||||
}
|
||||
|
||||
private void PluginSettingsCloseButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
@ -1,163 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
internal class ThemeManager
|
||||
{
|
||||
public static ThemeManager GetInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
return _instance.Value;
|
||||
}
|
||||
}
|
||||
private static HashSet<string> _excludedControlNames = new()
|
||||
{
|
||||
"ColourButton",
|
||||
};
|
||||
private static readonly Lazy<ThemeManager> _instance = new(() => new ThemeManager());
|
||||
private bool _init;
|
||||
|
||||
private ThemeManager()
|
||||
{
|
||||
_init = true;
|
||||
controls = new List<Control>();
|
||||
Themes = new()
|
||||
{
|
||||
{ "Dark", DarkTheme },
|
||||
{ "Light", LightTheme }
|
||||
};
|
||||
SelectedTheme = "Dark";
|
||||
}
|
||||
|
||||
private readonly List<Control> controls;
|
||||
|
||||
public List<string> GetThemes
|
||||
{
|
||||
get => Themes.Keys.ToList();
|
||||
}
|
||||
|
||||
public string CurrentTheme
|
||||
{
|
||||
get => SelectedTheme;
|
||||
|
||||
set
|
||||
{
|
||||
if (Themes.ContainsKey(value))
|
||||
{
|
||||
SelectedTheme = value;
|
||||
foreach (var control in controls)
|
||||
{
|
||||
ApplyTheme(control);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterControl(Control control)
|
||||
{
|
||||
// First time registering a control, build the "light" theme based
|
||||
// on defaults.
|
||||
if (_init)
|
||||
{
|
||||
SaveTheme(control, LightTheme);
|
||||
_init = false;
|
||||
}
|
||||
|
||||
controls.Add(control);
|
||||
ApplyTheme(control);
|
||||
if (control.HasChildren)
|
||||
foreach (Control child in control.Controls)
|
||||
{
|
||||
if (_excludedControlNames.Contains(child.Name)) continue;
|
||||
RegisterControl(child);
|
||||
}
|
||||
}
|
||||
|
||||
// This doesn't inherit from Control? Seriously?
|
||||
public void RegisterControl(ToolStripMenuItem toolStripMenuItem)
|
||||
{
|
||||
ApplyTheme(toolStripMenuItem);
|
||||
}
|
||||
|
||||
private void SaveTheme(Control control, Dictionary<string, Color> theme)
|
||||
{
|
||||
Control rootControl = control;
|
||||
while (rootControl.Parent != null)
|
||||
{
|
||||
rootControl = rootControl.Parent;
|
||||
}
|
||||
|
||||
SaveThemeControl(rootControl, theme);
|
||||
var themeJson = System.Text.Json.JsonSerializer.Serialize(DarkTheme);
|
||||
}
|
||||
|
||||
private void SaveThemeControl(Control control, Dictionary<string, Color> theme)
|
||||
{
|
||||
var properties = control.GetType().GetProperties();
|
||||
var colorProperties = properties.Where(p => p.PropertyType == typeof(Color));
|
||||
|
||||
foreach (var colorProperty in colorProperties)
|
||||
{
|
||||
string controlKey = control.GetType().Name + "." + colorProperty.Name;
|
||||
if (!theme.ContainsKey(controlKey))
|
||||
{
|
||||
theme.Add(controlKey, (Color)colorProperty.GetValue(control)!);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Control child in control.Controls)
|
||||
{
|
||||
SaveThemeControl(child, theme);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeRegisterControl(Control control)
|
||||
{
|
||||
if (control.HasChildren)
|
||||
foreach (Control child in control.Controls)
|
||||
{
|
||||
DeRegisterControl(child);
|
||||
}
|
||||
controls.Remove(control);
|
||||
}
|
||||
|
||||
private void ApplyTheme(Object control)
|
||||
{
|
||||
var controlType = control.GetType();
|
||||
|
||||
var theme = Themes.ContainsKey(SelectedTheme)
|
||||
? Themes[SelectedTheme] : Themes["Light"];
|
||||
|
||||
foreach (var property in controlType.GetProperties().Where(p => p.PropertyType == typeof(Color)))
|
||||
{
|
||||
string themeControl = Themes[SelectedTheme].ContainsKey(controlType.Name + "." + property.Name)
|
||||
? controlType.Name
|
||||
: "Default";
|
||||
|
||||
if (Themes[SelectedTheme].ContainsKey(themeControl + "." + property.Name))
|
||||
property.SetValue(control, Themes[SelectedTheme][themeControl + "." + property.Name]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Dictionary<string, Dictionary<string, Color>> Themes;
|
||||
|
||||
private string SelectedTheme;
|
||||
|
||||
private Dictionary<string, Color> LightTheme = new Dictionary<string, Color>();
|
||||
|
||||
static private Dictionary<string, Color> DarkTheme = new Dictionary<string, Color>
|
||||
{
|
||||
{"Default.ForeColor", Color.LightGray },
|
||||
{"Default.BackColor", Color.Black },
|
||||
{"Button.ForeColor", Color.LightGray },
|
||||
{"Button.BackColor", Color.DimGray }
|
||||
};
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Observatory.UI
|
||||
{
|
||||
internal class UIHelper
|
||||
{
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using NAudio.Wave;
|
||||
|
||||
namespace Observatory.Utils
|
||||
{
|
||||
internal static class AudioHandler
|
||||
{
|
||||
internal static async Task PlayFile(string filePath)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
using (var file = new AudioFileReader(filePath))
|
||||
using (var output = new WaveOutEvent())
|
||||
{
|
||||
output.Init(file);
|
||||
output.Play();
|
||||
|
||||
while (output.PlaybackState == PlaybackState.Playing)
|
||||
{
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Observatory.Utils
|
||||
{
|
||||
public static class ErrorReporter
|
||||
{
|
||||
public static void ShowErrorPopup(string title, List<(string error, string detail)> errorList)
|
||||
{
|
||||
// Limit number of errors displayed.
|
||||
StringBuilder displayMessage = new();
|
||||
displayMessage.AppendLine($"{errorList.Count} error{(errorList.Count > 1 ? "s" : string.Empty)} encountered.");
|
||||
var firstFiveErrors = errorList.Take(Math.Min(5, errorList.Count)).Select(e => e.error);
|
||||
displayMessage.AppendJoin(Environment.NewLine, firstFiveErrors);
|
||||
displayMessage.AppendLine();
|
||||
displayMessage.Append("Full error details logged to ObservatoryErrorLog file in your documents folder.");
|
||||
|
||||
//TODO: Winform error popup
|
||||
|
||||
// Log entirety of errors out to file.
|
||||
var timestamp = DateTime.Now.ToString("G");
|
||||
StringBuilder errorLog = new();
|
||||
foreach (var error in errorList)
|
||||
{
|
||||
errorLog.AppendLine($"[{timestamp}]:");
|
||||
errorLog.AppendLine($"{error.error} - {error.detail}");
|
||||
errorLog.AppendLine();
|
||||
}
|
||||
|
||||
var docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
||||
File.AppendAllText(docPath + Path.DirectorySeparatorChar + "ObservatoryErrorLog.txt", errorLog.ToString());
|
||||
|
||||
errorList.Clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Observatory.Utils
|
||||
{
|
||||
public sealed class HttpClient
|
||||
{
|
||||
private HttpClient()
|
||||
{ }
|
||||
|
||||
private static readonly Lazy<System.Net.Http.HttpClient> lazy = new Lazy<System.Net.Http.HttpClient>(() => new System.Net.Http.HttpClient());
|
||||
|
||||
public static System.Net.Http.HttpClient Client
|
||||
{
|
||||
get
|
||||
{
|
||||
return lazy.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetString(string url)
|
||||
{
|
||||
return lazy.Value.GetStringAsync(url).Result;
|
||||
}
|
||||
|
||||
public static HttpResponseMessage SendRequest(HttpRequestMessage request)
|
||||
{
|
||||
return lazy.Value.SendAsync(request).Result;
|
||||
}
|
||||
|
||||
public static Task<HttpResponseMessage> SendRequestAsync(HttpRequestMessage request)
|
||||
{
|
||||
return lazy.Value.SendAsync(request);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Observatory.Utils
|
||||
{
|
||||
internal static class SettingsManager
|
||||
{
|
||||
internal static void Save()
|
||||
{
|
||||
#if DEBUG || RELEASE
|
||||
Properties.Core.Default.Save();
|
||||
#elif PORTABLE
|
||||
|
||||
Dictionary<string, object?> settings = new();
|
||||
|
||||
foreach (PropertyInfo property in Properties.Core.Default.GetType().GetProperties())
|
||||
{
|
||||
if (property.CanRead && property.CanWrite && !property.GetIndexParameters().Any())
|
||||
settings.Add(
|
||||
property.Name,
|
||||
property.GetValue(Properties.Core.Default)
|
||||
);
|
||||
}
|
||||
|
||||
string serializedSettings = JsonSerializer.Serialize(settings, new JsonSerializerOptions()
|
||||
{
|
||||
ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.Preserve,
|
||||
|
||||
});
|
||||
File.WriteAllText("Observatory.config", serializedSettings);
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void Load()
|
||||
{
|
||||
#if PORTABLE
|
||||
if (File.Exists("Observatory.config"))
|
||||
{
|
||||
string savedSettings = File.ReadAllText("Observatory.config");
|
||||
Dictionary<string, object?>? settings = JsonSerializer.Deserialize<Dictionary<string, object?>>(savedSettings);
|
||||
if (settings != null)
|
||||
{
|
||||
var properties = Properties.Core.Default.GetType().GetProperties();
|
||||
|
||||
foreach (var savedProperty in settings)
|
||||
{
|
||||
|
||||
var currentProperty = properties.Where(p => p.Name == savedProperty.Key);
|
||||
if (currentProperty.Any())
|
||||
{
|
||||
JsonElement? value = (JsonElement?)savedProperty.Value;
|
||||
var deserializedValue = value?.Deserialize(currentProperty.First().PropertyType);
|
||||
currentProperty.First().SetValue(Properties.Core.Default, deserializedValue);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.3.32922.545
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ObservatoryBotanist", "..\ObservatoryBotanist\ObservatoryBotanist.csproj", "{498F7360-D443-4D64-895C-9EAB5570D019}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ObservatoryCore", "..\ObservatoryCore\ObservatoryCore.csproj", "{0E1C4F16-858E-4E53-948A-77D81A8F3395}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ObservatoryExplorer", "..\ObservatoryExplorer\ObservatoryExplorer.csproj", "{E0FCF2A2-BF56-4F4D-836B-92A0E8269192}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ObservatoryFramework", "..\ObservatoryFramework\ObservatoryFramework.csproj", "{27ABA3B7-AB3C-465F-BA40-4F06BD803811}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ObservatoryHerald", "..\ObservatoryHerald\ObservatoryHerald.csproj", "{BC57225F-D89B-4853-A816-9AB4865E7AC5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Portable|Any CPU = Portable|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{498F7360-D443-4D64-895C-9EAB5570D019}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{498F7360-D443-4D64-895C-9EAB5570D019}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{498F7360-D443-4D64-895C-9EAB5570D019}.Portable|Any CPU.ActiveCfg = Portable|Any CPU
|
||||
{498F7360-D443-4D64-895C-9EAB5570D019}.Portable|Any CPU.Build.0 = Portable|Any CPU
|
||||
{498F7360-D443-4D64-895C-9EAB5570D019}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{498F7360-D443-4D64-895C-9EAB5570D019}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0E1C4F16-858E-4E53-948A-77D81A8F3395}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0E1C4F16-858E-4E53-948A-77D81A8F3395}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0E1C4F16-858E-4E53-948A-77D81A8F3395}.Portable|Any CPU.ActiveCfg = Portable|Any CPU
|
||||
{0E1C4F16-858E-4E53-948A-77D81A8F3395}.Portable|Any CPU.Build.0 = Portable|Any CPU
|
||||
{0E1C4F16-858E-4E53-948A-77D81A8F3395}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0E1C4F16-858E-4E53-948A-77D81A8F3395}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Portable|Any CPU.ActiveCfg = Portable|Any CPU
|
||||
{E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Portable|Any CPU.Build.0 = Portable|Any CPU
|
||||
{E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{27ABA3B7-AB3C-465F-BA40-4F06BD803811}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{27ABA3B7-AB3C-465F-BA40-4F06BD803811}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{27ABA3B7-AB3C-465F-BA40-4F06BD803811}.Portable|Any CPU.ActiveCfg = Portable|Any CPU
|
||||
{27ABA3B7-AB3C-465F-BA40-4F06BD803811}.Portable|Any CPU.Build.0 = Portable|Any CPU
|
||||
{27ABA3B7-AB3C-465F-BA40-4F06BD803811}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{27ABA3B7-AB3C-465F-BA40-4F06BD803811}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BC57225F-D89B-4853-A816-9AB4865E7AC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BC57225F-D89B-4853-A816-9AB4865E7AC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BC57225F-D89B-4853-A816-9AB4865E7AC5}.Portable|Any CPU.ActiveCfg = Portable|Any CPU
|
||||
{BC57225F-D89B-4853-A816-9AB4865E7AC5}.Portable|Any CPU.Build.0 = Portable|Any CPU
|
||||
{BC57225F-D89B-4853-A816-9AB4865E7AC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BC57225F-D89B-4853-A816-9AB4865E7AC5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {F41B8681-A5D9-4167-9938-56DE88024000}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Observatory.Explorer
|
||||
{
|
||||
internal class CriteriaLoadException : Exception
|
||||
{
|
||||
public CriteriaLoadException(string message, string script)
|
||||
{
|
||||
Message = message;
|
||||
OriginalScript = script;
|
||||
}
|
||||
|
||||
new public readonly string Message;
|
||||
public readonly string OriginalScript;
|
||||
}
|
||||
}
|
@ -1,386 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Observatory.Framework.Files.Journal;
|
||||
using NLua;
|
||||
using System.Linq;
|
||||
|
||||
namespace Observatory.Explorer
|
||||
{
|
||||
internal class CustomCriteriaManager
|
||||
{
|
||||
private Lua LuaState;
|
||||
private Dictionary<String,LuaFunction> CriteriaFunctions;
|
||||
private Dictionary<string, string> CriteriaWithErrors = new();
|
||||
Action<Exception, String> ErrorLogger;
|
||||
private uint ScanCount;
|
||||
|
||||
public CustomCriteriaManager(Action<Exception, String> errorLogger)
|
||||
{
|
||||
ErrorLogger = errorLogger;
|
||||
CriteriaFunctions = new();
|
||||
ScanCount = 0;
|
||||
}
|
||||
|
||||
public void RefreshCriteria(string criteriaPath)
|
||||
{
|
||||
LuaState = new();
|
||||
LuaState.State.Encoding = Encoding.UTF8;
|
||||
LuaState.LoadCLRPackage();
|
||||
|
||||
#region Iterators
|
||||
|
||||
// Empty function for nil iterators
|
||||
LuaState.DoString("function nil_iterator() end");
|
||||
|
||||
//Materials and AtmosphereComposition
|
||||
LuaState.DoString(@"
|
||||
function materials (material_list)
|
||||
if material_list then
|
||||
local i = 0
|
||||
local count = material_list.Count
|
||||
return function ()
|
||||
i = i + 1
|
||||
if i <= count then
|
||||
return { name = material_list[i - 1].Name, percent = material_list[i - 1].Percent }
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil_iterator
|
||||
end
|
||||
end");
|
||||
|
||||
//Rings - internal filterable iterator
|
||||
LuaState.DoString(@"
|
||||
function _ringsFiltered (ring_list, filter_by)
|
||||
if ring_list then
|
||||
local i = 0
|
||||
local count = ring_list.Count
|
||||
return function ()
|
||||
i = i + 1
|
||||
while i <= count do
|
||||
local ring = ring_list[i - 1]
|
||||
if (filter_by == nil or string.find(ring.Name, filter_by)) then
|
||||
return { name = ring.Name, ringclass = ring.RingClass, massmt = ring.MassMT, innerrad = ring.InnerRad, outerrad = ring.OuterRad }
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil_iterator
|
||||
end
|
||||
end");
|
||||
|
||||
//Rings - internal filterable hasX check
|
||||
LuaState.DoString(@"
|
||||
function _hasRingsFiltered (ring_list, filter_by)
|
||||
if ring_list then
|
||||
local i = 0
|
||||
local count = ring_list.Count
|
||||
while i < count do
|
||||
if string.find(ring_list[i].Name, filter_by) then
|
||||
return true
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
return false
|
||||
end");
|
||||
|
||||
//Rings - iterate all - nil filter
|
||||
LuaState.DoString(@"
|
||||
function rings (ring_list)
|
||||
return _ringsFiltered(ring_list, nil)
|
||||
end");
|
||||
|
||||
//Rings - iterate proper rings only
|
||||
LuaState.DoString(@"
|
||||
function ringsOnly (ring_list)
|
||||
return _ringsFiltered(ring_list, 'Ring')
|
||||
end");
|
||||
|
||||
//Rings - iterate belts only
|
||||
LuaState.DoString(@"
|
||||
function beltsOnly (ring_list)
|
||||
return _ringsFiltered(ring_list, 'Belt')
|
||||
end");
|
||||
|
||||
//Bodies in system
|
||||
LuaState.DoString(@"
|
||||
function bodies (system_list)
|
||||
if system_list then
|
||||
local i = 0
|
||||
local count = system_list.Count
|
||||
return function ()
|
||||
i = i + 1
|
||||
if i <= count then
|
||||
return system_list[i - 1]
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil_iterator
|
||||
end
|
||||
end");
|
||||
|
||||
//Parent bodies
|
||||
LuaState.DoString(@"
|
||||
function allparents (parent_list)
|
||||
if parent_list then
|
||||
local i = 0
|
||||
local count
|
||||
if parent_list then count = parent_list.Count else count = 0 end
|
||||
return function ()
|
||||
i = i + 1
|
||||
if i <= count then
|
||||
return { parenttype = parent_list[i - 1].ParentType, body = parent_list[i - 1].Body, scan = parent_list[i - 1].Scan }
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil_iterator
|
||||
end
|
||||
end");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Convenience Functions
|
||||
|
||||
//Rings - has > 0 belts
|
||||
LuaState.DoString(@"
|
||||
function hasBelts (ring_list)
|
||||
return _hasRingsFiltered(ring_list, 'Belt')
|
||||
end");
|
||||
|
||||
//Rings - has > 0 proper rings
|
||||
LuaState.DoString(@"
|
||||
function hasRings (ring_list)
|
||||
return _hasRingsFiltered(ring_list, 'Ring')
|
||||
end");
|
||||
|
||||
LuaState.DoString(@"
|
||||
function isStar (scan)
|
||||
return scan.StarType and scan.StarType ~= ''
|
||||
end");
|
||||
|
||||
LuaState.DoString(@"
|
||||
function isPlanet (scan)
|
||||
return scan.PlanetClass ~= nil
|
||||
end");
|
||||
|
||||
LuaState.DoString(@"
|
||||
function hasAtmosphere (scan)
|
||||
return scan.AtmosphereComposition ~= nil
|
||||
end");
|
||||
|
||||
LuaState.DoString(@"
|
||||
function hasLandableAtmosphere (scan)
|
||||
return scan.Landable and scan.AtmosphereComposition ~= nil
|
||||
end");
|
||||
|
||||
#endregion
|
||||
|
||||
CriteriaFunctions.Clear();
|
||||
CriteriaWithErrors.Clear();
|
||||
var criteria = File.Exists(criteriaPath) ? File.ReadAllLines(criteriaPath) : Array.Empty<string>();
|
||||
StringBuilder script = new();
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < criteria.Length; i++)
|
||||
{
|
||||
if (criteria[i].Trim().StartsWith("::"))
|
||||
{
|
||||
string scriptDescription = criteria[i].Trim().Replace("::", string.Empty);
|
||||
if (scriptDescription.ToLower() == "criteria" || scriptDescription.ToLower().StartsWith("criteria="))
|
||||
{
|
||||
string functionName = $"Criteria{i}";
|
||||
script.AppendLine($"function {functionName} (scan, parents, system, biosignals, geosignals)");
|
||||
i++;
|
||||
do
|
||||
{
|
||||
if (i >= criteria.Length)
|
||||
throw new Exception("Unterminated multi-line criteria.\r\nAre you missing an ::End::?");
|
||||
|
||||
script.AppendLine(criteria[i]);
|
||||
i++;
|
||||
} while (!criteria[i].Trim().ToLower().StartsWith("::end::"));
|
||||
script.AppendLine("end");
|
||||
|
||||
LuaState.DoString(script.ToString());
|
||||
CriteriaFunctions.Add(GetUniqueDescription(functionName, scriptDescription), LuaState[functionName] as LuaFunction);
|
||||
script.Clear();
|
||||
}
|
||||
else if (scriptDescription.ToLower() == "global")
|
||||
{
|
||||
i++;
|
||||
do
|
||||
{
|
||||
script.AppendLine(criteria[i]);
|
||||
i++;
|
||||
} while (!criteria[i].Trim().ToLower().StartsWith("::end::"));
|
||||
LuaState.DoString(script.ToString());
|
||||
script.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
|
||||
string functionName = $"Criteria{i}";
|
||||
|
||||
script.AppendLine($"function {functionName} (scan, parents, system, biosignals, geosignals)");
|
||||
script.AppendLine($" local result = {criteria[i]}");
|
||||
script.AppendLine(" local detail = ''");
|
||||
|
||||
if (criteria.Length > i + 1 && criteria[i + 1].Trim().ToLower() == "::detail::")
|
||||
{
|
||||
i++; i++;
|
||||
// Gate detail evaluation on result to allow safe use of criteria-checked values in detail string.
|
||||
script.AppendLine(" if result then");
|
||||
script.AppendLine($" detail = {criteria[i]}");
|
||||
script.AppendLine(" end");
|
||||
}
|
||||
|
||||
script.AppendLine($" return result, '{scriptDescription}', detail");
|
||||
script.AppendLine("end");
|
||||
|
||||
LuaState.DoString(script.ToString());
|
||||
CriteriaFunctions.Add(GetUniqueDescription(functionName, scriptDescription), LuaState[functionName] as LuaFunction);
|
||||
script.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
string originalScript = script.ToString().Trim();
|
||||
|
||||
originalScript = originalScript.Remove(originalScript.LastIndexOf(Environment.NewLine));
|
||||
originalScript = originalScript[(originalScript.IndexOf(Environment.NewLine) + Environment.NewLine.Length)..];
|
||||
originalScript = originalScript.Replace('\t', ' ');
|
||||
|
||||
StringBuilder errorDetail = new();
|
||||
errorDetail.AppendLine("Error Reading Custom Criteria File:")
|
||||
.AppendLine(originalScript)
|
||||
.AppendLine("To correct this problem, make changes to the Lua source file, save it and either re-run read-all or scan another body. It will be automatically reloaded."); ErrorLogger(e, errorDetail.ToString());
|
||||
CriteriaFunctions.Clear(); // Don't use partial parse.
|
||||
throw new CriteriaLoadException(e.Message, originalScript);
|
||||
}
|
||||
}
|
||||
|
||||
public List<(string, string, bool)> CheckInterest(Scan scan, Dictionary<ulong, Dictionary<int, Scan>> scanHistory, Dictionary<ulong, Dictionary<int, FSSBodySignals>> signalHistory, ExplorerSettings settings)
|
||||
{
|
||||
List<(string, string, bool)> results = new();
|
||||
ScanCount++;
|
||||
|
||||
foreach (var criteriaFunction in CriteriaFunctions)
|
||||
{
|
||||
// Skip criteria which have previously thrown an error. We can't remove them from the dictionary while iterating it.
|
||||
if (CriteriaWithErrors.ContainsKey(criteriaFunction.Key)) continue;
|
||||
|
||||
var scanList = scanHistory[scan.SystemAddress].Values.ToList();
|
||||
|
||||
int bioSignals;
|
||||
int geoSignals;
|
||||
|
||||
if (signalHistory.ContainsKey(scan.SystemAddress) && signalHistory[scan.SystemAddress].ContainsKey(scan.BodyID))
|
||||
{
|
||||
bioSignals = signalHistory[scan.SystemAddress][scan.BodyID].Signals.Where(s => s.Type == "$SAA_SignalType_Biological;").Select(s => s.Count).FirstOrDefault();
|
||||
geoSignals = signalHistory[scan.SystemAddress][scan.BodyID].Signals.Where(s => s.Type == "$SAA_SignalType_Geological;").Select(s => s.Count).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
bioSignals = 0;
|
||||
geoSignals = 0;
|
||||
}
|
||||
|
||||
|
||||
List<Parent> parents;
|
||||
|
||||
if (scan.Parent != null)
|
||||
{
|
||||
parents = new();
|
||||
foreach (var parent in scan.Parent)
|
||||
{
|
||||
var parentScan = scanList.Where(s => s.BodyID == parent.Body);
|
||||
|
||||
parents.Add(new Parent()
|
||||
{
|
||||
ParentType = parent.ParentType.ToString(),
|
||||
Body = parent.Body,
|
||||
Scan = parentScan.Any() ? parentScan.First() : null
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
parents = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = criteriaFunction.Value.Call(scan, parents, scanList, bioSignals, geoSignals);
|
||||
if (result.Length == 3 && ((bool?)result[0]).GetValueOrDefault(false))
|
||||
{
|
||||
results.Add((result[1].ToString(), result[2].ToString(), false));
|
||||
}
|
||||
else if (result.Length == 2)
|
||||
{
|
||||
results.Add((result[0].ToString(), result[1].ToString(), false));
|
||||
}
|
||||
}
|
||||
catch (NLua.Exceptions.LuaScriptException e)
|
||||
{
|
||||
results.Add((e.Message, scan.Json, false));
|
||||
|
||||
StringBuilder errorDetail = new();
|
||||
errorDetail.AppendLine($"while processing custom criteria '{criteriaFunction.Key}' on scan:")
|
||||
.AppendLine(scan.Json)
|
||||
.AppendLine("To correct this problem, make changes to the Lua source file, save it and either re-run read-all or scan another body. It will be automatically reloaded.");
|
||||
ErrorLogger(e, errorDetail.ToString());
|
||||
CriteriaWithErrors.Add(criteriaFunction.Key, e.Message + Environment.NewLine + errorDetail.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any erroring criteria. They will be repopulated next time the file is parsed.
|
||||
if (CriteriaWithErrors.Count > 0)
|
||||
{
|
||||
foreach (var criteriaKey in CriteriaWithErrors.Keys)
|
||||
{
|
||||
if (CriteriaFunctions.ContainsKey(criteriaKey)) CriteriaFunctions.Remove(criteriaKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (ScanCount > 99)
|
||||
{
|
||||
ScanCount = 0;
|
||||
LuaGC();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private string GetUniqueDescription(string functionName, string scriptDescription)
|
||||
{
|
||||
string uniqueDescription = functionName;
|
||||
if (scriptDescription.ToLower().StartsWith("criteria="))
|
||||
{
|
||||
uniqueDescription += scriptDescription.Replace("criteria=", "=", StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
return uniqueDescription;
|
||||
}
|
||||
|
||||
private void LuaGC()
|
||||
{
|
||||
LuaState?.DoString("collectgarbage()");
|
||||
}
|
||||
|
||||
internal class Parent
|
||||
{
|
||||
public string ParentType;
|
||||
public int Body;
|
||||
public Scan Scan;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,392 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Observatory.Framework.Files.Journal;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Observatory.Explorer
|
||||
{
|
||||
internal static class DefaultCriteria
|
||||
{
|
||||
public static List<(string Description, string Detail, bool SystemWide)> CheckInterest(Scan scan, Dictionary<ulong, Dictionary<int, Scan>> scanHistory, Dictionary<ulong, Dictionary<int, FSSBodySignals>> signalHistory, ExplorerSettings settings)
|
||||
{
|
||||
List<(string, string, bool)> results = new();
|
||||
TextInfo textInfo = new CultureInfo("en-US", false).TextInfo;
|
||||
|
||||
bool isRing = scan.BodyName.Contains("Ring");
|
||||
|
||||
#if DEBUG
|
||||
// results.Add("Test Scan Event", "Test Detail");
|
||||
#endif
|
||||
|
||||
#region Landable Checks
|
||||
if (scan.Landable)
|
||||
{
|
||||
if (settings.LandableTerraformable && scan.TerraformState?.Length > 0)
|
||||
{
|
||||
results.Add($"Landable and {scan.TerraformState}");
|
||||
}
|
||||
|
||||
if (settings.LandableRing && scan.Rings?.Count > 0)
|
||||
{
|
||||
results.Add("Ringed Landable Body");
|
||||
}
|
||||
|
||||
if (settings.LandableAtmosphere && scan.Atmosphere.Length > 0)
|
||||
{
|
||||
results.Add("Landable with Atmosphere", textInfo.ToTitleCase(scan.Atmosphere));
|
||||
}
|
||||
|
||||
if (settings.LandableHighG && scan.SurfaceGravity > 29.4)
|
||||
{
|
||||
results.Add("Landable with High Gravity", $"Surface gravity: {scan.SurfaceGravity / 9.81:0.00}g");
|
||||
}
|
||||
|
||||
if (settings.LandableLarge && scan.Radius > 18000000)
|
||||
{
|
||||
results.Add("Landable Large Planet", $"Radius: {scan.Radius / 1000:0}km");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Value Checks
|
||||
if (settings.HighValueMappable)
|
||||
{
|
||||
IList<string> HighValueNonTerraformablePlanetClasses = new string[] {
|
||||
"Earthlike body",
|
||||
"Ammonia world",
|
||||
"Water world",
|
||||
};
|
||||
|
||||
if (HighValueNonTerraformablePlanetClasses.Contains(scan.PlanetClass) || scan.TerraformState?.Length > 0)
|
||||
{
|
||||
var info = new System.Text.StringBuilder();
|
||||
|
||||
if (!scan.WasMapped)
|
||||
{
|
||||
if (!scan.WasDiscovered)
|
||||
info.Append("Undiscovered ");
|
||||
else
|
||||
info.Append("Unmapped ");
|
||||
}
|
||||
|
||||
if (scan.TerraformState?.Length > 0)
|
||||
info.Append("Terraformable ");
|
||||
|
||||
results.Add("High-Value Body", $"{info.ToString()}{textInfo.ToTitleCase(scan.PlanetClass)}, {scan.DistanceFromArrivalLS:N0}Ls");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Parent Relative Checks
|
||||
|
||||
if (scan.SystemAddress != 0 && scan.SemiMajorAxis != 0 &&
|
||||
scanHistory[scan.SystemAddress].ContainsKey(scan.Parent[0].Body))
|
||||
{
|
||||
Scan parent = scanHistory[scan.SystemAddress][scan.Parent[0].Body];
|
||||
|
||||
if (settings.CloseOrbit && !isRing && parent.Radius * 3 > scan.SemiMajorAxis)
|
||||
{
|
||||
results.Add("Close Orbit", $"Orbital Radius: {scan.SemiMajorAxis / 1000:N0}km, Parent Radius: {parent.Radius / 1000:N0}km");
|
||||
}
|
||||
|
||||
if (settings.ShepherdMoon && !isRing && parent.Rings?.Any() == true && parent.Rings.Last().OuterRad > scan.SemiMajorAxis && !parent.Rings.Last().Name.Contains("Belt"))
|
||||
{
|
||||
results.Add("Shepherd Moon", $"Orbit: {scan.SemiMajorAxis / 1000:N0}km, Ring Radius: {parent.Rings.Last().OuterRad / 1000:N0}km");
|
||||
}
|
||||
|
||||
if (settings.CloseRing && parent.Rings?.Count > 0)
|
||||
{
|
||||
foreach (var ring in parent.Rings)
|
||||
{
|
||||
var separation = Math.Min(Math.Abs(scan.SemiMajorAxis - ring.OuterRad), Math.Abs(ring.InnerRad - scan.SemiMajorAxis));
|
||||
if (separation < scan.Radius * 10)
|
||||
{
|
||||
var ringTypeName = ring.Name.Contains("Belt") ? "Belt" : "Ring";
|
||||
var isLandable = scan.Landable ? ", Landable" : "";
|
||||
results.Add($"Close {ringTypeName} Proximity",
|
||||
$"Orbit: {scan.SemiMajorAxis / 1000:N0}km, Radius: {scan.Radius / 1000:N0}km, Distance from {ringTypeName.ToLower()}: {separation / 1000:N0}km{isLandable}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
if (settings.DiverseLife && signalHistory.ContainsKey(scan.SystemAddress) && signalHistory[scan.SystemAddress].ContainsKey(scan.BodyID))
|
||||
{
|
||||
var bioSignals = signalHistory[scan.SystemAddress][scan.BodyID].Signals.Where(s => s.Type == "$SAA_SignalType_Biological;");
|
||||
|
||||
if (bioSignals.Count() > 0 && bioSignals.First().Count > 7)
|
||||
{
|
||||
results.Add("Diverse Life", $"Biological Signals: {bioSignals.First().Count}");
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.WideRing && scan.Rings?.Count > 0)
|
||||
{
|
||||
foreach (var ring in scan.Rings.Where(r => !r.Name.Contains("Belt")))
|
||||
{
|
||||
var ringWidth = ring.OuterRad - ring.InnerRad;
|
||||
if (ringWidth > scan.Radius * 5)
|
||||
{
|
||||
var ringName = ring.Name.Replace(scan.BodyName, "").Trim();
|
||||
results.Add("Wide Ring", $"{ringName}: Width: {ringWidth / 299792458:N2}Ls / {ringWidth / 1000:N0}km, Parent Radius: {scan.Radius / 1000:N0}km");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.SmallObject && scan.StarType == null && scan.PlanetClass != null && scan.PlanetClass != "Barycentre" && scan.Radius < 300000)
|
||||
{
|
||||
results.Add("Small Object", $"Radius: {scan.Radius / 1000:N0}km");
|
||||
}
|
||||
|
||||
if (settings.HighEccentricity && scan.Eccentricity > 0.9)
|
||||
{
|
||||
results.Add("Highly Eccentric Orbit", $"Eccentricity: {Math.Round(scan.Eccentricity, 4)}");
|
||||
}
|
||||
|
||||
if (settings.Nested && !isRing && scan.Parent?.Count > 1 && scan.Parent[0].ParentType == ParentType.Planet && scan.Parent[1].ParentType == ParentType.Planet)
|
||||
{
|
||||
results.Add("Nested Moon");
|
||||
}
|
||||
|
||||
if (settings.FastRotation && scan.RotationPeriod != 0 && !scan.TidalLock && Math.Abs(scan.RotationPeriod) < 28800 && !isRing && string.IsNullOrEmpty(scan.StarType))
|
||||
{
|
||||
results.Add("Non-locked Body with Fast Rotation", $"Period: {scan.RotationPeriod/3600:N1} hours");
|
||||
}
|
||||
|
||||
if (settings.FastOrbit && scan.OrbitalPeriod != 0 && Math.Abs(scan.OrbitalPeriod) < 28800 && !isRing)
|
||||
{
|
||||
results.Add("Fast Orbit", $"Orbital Period: {Math.Abs(scan.OrbitalPeriod / 3600):N1} hours");
|
||||
}
|
||||
|
||||
// Close binary pair
|
||||
if ((settings.CloseBinary || settings.CollidingBinary) && scan.Parent?[0].ParentType == ParentType.Null && scan.Radius / scan.SemiMajorAxis > 0.4)
|
||||
{
|
||||
var binaryPartner = scanHistory[scan.SystemAddress].Where(priorScan => priorScan.Value.Parent?[0].Body == scan.Parent?[0].Body && scan.BodyID != priorScan.Key);
|
||||
|
||||
if (binaryPartner.Count() == 1)
|
||||
{
|
||||
if (binaryPartner.First().Value.Radius / binaryPartner.First().Value.SemiMajorAxis > 0.4)
|
||||
{
|
||||
if (settings.CollidingBinary && binaryPartner.First().Value.Radius + scan.Radius >= binaryPartner.First().Value.SemiMajorAxis * (1 - binaryPartner.First().Value.Eccentricity) + scan.SemiMajorAxis * (1 - scan.Eccentricity))
|
||||
{
|
||||
results.Add("COLLIDING binary", $"Orbit: {Math.Truncate((double)scan.SemiMajorAxis / 1000):N0}km, Radius: {Math.Truncate((double)scan.Radius / 1000):N0}km, Partner: {binaryPartner.First().Value.BodyName}");
|
||||
}
|
||||
else if (settings.CloseBinary)
|
||||
{
|
||||
results.Add("Close binary relative to body size", $"Orbit: {Math.Truncate((double)scan.SemiMajorAxis / 1000):N0}km, Radius: {Math.Truncate((double)scan.Radius / 1000):N0}km, Partner: {binaryPartner.First().Value.BodyName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.GoodFSDBody && scan.Landable)
|
||||
{
|
||||
List<string> boostMaterials = new()
|
||||
{
|
||||
"Carbon",
|
||||
"Germanium",
|
||||
"Arsenic",
|
||||
"Niobium",
|
||||
"Yttrium",
|
||||
"Polonium"
|
||||
};
|
||||
|
||||
if (boostMaterials.RemoveMatchedMaterials(scan) == 1)
|
||||
{
|
||||
results.Add("5 of 6 Premium Boost Materials", $"Missing material: {boostMaterials[0]}");
|
||||
}
|
||||
}
|
||||
|
||||
if ((settings.GreenSystem || settings.GoldSystem) && scan.Materials != null)
|
||||
{
|
||||
List<string> boostMaterials = new()
|
||||
{
|
||||
"Carbon",
|
||||
"Germanium",
|
||||
"Arsenic",
|
||||
"Niobium",
|
||||
"Yttrium",
|
||||
"Polonium"
|
||||
};
|
||||
|
||||
List<string> allSurfaceMaterials = new()
|
||||
{
|
||||
"Antimony", "Arsenic", "Cadmium", "Carbon",
|
||||
"Chromium", "Germanium", "Iron", "Manganese",
|
||||
"Mercury", "Molybdenum", "Nickel", "Niobium",
|
||||
"Phosphorus", "Polonium", "Ruthenium", "Selenium",
|
||||
"Sulphur", "Technetium", "Tellurium", "Tin",
|
||||
"Tungsten", "Vanadium", "Yttrium", "Zinc",
|
||||
"Zirconium"
|
||||
};
|
||||
|
||||
var systemBodies = scanHistory[scan.SystemAddress];
|
||||
|
||||
bool notifyGreen = false;
|
||||
bool notifyGold = false;
|
||||
|
||||
foreach (var body in systemBodies.Values)
|
||||
{
|
||||
|
||||
// If we enter either check and the count is already zero then a
|
||||
// previous body in system triggered the check, suppress notification.
|
||||
if (settings.GreenSystem && body.Materials != null)
|
||||
{
|
||||
if (boostMaterials.Count == 0)
|
||||
notifyGreen = false;
|
||||
else if (boostMaterials.RemoveMatchedMaterials(body) == 0)
|
||||
notifyGreen = true;
|
||||
}
|
||||
|
||||
if (settings.GoldSystem && body.Materials != null)
|
||||
{
|
||||
if (allSurfaceMaterials.Count == 0)
|
||||
notifyGold = false;
|
||||
else if (allSurfaceMaterials.RemoveMatchedMaterials(body) == 0)
|
||||
notifyGold = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (notifyGreen)
|
||||
results.Add("All Premium Boost Materials In System", string.Empty, true);
|
||||
|
||||
if (notifyGold)
|
||||
results.Add("All Surface Materials In System", string.Empty, true);
|
||||
}
|
||||
|
||||
if (settings.UncommonSecondary && scan.BodyID > 0 && !string.IsNullOrWhiteSpace(scan.StarType) && scan.DistanceFromArrivalLS > 10)
|
||||
{
|
||||
var excludeTypes = new List<string>() { "O", "B", "A", "F", "G", "K", "M", "L", "T", "Y", "TTS" };
|
||||
if (!excludeTypes.Contains(scan.StarType.ToUpper()))
|
||||
{
|
||||
results.Add("Uncommon Secondary Star Type", $"{GetUncommonStarTypeName(scan.StarType)}, Distance: {scan.DistanceFromArrivalLS:N0}Ls");
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static string GetUncommonStarTypeName(string starType)
|
||||
{
|
||||
string name;
|
||||
|
||||
switch (starType.ToLower())
|
||||
{
|
||||
case "b_bluewhitesupergiant":
|
||||
name = "B Blue-White Supergiant";
|
||||
break;
|
||||
case "a_bluewhitesupergiant":
|
||||
name = "A Blue-White Supergiant";
|
||||
break;
|
||||
case "f_whitesupergiant":
|
||||
name = "F White Supergiant";
|
||||
break;
|
||||
case "g_whitesupergiant":
|
||||
name = "G White Supergiant";
|
||||
break;
|
||||
case "k_orangegiant":
|
||||
name = "K Orange Giant";
|
||||
break;
|
||||
case "m_redgiant":
|
||||
name = "M Red Giant";
|
||||
break;
|
||||
case "m_redsupergiant":
|
||||
name = "M Red Supergiant";
|
||||
break;
|
||||
case "aebe":
|
||||
name = "Herbig Ae/Be";
|
||||
break;
|
||||
case "w":
|
||||
case "wn":
|
||||
case "wnc":
|
||||
case "wc":
|
||||
case "wo":
|
||||
name = "Wolf-Rayet";
|
||||
break;
|
||||
case "c":
|
||||
case "cs":
|
||||
case "cn":
|
||||
case "cj":
|
||||
case "ch":
|
||||
case "chd":
|
||||
name = "Carbon Star";
|
||||
break;
|
||||
case "s":
|
||||
name = "S-Type Star";
|
||||
break;
|
||||
case "ms":
|
||||
name = "MS-Type Star";
|
||||
break;
|
||||
case "d":
|
||||
case "da":
|
||||
case "dab":
|
||||
case "dao":
|
||||
case "daz":
|
||||
case "dav":
|
||||
case "db":
|
||||
case "dbz":
|
||||
case "dbv":
|
||||
case "do":
|
||||
case "dov":
|
||||
case "dq":
|
||||
case "dc":
|
||||
case "dcv":
|
||||
case "dx":
|
||||
name = "White Dwarf";
|
||||
break;
|
||||
case "n":
|
||||
name = "Neutron Star";
|
||||
break;
|
||||
case "h":
|
||||
name = "Black Hole";
|
||||
break;
|
||||
case "supermassiveblackhole":
|
||||
name = "Supermassive Black Hole";
|
||||
break;
|
||||
case "x":
|
||||
name = "Exotic Star";
|
||||
break;
|
||||
case "rogueplanet":
|
||||
name = "Rogue Planet";
|
||||
break;
|
||||
case "tts":
|
||||
case "t":
|
||||
name = "T Tauri Type";
|
||||
break;
|
||||
default:
|
||||
name = starType + "-Type Star";
|
||||
break;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes materials from the list if found on the specified body.
|
||||
/// </summary>
|
||||
/// <param name="materials"></param>
|
||||
/// <param name="body"></param>
|
||||
/// <returns>Count of materials remaining in list.</returns>
|
||||
private static int RemoveMatchedMaterials(this List<string> materials, Scan body)
|
||||
{
|
||||
foreach (var material in body.Materials)
|
||||
{
|
||||
var matchedMaterial = materials.Find(mat => mat.Equals(material.Name, StringComparison.OrdinalIgnoreCase));
|
||||
if (matchedMaterial != null)
|
||||
{
|
||||
materials.Remove(matchedMaterial);
|
||||
}
|
||||
}
|
||||
return materials.Count;
|
||||
}
|
||||
|
||||
private static void Add(this List<(string, string, bool)> results, string description, string detail = "", bool systemWide = false)
|
||||
{
|
||||
results.Add((description, detail, systemWide));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,321 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Files.Journal;
|
||||
using Observatory.Framework.Interfaces;
|
||||
|
||||
namespace Observatory.Explorer
|
||||
{
|
||||
internal class Explorer
|
||||
{
|
||||
private IObservatoryCore ObservatoryCore;
|
||||
private ObservableCollection<object> Results;
|
||||
private ExplorerWorker ExplorerWorker;
|
||||
private Dictionary<ulong, Dictionary<int, Scan>> SystemBodyHistory;
|
||||
private Dictionary<ulong, Dictionary<int, FSSBodySignals>> BodySignalHistory;
|
||||
private Dictionary<ulong, Dictionary<int, ScanBaryCentre>> BarycentreHistory;
|
||||
private CustomCriteriaManager CustomCriteriaManager;
|
||||
private DateTime CriteriaLastModified;
|
||||
private string currentSystem = string.Empty;
|
||||
|
||||
internal Explorer(ExplorerWorker explorerWorker, IObservatoryCore core, ObservableCollection<object> results)
|
||||
{
|
||||
SystemBodyHistory = new();
|
||||
BodySignalHistory = new();
|
||||
BarycentreHistory = new();
|
||||
ExplorerWorker = explorerWorker;
|
||||
ObservatoryCore = core;
|
||||
Results = results;
|
||||
CustomCriteriaManager = new(core.GetPluginErrorLogger(explorerWorker));
|
||||
CriteriaLastModified = new DateTime(0);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
SystemBodyHistory.Clear();
|
||||
BodySignalHistory.Clear();
|
||||
BarycentreHistory.Clear();
|
||||
}
|
||||
|
||||
public void RecordSignal(FSSBodySignals bodySignals)
|
||||
{
|
||||
if (!BodySignalHistory.ContainsKey(bodySignals.SystemAddress))
|
||||
{
|
||||
BodySignalHistory.Add(bodySignals.SystemAddress, new Dictionary<int, FSSBodySignals>());
|
||||
}
|
||||
|
||||
if (!BodySignalHistory[bodySignals.SystemAddress].ContainsKey(bodySignals.BodyID))
|
||||
{
|
||||
BodySignalHistory[bodySignals.SystemAddress].Add(bodySignals.BodyID, bodySignals);
|
||||
}
|
||||
}
|
||||
|
||||
public void RecordBarycentre(ScanBaryCentre scan)
|
||||
{
|
||||
if (!BarycentreHistory.ContainsKey(scan.SystemAddress))
|
||||
{
|
||||
BarycentreHistory.Add(scan.SystemAddress, new Dictionary<int, ScanBaryCentre>());
|
||||
}
|
||||
|
||||
if (!BarycentreHistory[scan.SystemAddress].ContainsKey(scan.BodyID))
|
||||
{
|
||||
BarycentreHistory[scan.SystemAddress].Add(scan.BodyID, scan);
|
||||
}
|
||||
}
|
||||
|
||||
private static string IncrementOrdinal(string ordinal)
|
||||
{
|
||||
char ordChar = ordinal.ToCharArray().Last();
|
||||
|
||||
if (new char[] {'Z', '9'}.Contains(ordChar))
|
||||
{
|
||||
ordinal = IncrementOrdinal(ordinal.Length == 1 ? " " : String.Empty + ordinal[..^1]);
|
||||
ordChar = (char)(ordChar - 10);
|
||||
}
|
||||
|
||||
return ordinal[..^1] + (char)(ordChar + 1);
|
||||
}
|
||||
|
||||
private static string DecrementOrdinal(string ordinal)
|
||||
{
|
||||
char ordChar = ordinal.ToCharArray().Last();
|
||||
|
||||
if (new char[] { 'A', '0' }.Contains(ordChar))
|
||||
{
|
||||
ordinal = DecrementOrdinal(ordinal.Length == 1 ? " " : String.Empty + ordinal[..^1]);
|
||||
ordChar = (char)(ordChar + 10);
|
||||
}
|
||||
|
||||
return ordinal[..^1] + (char)(ordChar - 1);
|
||||
}
|
||||
|
||||
public Scan ConvertBarycentre(ScanBaryCentre barycentre, Scan childScan)
|
||||
{
|
||||
string childAffix = childScan.BodyName
|
||||
.Replace(childScan.StarSystem, string.Empty).Trim();
|
||||
|
||||
string baryName;
|
||||
|
||||
if (!string.IsNullOrEmpty(childAffix))
|
||||
{
|
||||
char childOrdinal = childAffix.ToCharArray().Last();
|
||||
|
||||
// If the ID is one higher than the barycentre than this is the "first" child body
|
||||
bool lowChild = childScan.BodyID - barycentre.BodyID == 1;
|
||||
|
||||
string baryAffix;
|
||||
|
||||
// Barycentre ordinal always labelled as low before high, e.g. "AB"
|
||||
if (lowChild)
|
||||
{
|
||||
baryAffix = childAffix + "-" + IncrementOrdinal(childAffix);
|
||||
}
|
||||
else
|
||||
{
|
||||
baryAffix = DecrementOrdinal(childAffix) + "-" + childAffix;
|
||||
}
|
||||
|
||||
baryName = barycentre.StarSystem + " " + baryAffix;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Without ordinals it's complicated to determine what the ordinal *should* be.
|
||||
// Just name the barycentre after the child object.
|
||||
baryName = childScan.BodyName + " Barycentre";
|
||||
}
|
||||
|
||||
Scan barycentreScan = new()
|
||||
{
|
||||
Timestamp = barycentre.Timestamp,
|
||||
BodyName = baryName,
|
||||
Parents = childScan.Parents.RemoveAt(0),
|
||||
PlanetClass = "Barycentre",
|
||||
StarSystem = barycentre.StarSystem,
|
||||
SystemAddress = barycentre.SystemAddress,
|
||||
BodyID = barycentre.BodyID,
|
||||
SemiMajorAxis = barycentre.SemiMajorAxis,
|
||||
Eccentricity = barycentre.Eccentricity,
|
||||
OrbitalInclination = barycentre.OrbitalInclination,
|
||||
Periapsis = barycentre.Periapsis,
|
||||
OrbitalPeriod = barycentre.OrbitalPeriod,
|
||||
AscendingNode = barycentre.AscendingNode,
|
||||
MeanAnomaly = barycentre.MeanAnomaly,
|
||||
Json = barycentre.Json
|
||||
};
|
||||
|
||||
return barycentreScan;
|
||||
}
|
||||
public void SetSystem(string potentialNewSystem)
|
||||
{
|
||||
if (string.IsNullOrEmpty(currentSystem) || currentSystem != potentialNewSystem)
|
||||
{
|
||||
currentSystem = potentialNewSystem;
|
||||
if (ExplorerWorker.settings.OnlyShowCurrentSystem && !ObservatoryCore.IsLogMonitorBatchReading)
|
||||
{
|
||||
ObservatoryCore.ClearGrid(ExplorerWorker, new ExplorerUIResults());
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessScan(Scan scanEvent, bool readAll)
|
||||
{
|
||||
if (!readAll)
|
||||
{
|
||||
string criteriaFilePath = ExplorerWorker.settings.CustomCriteriaFile;
|
||||
|
||||
if (File.Exists(criteriaFilePath))
|
||||
{
|
||||
DateTime fileModified = new FileInfo(criteriaFilePath).LastWriteTime;
|
||||
|
||||
if (fileModified != CriteriaLastModified)
|
||||
{
|
||||
try
|
||||
{
|
||||
CustomCriteriaManager.RefreshCriteria(criteriaFilePath);
|
||||
}
|
||||
catch (CriteriaLoadException e)
|
||||
{
|
||||
var exceptionResult = new ExplorerUIResults()
|
||||
{
|
||||
BodyName = "Error Reading Custom Criteria File",
|
||||
Time = DateTime.Now.ToString("G"),
|
||||
Description = e.Message,
|
||||
Details = e.OriginalScript
|
||||
};
|
||||
ObservatoryCore.AddGridItem(ExplorerWorker, exceptionResult);
|
||||
}
|
||||
|
||||
CriteriaLastModified = fileModified;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary<int, Scan> systemBodies;
|
||||
if (SystemBodyHistory.ContainsKey(scanEvent.SystemAddress))
|
||||
{
|
||||
systemBodies = SystemBodyHistory[scanEvent.SystemAddress];
|
||||
if (systemBodies.ContainsKey(scanEvent.BodyID))
|
||||
{
|
||||
if (scanEvent.SystemAddress != 0)
|
||||
{
|
||||
//We've already checked this object.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
systemBodies = new();
|
||||
SystemBodyHistory.Add(scanEvent.SystemAddress, systemBodies);
|
||||
}
|
||||
|
||||
if (SystemBodyHistory.Count > 1000)
|
||||
{
|
||||
foreach (var entry in SystemBodyHistory.Where(entry => entry.Key != scanEvent.SystemAddress).ToList())
|
||||
{
|
||||
SystemBodyHistory.Remove(entry.Key);
|
||||
}
|
||||
SystemBodyHistory.TrimExcess();
|
||||
}
|
||||
|
||||
if (scanEvent.SystemAddress != 0 && !systemBodies.ContainsKey(scanEvent.BodyID))
|
||||
systemBodies.Add(scanEvent.BodyID, scanEvent);
|
||||
|
||||
var results = DefaultCriteria.CheckInterest(scanEvent, SystemBodyHistory, BodySignalHistory, ExplorerWorker.settings);
|
||||
|
||||
if (BarycentreHistory.ContainsKey(scanEvent.SystemAddress) && scanEvent.Parent != null && BarycentreHistory[scanEvent.SystemAddress].ContainsKey(scanEvent.Parent[0].Body))
|
||||
{
|
||||
ProcessScan(ConvertBarycentre(BarycentreHistory[scanEvent.SystemAddress][scanEvent.Parent[0].Body], scanEvent), readAll);
|
||||
}
|
||||
|
||||
if (ExplorerWorker.settings.EnableCustomCriteria)
|
||||
results.AddRange(CustomCriteriaManager.CheckInterest(scanEvent, SystemBodyHistory, BodySignalHistory, ExplorerWorker.settings));
|
||||
|
||||
if (results.Count > 0)
|
||||
{
|
||||
StringBuilder notificationDetail = new();
|
||||
StringBuilder notificationExtendedDetail = new();
|
||||
foreach (var result in results)
|
||||
{
|
||||
var scanResult = new ExplorerUIResults()
|
||||
{
|
||||
BodyName = result.SystemWide ? scanEvent.StarSystem : scanEvent.BodyName,
|
||||
Time = scanEvent.TimestampDateTime.ToString("G"),
|
||||
Description = result.Description,
|
||||
Details = result.Detail
|
||||
};
|
||||
ObservatoryCore.AddGridItem(ExplorerWorker, scanResult);
|
||||
notificationDetail.AppendLine(result.Description);
|
||||
notificationExtendedDetail.AppendLine(result.Detail);
|
||||
}
|
||||
|
||||
string bodyAffix;
|
||||
|
||||
if (scanEvent.StarSystem != null && scanEvent.BodyName.StartsWith(scanEvent.StarSystem))
|
||||
{
|
||||
bodyAffix = scanEvent.BodyName.Replace(scanEvent.StarSystem, string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
bodyAffix = string.Empty;
|
||||
}
|
||||
|
||||
string bodyLabel = System.Security.SecurityElement.Escape(scanEvent.PlanetClass == "Barycentre" ? "Barycentre" : "Body");
|
||||
|
||||
string spokenAffix;
|
||||
|
||||
if (bodyAffix.Length > 0)
|
||||
{
|
||||
if (bodyAffix.Contains("Ring"))
|
||||
{
|
||||
int ringIndex = bodyAffix.Length - 6;
|
||||
spokenAffix =
|
||||
"<say-as interpret-as=\"spell-out\">" + bodyAffix[..ringIndex]
|
||||
+ "</say-as><break strength=\"weak\"/>" + SplitOrdinalForSsml(bodyAffix.Substring(ringIndex, 1))
|
||||
+ bodyAffix[(ringIndex + 1)..];
|
||||
}
|
||||
else
|
||||
{
|
||||
spokenAffix = SplitOrdinalForSsml(bodyAffix);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bodyLabel = "Primary Star";
|
||||
spokenAffix = string.Empty;
|
||||
}
|
||||
|
||||
NotificationArgs args = new()
|
||||
{
|
||||
Title = bodyLabel + bodyAffix,
|
||||
TitleSsml = $"<speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xml:lang=\"en-US\"><voice name=\"\">{bodyLabel} {spokenAffix}</voice></speak>",
|
||||
Detail = notificationDetail.ToString(),
|
||||
Sender = ExplorerWorker.ShortName,
|
||||
ExtendedDetails = notificationExtendedDetail.ToString(),
|
||||
CoalescingId = scanEvent.BodyID,
|
||||
};
|
||||
|
||||
ObservatoryCore.SendNotification(args);
|
||||
}
|
||||
}
|
||||
|
||||
private static string SplitOrdinalForSsml(string ordinalString)
|
||||
{
|
||||
var ordinalSegments = ordinalString.Split(' ');
|
||||
StringBuilder affix = new();
|
||||
foreach (var ordinalSegment in ordinalSegments)
|
||||
{
|
||||
if (ordinalSegment.All(Char.IsDigit))
|
||||
affix.Append(" " + ordinalSegment);
|
||||
else
|
||||
affix.Append("<say-as interpret-as=\"spell-out\">" + ordinalSegment + "</say-as>");
|
||||
}
|
||||
return affix.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
using Observatory.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Observatory.Explorer
|
||||
{
|
||||
[SettingSuggestedColumnWidth(300)]
|
||||
public class ExplorerSettings
|
||||
{
|
||||
public ExplorerSettings()
|
||||
{
|
||||
CustomCriteriaFile = $"{Environment.GetFolderPath(Environment.SpecialFolder.Personal)}{System.IO.Path.DirectorySeparatorChar}ObservatoryCriteria.lua";
|
||||
}
|
||||
|
||||
[SettingNewGroup("Display")]
|
||||
[SettingDisplayName("Only Show Current System")]
|
||||
public bool OnlyShowCurrentSystem { get; set; }
|
||||
|
||||
|
||||
[SettingNewGroup("Built-in Criteria")]
|
||||
[SettingDisplayName("Landable & Terraformable")]
|
||||
public bool LandableTerraformable { get; set; }
|
||||
|
||||
[SettingDisplayName("Landable w/ Atmosphere")]
|
||||
public bool LandableAtmosphere { get; set; }
|
||||
|
||||
[SettingDisplayName("Landable High-g")]
|
||||
public bool LandableHighG { get; set; }
|
||||
|
||||
[SettingDisplayName("Landable Large")]
|
||||
public bool LandableLarge { get; set; }
|
||||
|
||||
[SettingDisplayName("Close Orbit")]
|
||||
public bool CloseOrbit { get; set; }
|
||||
|
||||
[SettingDisplayName("Shepherd Moon")]
|
||||
public bool ShepherdMoon { get; set; }
|
||||
|
||||
[SettingDisplayName("Wide Ring")]
|
||||
public bool WideRing { get; set; }
|
||||
|
||||
[SettingDisplayName("Close Binary")]
|
||||
public bool CloseBinary { get; set; }
|
||||
|
||||
[SettingDisplayName("Colliding Binary")]
|
||||
public bool CollidingBinary { get; set; }
|
||||
|
||||
[SettingDisplayName("Close Ring Proximity")]
|
||||
public bool CloseRing { get; set; }
|
||||
|
||||
[SettingDisplayName("Codex Discoveries")]
|
||||
public bool Codex { get; set; }
|
||||
|
||||
[SettingDisplayName("Uncommon Secondary Star")]
|
||||
public bool UncommonSecondary { get; set; }
|
||||
|
||||
[SettingDisplayName("Landable w/ Ring")]
|
||||
public bool LandableRing { get; set; }
|
||||
|
||||
[SettingDisplayName("Nested Moon")]
|
||||
public bool Nested { get; set; }
|
||||
|
||||
[SettingDisplayName("Small Object")]
|
||||
public bool SmallObject { get; set; }
|
||||
|
||||
[SettingDisplayName("Fast Rotation")]
|
||||
public bool FastRotation { get; set; }
|
||||
|
||||
[SettingDisplayName("Fast Orbit")]
|
||||
public bool FastOrbit { get; set; }
|
||||
|
||||
[SettingDisplayName("High Eccentricity")]
|
||||
public bool HighEccentricity { get; set; }
|
||||
|
||||
[SettingDisplayName("Diverse Life")]
|
||||
public bool DiverseLife { get; set; }
|
||||
|
||||
[SettingDisplayName("Good FSD Injection")]
|
||||
public bool GoodFSDBody { get; set; }
|
||||
|
||||
[SettingDisplayName("All FSD Mats In System")]
|
||||
public bool GreenSystem { get; set; }
|
||||
|
||||
[SettingDisplayName("All Surface Mats In System")]
|
||||
public bool GoldSystem { get; set; }
|
||||
|
||||
[SettingDisplayName("High-Value Body")]
|
||||
public bool HighValueMappable { get; set; }
|
||||
|
||||
[SettingNewGroup("Custom Criteria")]
|
||||
[SettingDisplayName("Enable Custom Criteria")]
|
||||
public bool EnableCustomCriteria { get; set; }
|
||||
[SettingDisplayName("Custom Criteria File")]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public System.IO.FileInfo CustomCriteria {get => new System.IO.FileInfo(CustomCriteriaFile); set => CustomCriteriaFile = value.FullName;}
|
||||
|
||||
[SettingIgnore]
|
||||
public string CustomCriteriaFile { get; set; }
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using Observatory.Framework;
|
||||
|
||||
namespace Observatory.Explorer
|
||||
{
|
||||
public class ExplorerUIResults
|
||||
{
|
||||
[ColumnSuggestedWidth(300)]
|
||||
public string Time { get; set; }
|
||||
|
||||
[ColumnSuggestedWidth(350)]
|
||||
public string BodyName { get; set; }
|
||||
|
||||
[ColumnSuggestedWidth(400)]
|
||||
public string Description { get; set; }
|
||||
|
||||
[ColumnSuggestedWidth(600)]
|
||||
public string Details { get; set; }
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<SignAssembly>false</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>ObservatoryKey.snk</AssemblyOriginatorKeyFile>
|
||||
<Configurations>Debug;Release;Portable</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<VersionSuffix>0.1.$([System.DateTime]::UtcNow.Year.ToString().Substring(2))$([System.DateTime]::UtcNow.DayOfYear.ToString().PadLeft(3, "0")).$([System.DateTime]::UtcNow.ToString(HHmm))</VersionSuffix>
|
||||
<AssemblyVersion Condition=" '$(VersionSuffix)' == '' ">0.0.0.1</AssemblyVersion>
|
||||
<AssemblyVersion Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</AssemblyVersion>
|
||||
<Version Condition=" '$(VersionSuffix)' == '' ">0.0.1.0</Version>
|
||||
<Version Condition=" '$(VersionSuffix)' != '' ">$(VersionSuffix)</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Condition=" '$(OS)' == 'Windows_NT' " Command="xcopy "$(TargetPath)" "$(ProjectDir)..\ObservatoryCore\$(OutDir)..\net6.0-windows\plugins\" /y" />
|
||||
<Exec Condition=" '$(OS)' == 'Windows_NT' " Command="xcopy "$(TargetDir)NLua.dll" "$(ProjectDir)..\ObservatoryCore\$(OutDir)..\net6.0-windows\plugins\deps\" /y" />
|
||||
<Exec Condition=" '$(OS)' == 'Windows_NT' " Command="xcopy "$(TargetDir)KeraLua.dll" "$(ProjectDir)..\ObservatoryCore\$(OutDir)..\net6.0-windows\plugins\deps\" /y" />
|
||||
<Exec Condition=" '$(OS)' == 'Windows_NT' " Command="xcopy "$(TargetDir)runtimes\win-x64\native\lua54.dll" "$(ProjectDir)..\ObservatoryCore\$(OutDir)..\net6.0-windows\plugins\deps\" /y" />
|
||||
<Exec Condition=" '$(OS)' != 'Windows_NT' " Command="[ ! -d "$(ProjectDir)../ObservatoryCore/$(OutDir)plugins/deps" ] && mkdir -p "$(ProjectDir)../ObservatoryCore/$(OutDir)plugins/deps" || echo Directory already exists" />
|
||||
<Exec Condition=" '$(OS)' != 'Windows_NT' " Command="cp "$(TargetPath)" "$(ProjectDir)../ObservatoryCore/$(OutDir)plugins/" -f" />
|
||||
<Exec Condition=" '$(OS)' != 'Windows_NT' " Command="cp "$(TargetDir)NLua.dll" "$(ProjectDir)../ObservatoryCore/$(OutDir)plugins/deps/" -f" />
|
||||
<Exec Condition=" '$(OS)' != 'Windows_NT' " Command="cp "$(TargetDir)KeraLua.dll" "$(ProjectDir)../ObservatoryCore/$(OutDir)plugins/deps/" -f" />
|
||||
<Exec Condition=" '$(OS)' != 'Windows_NT' " Command="cp "$(TargetDir)runtimes/linux-x64/native/liblua54.so" "$(ProjectDir)../ObservatoryCore/$(OutDir)lua54.so" -f" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLua" Version="1.6.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="ObservatoryFramework">
|
||||
<HintPath>..\ObservatoryFramework\bin\Release\net6.0\ObservatoryFramework.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -1,115 +0,0 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Observatory.Framework.Files.Journal;
|
||||
using Observatory.Framework.Interfaces;
|
||||
using Observatory.Framework;
|
||||
|
||||
namespace Observatory.Explorer
|
||||
{
|
||||
public class ExplorerWorker : IObservatoryWorker
|
||||
{
|
||||
public ExplorerWorker()
|
||||
{
|
||||
settings = new()
|
||||
{
|
||||
CloseBinary = true,
|
||||
CloseOrbit = true,
|
||||
CloseRing = true,
|
||||
CollidingBinary = true,
|
||||
FastRotation = true,
|
||||
HighEccentricity = true,
|
||||
LandableAtmosphere = true,
|
||||
LandableHighG = true,
|
||||
LandableLarge = true,
|
||||
LandableRing = true,
|
||||
LandableTerraformable = true,
|
||||
Nested = true,
|
||||
ShepherdMoon = true,
|
||||
SmallObject = true,
|
||||
WideRing = true
|
||||
};
|
||||
resultsGrid = new();
|
||||
}
|
||||
|
||||
private Explorer explorer;
|
||||
private ObservableCollection<object> resultsGrid;
|
||||
private IObservatoryCore Core;
|
||||
|
||||
private bool recordProcessedSinceBatchStart = false;
|
||||
|
||||
public string Name => "Observatory Explorer";
|
||||
|
||||
public string ShortName => "Explorer";
|
||||
|
||||
public string Version => typeof(ExplorerWorker).Assembly.GetName().Version.ToString();
|
||||
|
||||
private PluginUI pluginUI;
|
||||
|
||||
public PluginUI PluginUI => pluginUI;
|
||||
|
||||
public void Load(IObservatoryCore observatoryCore)
|
||||
{
|
||||
explorer = new Explorer(this, observatoryCore, resultsGrid);
|
||||
resultsGrid.Add(new ExplorerUIResults());
|
||||
pluginUI = new PluginUI(resultsGrid);
|
||||
Core = observatoryCore;
|
||||
}
|
||||
|
||||
public void JournalEvent<TJournal>(TJournal journal) where TJournal : JournalBase
|
||||
{
|
||||
switch (journal)
|
||||
{
|
||||
case Scan scan:
|
||||
explorer.ProcessScan(scan, Core.IsLogMonitorBatchReading && recordProcessedSinceBatchStart);
|
||||
// Set this *after* the first scan processes so that we get the current custom criteria file.
|
||||
if (Core.IsLogMonitorBatchReading) recordProcessedSinceBatchStart = true;
|
||||
break;
|
||||
case FSSBodySignals signals:
|
||||
explorer.RecordSignal(signals);
|
||||
break;
|
||||
case ScanBaryCentre barycentre:
|
||||
explorer.RecordBarycentre(barycentre);
|
||||
break;
|
||||
case FSDJump fsdjump:
|
||||
if (fsdjump is CarrierJump && !((CarrierJump)fsdjump).Docked)
|
||||
break;
|
||||
explorer.SetSystem(fsdjump.StarSystem);
|
||||
break;
|
||||
case Location location:
|
||||
explorer.SetSystem(location.StarSystem);
|
||||
break;
|
||||
case DiscoveryScan discoveryScan:
|
||||
break;
|
||||
case FSSDiscoveryScan discoveryScan:
|
||||
break;
|
||||
case FSSSignalDiscovered signalDiscovered:
|
||||
break;
|
||||
case NavBeaconScan beaconScan:
|
||||
break;
|
||||
case SAAScanComplete scanComplete:
|
||||
break;
|
||||
case SAASignalsFound signalsFound:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void LogMonitorStateChanged(LogMonitorStateChangedEventArgs args)
|
||||
{
|
||||
if (LogMonitorStateChangedEventArgs.IsBatchRead(args.NewState))
|
||||
{
|
||||
// Beginning a batch read. Clear grid.
|
||||
recordProcessedSinceBatchStart = false;
|
||||
Core.ClearGrid(this, new ExplorerUIResults());
|
||||
explorer.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public object Settings
|
||||
{
|
||||
get => settings;
|
||||
set => settings = (ExplorerSettings)value;
|
||||
}
|
||||
|
||||
internal ExplorerSettings settings;
|
||||
}
|
||||
}
|
@ -1,43 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
namespace Observatory.Framework;
|
||||
|
||||
namespace Observatory.Framework
|
||||
#region Setting property attributes
|
||||
|
||||
/// <summary>
|
||||
/// Specifies text to display as the name of the setting in the UI instead of the property name.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class SettingDisplayName : Attribute
|
||||
{
|
||||
|
||||
#region Settings class attributes
|
||||
/// <summary>
|
||||
/// Specifies the width of a settings column in the settings view. There are two columns.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
|
||||
public class SettingSuggestedColumnWidth : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the width of a settings column in the settings view. There are two columns.
|
||||
/// </summary>
|
||||
/// <param name="width">Provides a hint of the width of a settings column.</param>
|
||||
public SettingSuggestedColumnWidth(int width)
|
||||
{
|
||||
Width = width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a hint of the width of a settings column.
|
||||
/// </summary>
|
||||
public int Width { get; }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Setting property attributes
|
||||
|
||||
/// <summary>
|
||||
/// Specifies text to display as the name of the setting in the UI instead of the property name.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class SettingDisplayName : Attribute
|
||||
{
|
||||
private string name;
|
||||
|
||||
/// <summary>
|
||||
@ -57,76 +27,21 @@ namespace Observatory.Framework
|
||||
get => name;
|
||||
set => name = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new visual group of settings beginning with the current setting with an optional label.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class SettingNewGroup : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Starts a new visual group of settings beginning with the current setting with an optional label.
|
||||
/// </summary>
|
||||
/// <param name="label">An optional label describing the group.</param>
|
||||
public SettingNewGroup(string label = "")
|
||||
{
|
||||
Label = label;
|
||||
}
|
||||
/// <summary>
|
||||
/// Indicates numeric properly should use a slider control instead of a numeric textbox with roller.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class SettingNumericUseSlider : Attribute
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// An optional label describing the group.
|
||||
/// </summary>
|
||||
public string Label { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the property should not be displayed to the user in the UI.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class SettingIgnore : Attribute
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates numeric properly should use a slider control instead of a numeric textbox with roller.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class SettingNumericUseSlider : Attribute
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Specify backing value used by Dictionary<string, object> to indicate selected option.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class SettingBackingValue : Attribute
|
||||
{
|
||||
private string property;
|
||||
|
||||
/// <summary>
|
||||
/// Specify backing value used by Dictionary<string, object> to indicate selected option.
|
||||
/// </summary>
|
||||
/// <param name="property">Property name for backing value.</param>
|
||||
public SettingBackingValue(string property)
|
||||
{
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accessor to get/set backing value property name.
|
||||
/// </summary>
|
||||
public string BackingProperty
|
||||
{
|
||||
get => property;
|
||||
set => property = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify bounds for numeric inputs.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class SettingNumericBounds : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Specify bounds for numeric inputs.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class SettingNumericBounds : Attribute
|
||||
{
|
||||
private double minimum;
|
||||
private double maximum;
|
||||
private double increment;
|
||||
@ -182,29 +97,5 @@ namespace Observatory.Framework
|
||||
get => precision;
|
||||
set => precision = value;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BasicUI attributes
|
||||
/// <summary>
|
||||
/// Suggests default column width when building basic plugin grid UI.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class ColumnSuggestedWidth : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Suggests default column width when building basic plugin grid UI.
|
||||
/// </summary>
|
||||
/// <param name="width">The suggested width of the annotated column.</param>
|
||||
public ColumnSuggestedWidth(int width)
|
||||
{
|
||||
Width = width;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The suggested width of the annotated column.
|
||||
/// </summary>
|
||||
public int Width { get; }
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
@ -1,12 +1,10 @@
|
||||
using System;
|
||||
namespace Observatory.Framework;
|
||||
|
||||
namespace Observatory.Framework
|
||||
/// <summary>
|
||||
/// Provides data for Elite Dangerous journal events.
|
||||
/// </summary>
|
||||
public class JournalEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides data for Elite Dangerous journal events.
|
||||
/// </summary>
|
||||
public class JournalEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>Type of journal entry that triggered event.</para>
|
||||
/// <para>Will be a class from the Observatory.Framework.Files.Journal namespace derived from JournalBase, or JournalBase itself in the case of an unhandled journal event type.</para>
|
||||
@ -17,13 +15,13 @@ namespace Observatory.Framework
|
||||
/// <para>Unhandled json values within a journal entry type will be contained in member property:<br/>Dictionary<string, object> AdditionalProperties.</para>
|
||||
/// </summary>
|
||||
public object journalEvent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides values used as arguments for Observatory notification events.
|
||||
/// </summary>
|
||||
public class NotificationArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides values used as arguments for Observatory notification events.
|
||||
/// </summary>
|
||||
public class NotificationArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Text typically displayed as header content.
|
||||
/// </summary>
|
||||
@ -74,14 +72,14 @@ namespace Observatory.Framework
|
||||
/// A value which allows grouping of notifications together. For example, values >= 0 <= 1000 could be system body IDs, -1 is the system, anything else is arbitrary.
|
||||
/// </summary>
|
||||
public int? CoalescingId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines constants for suppression of title or detail announcement in a notification.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum NotificationSuppression
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines constants for suppression of title or detail announcement in a notification.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum NotificationSuppression
|
||||
{
|
||||
/// <summary>
|
||||
/// No suppression.
|
||||
/// </summary>
|
||||
@ -94,14 +92,14 @@ namespace Observatory.Framework
|
||||
/// Suppress detail.
|
||||
/// </summary>
|
||||
Detail = 2,
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines constants for controlling notification routing to plugins or native notification handlers.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum NotificationRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines constants for controlling notification routing to plugins or native notification handlers.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum NotificationRendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Send notification to native visual popup notificaiton handler.
|
||||
/// </summary>
|
||||
@ -118,14 +116,14 @@ namespace Observatory.Framework
|
||||
/// Send notification to all available handlers.
|
||||
/// </summary>
|
||||
All = (NativeVisual | NativeVocal | PluginNotifier)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flags indicating current state of journal monitoring.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum LogMonitorState : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Flags indicating current state of journal monitoring.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum LogMonitorState : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Monitoring is stopped.
|
||||
/// </summary>
|
||||
@ -142,13 +140,13 @@ namespace Observatory.Framework
|
||||
/// Batch read of recent journals is in progress to establish current player state.
|
||||
/// </summary>
|
||||
PreRead = 4
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides information about a LogMonitor state transition.
|
||||
/// </summary>
|
||||
public class LogMonitorStateChangedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides information about a LogMonitor state transition.
|
||||
/// </summary>
|
||||
public class LogMonitorStateChangedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The previous LogMonitor state.
|
||||
/// </summary>
|
||||
@ -168,5 +166,4 @@ namespace Observatory.Framework
|
||||
{
|
||||
return state.HasFlag(LogMonitorState.Batch) || state.HasFlag(LogMonitorState.PreRead);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
using System;
|
||||
namespace Observatory.Framework;
|
||||
|
||||
namespace Observatory.Framework
|
||||
/// <summary>
|
||||
/// Container for exceptions within plugins which cannot be gracefully handled in context,
|
||||
/// but benefit from having a context-specific user message.
|
||||
/// </summary>
|
||||
public class PluginException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Container for exceptions within plugins which cannot be gracefully handled in context,
|
||||
/// but benefit from having a context-specific user message.
|
||||
/// </summary>
|
||||
public class PluginException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialze new PluginException with details of the originating plugin and a specific user-facing message for display.
|
||||
/// </summary>
|
||||
@ -30,5 +28,4 @@ namespace Observatory.Framework
|
||||
/// </summary>
|
||||
public string UserMessage { get; }
|
||||
|
||||
}
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Immutable;
|
||||
using Observatory.Framework.Files.Journal;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Observatory.Framework.Files.Journal
|
||||
namespace Observatory.Framework.Files;
|
||||
|
||||
/// <summary>
|
||||
/// Elite Dangerous backpack.json file. Describes all the items currently carried by the player.
|
||||
/// </summary>
|
||||
public class BackpackFile : JournalBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Elite Dangerous backpack.json file. Describes all the items currently carried by the player.
|
||||
/// </summary>
|
||||
public class BackpackFile : JournalBase
|
||||
{
|
||||
/// <summary>
|
||||
/// List of all items carried.
|
||||
/// </summary>
|
||||
@ -24,5 +25,4 @@ namespace Observatory.Framework.Files.Journal
|
||||
/// List of all data currently stored by the player.
|
||||
/// </summary>
|
||||
public ImmutableList<BackpackItem> Data { get; init; }
|
||||
}
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Immutable;
|
||||
using Observatory.Framework.Files.Journal;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Observatory.Framework.Files
|
||||
namespace Observatory.Framework.Files;
|
||||
|
||||
/// <summary>
|
||||
/// Elite Dangerous cargo.json file. Describes the current cargo carried above the player's ship.
|
||||
/// </summary>
|
||||
public class CargoFile : JournalBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Elite Dangerous cargo.json file. Describes the current cargo carried above the player's ship.
|
||||
/// </summary>
|
||||
public class CargoFile : Journal.JournalBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of vehicle currently being reported. "Ship" or "SRV".
|
||||
/// </summary>
|
||||
@ -20,5 +21,4 @@ namespace Observatory.Framework.Files
|
||||
/// List of full cargo details.
|
||||
/// </summary>
|
||||
public ImmutableList<CargoType> Inventory { get; init; }
|
||||
}
|
||||
}
|
@ -1,18 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
class FleetCarrierTravelConverter : JsonConverter<float>
|
||||
{
|
||||
class FleetCarrierTravelConverter : JsonConverter<float>
|
||||
{
|
||||
public override float Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
return Single.Parse(reader.GetString().Split(' ')[0]);
|
||||
else
|
||||
return float.Parse(reader.GetString().Split(' ')[0]);
|
||||
return reader.GetSingle();
|
||||
}
|
||||
|
||||
@ -20,5 +16,4 @@ namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
public class IntBoolConverter : JsonConverter<bool>
|
||||
{
|
||||
public class IntBoolConverter : JsonConverter<bool>
|
||||
{
|
||||
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return reader.GetInt16() == 1;
|
||||
@ -16,5 +14,4 @@ namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
writer.WriteNumberValue(value ? 1 : 0);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,14 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
public class IntBoolFlexConverter : JsonConverter<bool>
|
||||
{
|
||||
public class IntBoolFlexConverter : JsonConverter<bool>
|
||||
{
|
||||
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.Number)
|
||||
return reader.GetInt16() == 1;
|
||||
else
|
||||
return reader.GetBoolean();
|
||||
}
|
||||
|
||||
@ -18,5 +16,4 @@ namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
writer.WriteBooleanValue(value);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +1,26 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Faction changed from a simple string to an object with additional state information. If we find a string convert it to an object with null state.
|
||||
/// </summary>
|
||||
public class LegacyFactionConverter<TFaction> : JsonConverter<TFaction> where TFaction : Faction, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Faction changed from a simple string to an object with additional state information. If we find a string convert it to an object with null state.
|
||||
/// </summary>
|
||||
public class LegacyFactionConverter<TFaction> : JsonConverter<TFaction> where TFaction : Faction, new()
|
||||
{
|
||||
public override TFaction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
return new TFaction { Name = reader.GetString(), FactionState = null };
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
return JsonSerializer.Deserialize<TFaction>(ref reader);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, TFaction value, JsonSerializerOptions options)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// The format used for materials changed from an object with a key for each material to an array of objects containing "name" and "percent".
|
||||
/// Need to handle both if we're going to read historical data. This reads the old format into a class reflecting the new structure.
|
||||
/// </summary>
|
||||
public class MaterialCompositionConverter : JsonConverter<ImmutableList<MaterialComposition>>
|
||||
{
|
||||
/// <summary>
|
||||
/// The format used for materials changed from an object with a key for each material to an array of objects containing "name" and "percent".
|
||||
/// Need to handle both if we're going to read historical data. This reads the old format into a class reflecting the new structure.
|
||||
/// </summary>
|
||||
public class MaterialCompositionConverter : JsonConverter<ImmutableList<MaterialComposition>>
|
||||
{
|
||||
public override ImmutableList<MaterialComposition> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.StartObject)
|
||||
@ -24,9 +22,9 @@ namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.PropertyName)
|
||||
{
|
||||
string name = reader.GetString();
|
||||
var name = reader.GetString();
|
||||
reader.Read();
|
||||
float percent = reader.GetSingle();
|
||||
var percent = reader.GetSingle();
|
||||
var material = new MaterialComposition
|
||||
{
|
||||
Name = name,
|
||||
@ -42,15 +40,12 @@ namespace Observatory.Framework.Files.Converters
|
||||
}
|
||||
return materialComposition.ToImmutableList();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
return (ImmutableList<MaterialComposition>)JsonSerializer.Deserialize(ref reader, typeof(ImmutableList<MaterialComposition>));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, ImmutableList<MaterialComposition> value, JsonSerializerOptions options)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// The format used for materials changed from an object with a key for each material to an array of objects containing "name" and "percent".
|
||||
/// Need to handle both if we're going to read historical data. This reads the old format into a class reflecting the new structure.
|
||||
/// </summary>
|
||||
public class MaterialConverter : JsonConverter<ImmutableList<Material>>
|
||||
{
|
||||
/// <summary>
|
||||
/// The format used for materials changed from an object with a key for each material to an array of objects containing "name" and "percent".
|
||||
/// Need to handle both if we're going to read historical data. This reads the old format into a class reflecting the new structure.
|
||||
/// </summary>
|
||||
public class MaterialConverter : JsonConverter<ImmutableList<Material>>
|
||||
{
|
||||
public override ImmutableList<Material> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.StartObject)
|
||||
@ -24,9 +22,9 @@ namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.PropertyName)
|
||||
{
|
||||
string name = reader.GetString();
|
||||
var name = reader.GetString();
|
||||
reader.Read();
|
||||
int count = reader.GetInt32();
|
||||
var count = reader.GetInt32();
|
||||
var material = new Material
|
||||
{
|
||||
Name = name,
|
||||
@ -43,15 +41,12 @@ namespace Observatory.Framework.Files.Converters
|
||||
}
|
||||
return materialComposition.ToImmutableList();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
return (ImmutableList<Material>)JsonSerializer.Deserialize(ref reader, typeof(ImmutableList<Material>));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, ImmutableList<Material> value, JsonSerializerOptions options)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,14 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
public class MissionEffectConverter : JsonConverter<MissionEffect>
|
||||
{
|
||||
public class MissionEffectConverter : JsonConverter<MissionEffect>
|
||||
{
|
||||
public override MissionEffect Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
string effect = reader.GetString();
|
||||
var effect = reader.GetString();
|
||||
//TODO: Find out all possible values
|
||||
switch (effect)
|
||||
{
|
||||
@ -24,11 +23,9 @@ namespace Observatory.Framework.Files.Converters
|
||||
case "+++++":
|
||||
effect = "High";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
MissionEffect missionEffect = (MissionEffect)Enum.Parse(typeof(MissionEffect), effect, true);
|
||||
var missionEffect = (MissionEffect)Enum.Parse(typeof(MissionEffect), effect, true);
|
||||
|
||||
return missionEffect;
|
||||
}
|
||||
@ -37,5 +34,4 @@ namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
class MutableStringDoubleConverter : JsonConverter<object>
|
||||
{
|
||||
class MutableStringDoubleConverter : JsonConverter<object>
|
||||
{
|
||||
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
return reader.GetString();
|
||||
else
|
||||
return reader.GetDouble();
|
||||
}
|
||||
|
||||
@ -20,5 +16,4 @@ namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,13 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
class PipConverter : JsonConverter<(int Sys, int Eng, int Wep)>
|
||||
{
|
||||
class PipConverter : JsonConverter<(int Sys, int Eng, int Wep)>
|
||||
{
|
||||
public override (int Sys, int Eng, int Wep) Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
int[] values = (int[])JsonSerializer.Deserialize(ref reader, typeof(int[]));
|
||||
var values = (int[])JsonSerializer.Deserialize(ref reader, typeof(int[]));
|
||||
|
||||
return (Sys: values[0], Eng: values[1], Wep: values[2]);
|
||||
}
|
||||
@ -17,5 +16,4 @@ namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
public class RepInfConverter : JsonConverter<int>
|
||||
{
|
||||
public class RepInfConverter : JsonConverter<int>
|
||||
{
|
||||
public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return reader.GetString().Trim().Length;
|
||||
@ -15,5 +14,4 @@ namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +1,16 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Converting the ordered array of coordinates from the journal to a named tuple for clarity.
|
||||
/// </summary>
|
||||
public class StarPosConverter : JsonConverter<(double x, double y, double z)>
|
||||
{
|
||||
/// <summary>
|
||||
/// Converting the ordered array of coordinates from the journal to a named tuple for clarity.
|
||||
/// </summary>
|
||||
public class StarPosConverter : JsonConverter<(double x, double y, double z)>
|
||||
{
|
||||
public override (double x, double y, double z) Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
double[] values = (double[])JsonSerializer.Deserialize(ref reader, typeof(double[]));
|
||||
var values = (double[])JsonSerializer.Deserialize(ref reader, typeof(double[]));
|
||||
|
||||
return (x: values[0], y: values[1], z: values[2]);
|
||||
}
|
||||
@ -20,5 +19,4 @@ namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +1,14 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
public class StationServiceConverter : JsonConverter<StationService>
|
||||
{
|
||||
public class StationServiceConverter : JsonConverter<StationService>
|
||||
{
|
||||
public override StationService Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
StationService services = StationService.None;
|
||||
var services = StationService.None;
|
||||
|
||||
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
|
||||
{
|
||||
@ -24,5 +23,4 @@ namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
class StringIntConverter : JsonConverter<int>
|
||||
{
|
||||
class StringIntConverter : JsonConverter<int>
|
||||
{
|
||||
public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
return Int32.Parse(reader.GetString());
|
||||
else
|
||||
return int.Parse(reader.GetString());
|
||||
return reader.GetInt32();
|
||||
}
|
||||
|
||||
@ -20,5 +16,4 @@ namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
class ThargoidWarRemainingTimeConverter : JsonConverter<int>
|
||||
{
|
||||
class ThargoidWarRemainingTimeConverter : JsonConverter<int>
|
||||
{
|
||||
public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
string value = reader.GetString();
|
||||
var value = reader.GetString();
|
||||
|
||||
int dayCount = Int32.TryParse(value.Split(' ')[0], out int days)
|
||||
var dayCount = int.TryParse(value.Split(' ')[0], out var days)
|
||||
? days
|
||||
: 0;
|
||||
|
||||
return dayCount;
|
||||
}
|
||||
else
|
||||
|
||||
return reader.GetInt32();
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString() + " Days");
|
||||
}
|
||||
writer.WriteStringValue(value + " Days");
|
||||
}
|
||||
}
|
@ -1,22 +1,19 @@
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Observatory.Framework.Files.Converters
|
||||
namespace Observatory.Framework.Files.Converters;
|
||||
|
||||
class VoucherTypeConverter : JsonConverter<VoucherType>
|
||||
{
|
||||
class VoucherTypeConverter : JsonConverter<VoucherType>
|
||||
{
|
||||
public override VoucherType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
string voucher = reader.GetString();
|
||||
var voucher = reader.GetString();
|
||||
|
||||
if (voucher.Length == 0)
|
||||
voucher = "None";
|
||||
|
||||
VoucherType missionEffect = (VoucherType)Enum.Parse(typeof(VoucherType), voucher, true);
|
||||
var missionEffect = (VoucherType)Enum.Parse(typeof(VoucherType), voucher, true);
|
||||
|
||||
return missionEffect;
|
||||
}
|
||||
@ -25,5 +22,4 @@ namespace Observatory.Framework.Files.Converters
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Immutable;
|
||||
using Observatory.Framework.Files.Journal;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Observatory.Framework.Files
|
||||
namespace Observatory.Framework.Files;
|
||||
|
||||
/// <summary>
|
||||
/// Elite Dangerous fcmaterials.json file. Contains data about current fleet carrier bartender stock.
|
||||
/// </summary>
|
||||
public class FCMaterialsFile : JournalBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Elite Dangerous fcmaterials.json file. Contains data about current fleet carrier bartender stock.
|
||||
/// </summary>
|
||||
public class FCMaterialsFile : Journal.JournalBase
|
||||
{
|
||||
/// <summary>
|
||||
/// List of items in stock and in demand from the carrier bartender.
|
||||
/// </summary>
|
||||
public ImmutableList<FCMaterial> Items { get; init; }
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Immutable;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Observatory.Framework.Files.Journal
|
||||
namespace Observatory.Framework.Files.Journal.Combat;
|
||||
|
||||
public class Bounty : JournalBase
|
||||
{
|
||||
public class Bounty : JournalBase
|
||||
{
|
||||
public ImmutableList<Rewards> Rewards { get; init; }
|
||||
public string PilotName { get; set; }
|
||||
public string PilotName_Localised { get; set; }
|
||||
@ -17,5 +17,4 @@ namespace Observatory.Framework.Files.Journal
|
||||
public string VictimFaction { get; init; }
|
||||
public string VictimFaction_Localised { get; init; }
|
||||
public int SharedWithOthers { get; init; }
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
namespace Observatory.Framework.Files.Journal
|
||||
namespace Observatory.Framework.Files.Journal.Combat;
|
||||
|
||||
public class CapShipBound : JournalBase
|
||||
{
|
||||
public class CapShipBound : JournalBase
|
||||
{
|
||||
public long Reward { get; init; }
|
||||
public string AwardingFaction { get; init; }
|
||||
public string VictimFaction { get; init; }
|
||||
}
|
||||
}
|
@ -1,14 +1,13 @@
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Immutable;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Observatory.Framework.Files.Journal
|
||||
namespace Observatory.Framework.Files.Journal.Combat;
|
||||
|
||||
public class Died : JournalBase
|
||||
{
|
||||
public class Died : JournalBase
|
||||
{
|
||||
public string KillerName { get; init; }
|
||||
public string KillerName_Localised { get; init; }
|
||||
public string KillerShip { get; init; }
|
||||
public string KillerRank { get; init; }
|
||||
public ImmutableList<Killer> Killers { get; init; }
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
namespace Observatory.Framework.Files.Journal
|
||||
namespace Observatory.Framework.Files.Journal.Combat;
|
||||
|
||||
public class EscapeInterdiction : JournalBase
|
||||
{
|
||||
public class EscapeInterdiction : JournalBase
|
||||
{
|
||||
public string Interdictor { get; init; }
|
||||
public bool IsPlayer { get; init; }
|
||||
public bool IsThargoid { get; init; }
|
||||
}
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
namespace Observatory.Framework.Files.Journal
|
||||
namespace Observatory.Framework.Files.Journal.Combat;
|
||||
|
||||
public class FactionKillBond : JournalBase
|
||||
{
|
||||
public class FactionKillBond : JournalBase
|
||||
{
|
||||
public long Reward { get; init; }
|
||||
public string AwardingFaction { get; init; }
|
||||
public string AwardingFaction_Localised { get; init; }
|
||||
public string VictimFaction { get; init; }
|
||||
public string VictimFaction_Localised { get; init; }
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
namespace Observatory.Framework.Files.Journal
|
||||
namespace Observatory.Framework.Files.Journal.Combat;
|
||||
|
||||
public class FighterDestroyed : JournalBase
|
||||
{
|
||||
public class FighterDestroyed : JournalBase
|
||||
{
|
||||
public ulong ID { get; init; }
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
namespace Observatory.Framework.Files.Journal
|
||||
{
|
||||
public class HeatDamage : JournalBase
|
||||
{ }
|
||||
}
|
||||
namespace Observatory.Framework.Files.Journal.Combat;
|
||||
|
||||
public class HeatDamage : JournalBase
|
||||
{ }
|
@ -1,6 +1,5 @@
|
||||
namespace Observatory.Framework.Files.Journal
|
||||
namespace Observatory.Framework.Files.Journal.Combat;
|
||||
|
||||
public class HeatWarning : JournalBase
|
||||
{
|
||||
public class HeatWarning : JournalBase
|
||||
{
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
namespace Observatory.Framework.Files.Journal
|
||||
namespace Observatory.Framework.Files.Journal.Combat;
|
||||
|
||||
public class HullDamage : JournalBase
|
||||
{
|
||||
public class HullDamage : JournalBase
|
||||
{
|
||||
public float Health { get; init; }
|
||||
public bool PlayerPilot { get; init; }
|
||||
public bool Fighter { get; init; }
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
namespace Observatory.Framework.Files.Journal
|
||||
namespace Observatory.Framework.Files.Journal.Combat;
|
||||
|
||||
public class Interdicted : JournalBase
|
||||
{
|
||||
public class Interdicted : JournalBase
|
||||
{
|
||||
public bool Submitted { get; init; }
|
||||
public string Interdictor { get; init; }
|
||||
public string Interdictor_Localised { get; init; }
|
||||
@ -10,5 +10,4 @@
|
||||
public string Faction { get; init; }
|
||||
public string Power { get; init; }
|
||||
public bool IsThargoid { get; init; }
|
||||
}
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
namespace Observatory.Framework.Files.Journal
|
||||
namespace Observatory.Framework.Files.Journal.Combat;
|
||||
|
||||
public class Interdiction : JournalBase
|
||||
{
|
||||
public class Interdiction : JournalBase
|
||||
{
|
||||
public bool Success { get; init; }
|
||||
public string Interdictor { get; init; }
|
||||
public bool IsPlayer { get; init; }
|
||||
public int CombatRank { get; init; }
|
||||
public string Faction { get; init; }
|
||||
public string Power { get; init; }
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
namespace Observatory.Framework.Files.Journal
|
||||
namespace Observatory.Framework.Files.Journal.Combat;
|
||||
|
||||
public class PVPKill : JournalBase
|
||||
{
|
||||
public class PVPKill : JournalBase
|
||||
{
|
||||
public string Victim { get; init; }
|
||||
public int CombatRank { get; init; }
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
namespace Observatory.Framework.Files.Journal
|
||||
namespace Observatory.Framework.Files.Journal.Combat;
|
||||
|
||||
public class SRVDestroyed : JournalBase
|
||||
{
|
||||
public class SRVDestroyed : JournalBase
|
||||
{
|
||||
public string SRVType { get; init; }
|
||||
public string SRVType_Localised { get; init; }
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user