Rework Journal File Reading
Remove Explorer Remove Plugin Architecture
This commit is contained in:
parent
c0c69dcdf7
commit
256ebb179e
42 changed files with 835 additions and 2787 deletions
8
Botanist/BioPlanetDetail.cs
Normal file
8
Botanist/BioPlanetDetail.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Botanist;
|
||||||
|
|
||||||
|
class BioPlanetDetail
|
||||||
|
{
|
||||||
|
public string BodyName { get; set; }
|
||||||
|
public int BioTotal { get; set; }
|
||||||
|
public Dictionary<string, BioSampleDetail> SpeciesFound { get; set; }
|
||||||
|
}
|
||||||
7
Botanist/BioSampleDetail.cs
Normal file
7
Botanist/BioSampleDetail.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Botanist;
|
||||||
|
|
||||||
|
class BioSampleDetail
|
||||||
|
{
|
||||||
|
public string Genus { get; set; }
|
||||||
|
public bool Analysed { get; set; }
|
||||||
|
}
|
||||||
33
Botanist/BodyAddress.cs
Normal file
33
Botanist/BodyAddress.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
namespace Botanist;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Reflection;
|
|
||||||
using Observatory.Framework;
|
using Observatory.Framework;
|
||||||
using Observatory.Framework.Files.Journal;
|
using Observatory.Framework.Files.Journal;
|
||||||
using Observatory.Framework.Files.Journal.Exploration;
|
using Observatory.Framework.Files.Journal.Exploration;
|
||||||
|
|
@ -19,7 +18,7 @@ public class Botanist : IObservatoryWorker
|
||||||
|
|
||||||
// To make this journal locale agnostic, use the genus identifier and map to English names used in notifications.
|
// 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.
|
// 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()
|
public static readonly IReadOnlyDictionary<string, string> EnglishGenusByIdentifier = new Dictionary<string, string>
|
||||||
{
|
{
|
||||||
{ "$Codex_Ent_Aleoids_Genus_Name;", "Aleoida" },
|
{ "$Codex_Ent_Aleoids_Genus_Name;", "Aleoida" },
|
||||||
{ "$Codex_Ent_Bacterial_Genus_Name;", "Bacterium" },
|
{ "$Codex_Ent_Bacterial_Genus_Name;", "Bacterium" },
|
||||||
|
|
@ -51,7 +50,7 @@ public class Botanist : IObservatoryWorker
|
||||||
};
|
};
|
||||||
|
|
||||||
// Note: Some Horizons bios may be missing, but they'll get localized genus name and default colony distance
|
// 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()
|
public static readonly IReadOnlyDictionary<string, int> ColonyDistancesByGenus = new Dictionary<string, int>()
|
||||||
{
|
{
|
||||||
{ "Aleoida", 150 },
|
{ "Aleoida", 150 },
|
||||||
{ "Bacterium", 500 },
|
{ "Bacterium", 500 },
|
||||||
|
|
@ -99,7 +98,7 @@ public class Botanist : IObservatoryWorker
|
||||||
public object Settings
|
public object Settings
|
||||||
{
|
{
|
||||||
get => botanistSettings;
|
get => botanistSettings;
|
||||||
set { botanistSettings = (BotanistSettings)value; }
|
set => botanistSettings = (BotanistSettings)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void JournalEvent<TJournal>(TJournal journal) where TJournal : JournalBase
|
public void JournalEvent<TJournal>(TJournal journal) where TJournal : JournalBase
|
||||||
|
|
@ -216,8 +215,6 @@ public class Botanist : IObservatoryWorker
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateUIGrid();
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case LeaveBody:
|
case LeaveBody:
|
||||||
|
|
@ -259,121 +256,4 @@ public class Botanist : IObservatoryWorker
|
||||||
|
|
||||||
Core = observatoryCore;
|
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; }
|
|
||||||
}
|
}
|
||||||
10
Botanist/BotanistGrid.cs
Normal file
10
Botanist/BotanistGrid.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace Botanist;
|
||||||
|
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,382 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,389 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,304 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 16
|
|
||||||
VisualStudioVersion = 16.0.31205.134
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ObservatoryExplorer", "ObservatoryExplorer.csproj", "{E0FCF2A2-BF56-4F4D-836B-92A0E8269192}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{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}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {196B0F23-25FC-4A58-A7A9-2676C7749FFD}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
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; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
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; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
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,20 +1,22 @@
|
||||||
namespace Observatory.Framework;
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace Observatory.Framework;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides data for Elite Dangerous journal events.
|
/// Provides data for Elite Dangerous journal events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JournalEventArgs : EventArgs
|
public class JournalEventArgs(string journalEventType, JsonObject journalEvent) : EventArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <para>Type of journal entry that triggered event.</para>
|
/// <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>
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Type journalType;
|
public string JournalEventType = journalEventType;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <para>Elite Dangerous journal event, deserialized into a .NET object of the type specified by JournalEventArgs.journalType.</para>
|
/// <para>Elite Dangerous journal event, deserialized into a .NET object of the type specified by JournalEventArgs.journalType.</para>
|
||||||
/// <para>Unhandled json values within a journal entry type will be contained in member property:<br/>Dictionary<string, object> AdditionalProperties.</para>
|
/// <para>Unhandled json values within a journal entry type will be contained in member property:<br/>Dictionary<string, object> AdditionalProperties.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public object journalEvent;
|
public JsonObject JournalEvent = journalEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -26,48 +28,59 @@ public class NotificationArgs
|
||||||
/// Text typically displayed as header content.
|
/// Text typically displayed as header content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Title;
|
public string Title;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SSML representation of Title text.<br/>
|
/// SSML representation of Title text.<br/>
|
||||||
/// This value is optional, if omitted the value of <c>NotificationArgs.Title</c> will be used for voice synthesis without markup.
|
/// This value is optional, if omitted the value of <c>NotificationArgs.Title</c> will be used for voice synthesis without markup.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string TitleSsml;
|
public string TitleSsml;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Text typically displayed as body content.
|
/// Text typically displayed as body content.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Detail;
|
public string Detail;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SSML representation of Detail text.<br/>
|
/// SSML representation of Detail text.<br/>
|
||||||
/// This value is optional, if omitted the value of <c>NotificationArgs.Detail</c> will be used for voice synthesis without markup.
|
/// This value is optional, if omitted the value of <c>NotificationArgs.Detail</c> will be used for voice synthesis without markup.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string DetailSsml;
|
public string DetailSsml;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specify window timeout in ms (overrides Core setting). Specify 0 timeout to persist until removed via IObservatoryCore.CancelNotification. Default -1 (use Core setting).
|
/// Specify window timeout in ms (overrides Core setting). Specify 0 timeout to persist until removed via IObservatoryCore.CancelNotification. Default -1 (use Core setting).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Timeout = -1;
|
public int Timeout = -1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specify window X position as a percentage from upper left corner (overrides Core setting). Default -1.0 (use Core setting).
|
/// Specify window X position as a percentage from upper left corner (overrides Core setting). Default -1.0 (use Core setting).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double XPos = -1.0;
|
public double XPos = -1.0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specify window Y position as a percentage from upper left corner (overrides Core setting). Default -1.0 (use Core setting).
|
/// Specify window Y position as a percentage from upper left corner (overrides Core setting). Default -1.0 (use Core setting).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double YPos = -1.0;
|
public double YPos = -1.0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies the desired renderings of the notification. Defaults to <see cref="NotificationRendering.All"/>.
|
/// Specifies the desired renderings of the notification. Defaults to <see cref="NotificationRendering.All"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public NotificationRendering Rendering = NotificationRendering.All;
|
public NotificationRendering Rendering = NotificationRendering.All;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies if some part of the notification should be suppressed. Not supported by all notifiers. Defaults to <see cref="NotificationSuppression.None"/>.
|
/// Specifies if some part of the notification should be suppressed. Not supported by all notifiers. Defaults to <see cref="NotificationSuppression.None"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public NotificationSuppression Suppression = NotificationSuppression.None;
|
public NotificationSuppression Suppression = NotificationSuppression.None;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The plugin sending this notification.
|
/// The plugin sending this notification.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Sender = "";
|
public string Sender = "";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Additional notification detailed (generally not rendered by voice or popup; potentially used by aggregating/logging plugins).
|
/// Additional notification detailed (generally not rendered by voice or popup; potentially used by aggregating/logging plugins).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ExtendedDetails;
|
public string ExtendedDetails;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
/// 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>
|
/// </summary>
|
||||||
|
|
@ -84,10 +97,12 @@ public enum NotificationSuppression
|
||||||
/// No suppression.
|
/// No suppression.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
None = 0,
|
None = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Suppress title.
|
/// Suppress title.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Title = 1,
|
Title = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Suppress detail.
|
/// Suppress detail.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -104,14 +119,17 @@ public enum NotificationRendering
|
||||||
/// Send notification to native visual popup notificaiton handler.
|
/// Send notification to native visual popup notificaiton handler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NativeVisual = 1,
|
NativeVisual = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send notification to native speech notification handler.
|
/// Send notification to native speech notification handler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
NativeVocal = 2,
|
NativeVocal = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send notification to all installed notifier plugins.
|
/// Send notification to all installed notifier plugins.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PluginNotifier = 4,
|
PluginNotifier = 4,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send notification to all available handlers.
|
/// Send notification to all available handlers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -128,18 +146,21 @@ public enum LogMonitorState : uint
|
||||||
/// Monitoring is stopped.
|
/// Monitoring is stopped.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Idle = 0,
|
Idle = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Real-time monitoring is active.
|
/// Real-time monitoring is active.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Realtime = 1,
|
Realtime = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Batch read of historical journals is in progress.
|
/// Batch read of historical journals is in progress.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Batch = 2,
|
BatchProcessing = 2,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Batch read of recent journals is in progress to establish current player state.
|
/// Initial read of recent journals to establish current player state is in progress .
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PreRead = 4
|
Init = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -164,6 +185,6 @@ public class LogMonitorStateChangedEventArgs : EventArgs
|
||||||
/// <returns>A boolean; True iff the state provided represents a batch-mode read.</returns>
|
/// <returns>A boolean; True iff the state provided represents a batch-mode read.</returns>
|
||||||
public static bool IsBatchRead(LogMonitorState state)
|
public static bool IsBatchRead(LogMonitorState state)
|
||||||
{
|
{
|
||||||
return state.HasFlag(LogMonitorState.Batch) || state.HasFlag(LogMonitorState.PreRead);
|
return state.HasFlag(LogMonitorState.BatchProcessing) || state.HasFlag(LogMonitorState.Init);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,11 +5,5 @@ public class CarrierDecommission : JournalBase
|
||||||
public ulong CarrierID { get; init; }
|
public ulong CarrierID { get; init; }
|
||||||
public long ScrapRefund { get; init; }
|
public long ScrapRefund { get; init; }
|
||||||
public long ScrapTime { get; init; }
|
public long ScrapTime { get; init; }
|
||||||
public DateTime ScrapTimeUTC
|
public DateTime ScrapTimeUTC => DateTimeOffset.FromUnixTimeSeconds(ScrapTime).UtcDateTime;
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return DateTimeOffset.FromUnixTimeSeconds(ScrapTime).UtcDateTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System.Text.Json.Serialization;
|
namespace Observatory.Framework.Files.Journal.FleetCarrier;
|
||||||
|
|
||||||
namespace Observatory.Framework.Files.Journal.FleetCarrier;
|
|
||||||
|
|
||||||
public class CarrierJumpRequest : JournalBase
|
public class CarrierJumpRequest : JournalBase
|
||||||
{
|
{
|
||||||
|
|
@ -10,10 +8,5 @@ public class CarrierJumpRequest : JournalBase
|
||||||
public ulong CarrierID { get; init; }
|
public ulong CarrierID { get; init; }
|
||||||
public string SystemName { get; init; }
|
public string SystemName { get; init; }
|
||||||
public ulong SystemID { get; init; }
|
public ulong SystemID { get; init; }
|
||||||
public string DepartureTime { get; init; }
|
public DateTimeOffset DepartureTime { get; init; }
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public DateTimeOffset DepartureTimeDateTime {
|
|
||||||
get => ParseDateTime(DepartureTime);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,52 +1,289 @@
|
||||||
using System.Globalization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Text.Json.Serialization;
|
using Observatory.Framework.Files.Journal.Combat;
|
||||||
|
using Observatory.Framework.Files.Journal.Exploration;
|
||||||
|
using Observatory.Framework.Files.Journal.FleetCarrier;
|
||||||
|
using Observatory.Framework.Files.Journal.Odyssey;
|
||||||
|
using Observatory.Framework.Files.Journal.Other;
|
||||||
|
using Observatory.Framework.Files.Journal.Powerplay;
|
||||||
|
using Observatory.Framework.Files.Journal.Squadron;
|
||||||
|
using Observatory.Framework.Files.Journal.Startup;
|
||||||
|
using Observatory.Framework.Files.Journal.StationServices;
|
||||||
|
using Observatory.Framework.Files.Journal.Trade;
|
||||||
|
using Observatory.Framework.Files.Journal.Travel;
|
||||||
|
|
||||||
namespace Observatory.Framework.Files.Journal;
|
namespace Observatory.Framework.Files.Journal;
|
||||||
|
[JsonDerivedType(typeof(BackpackFile))]
|
||||||
|
[JsonDerivedType(typeof(CargoFile))]
|
||||||
|
[JsonDerivedType(typeof(FCMaterialsFile))]
|
||||||
|
[JsonDerivedType(typeof(Bounty))]
|
||||||
|
[JsonDerivedType(typeof(CapShipBound))]
|
||||||
|
[JsonDerivedType(typeof(Died))]
|
||||||
|
[JsonDerivedType(typeof(EscapeInterdiction))]
|
||||||
|
[JsonDerivedType(typeof(FactionKillBond))]
|
||||||
|
[JsonDerivedType(typeof(FighterDestroyed))]
|
||||||
|
[JsonDerivedType(typeof(HeatDamage))]
|
||||||
|
[JsonDerivedType(typeof(HeatWarning))]
|
||||||
|
[JsonDerivedType(typeof(HullDamage))]
|
||||||
|
[JsonDerivedType(typeof(Interdicted))]
|
||||||
|
[JsonDerivedType(typeof(Interdiction))]
|
||||||
|
[JsonDerivedType(typeof(PVPKill))]
|
||||||
|
[JsonDerivedType(typeof(SRVDestroyed))]
|
||||||
|
[JsonDerivedType(typeof(ShieldState))]
|
||||||
|
[JsonDerivedType(typeof(ShipTargeted))]
|
||||||
|
[JsonDerivedType(typeof(UnderAttack))]
|
||||||
|
[JsonDerivedType(typeof(BuyExplorationData))]
|
||||||
|
[JsonDerivedType(typeof(CodexEntry))]
|
||||||
|
[JsonDerivedType(typeof(DiscoveryScan))]
|
||||||
|
[JsonDerivedType(typeof(FSSAllBodiesFound))]
|
||||||
|
[JsonDerivedType(typeof(FSSBodySignals))]
|
||||||
|
[JsonDerivedType(typeof(FSSDiscoveryScan))]
|
||||||
|
[JsonDerivedType(typeof(FSSSignalDiscovered))]
|
||||||
|
[JsonDerivedType(typeof(MaterialCollected))]
|
||||||
|
[JsonDerivedType(typeof(MaterialDiscarded))]
|
||||||
|
[JsonDerivedType(typeof(MaterialDiscovered))]
|
||||||
|
[JsonDerivedType(typeof(MultiSellExplorationData))]
|
||||||
|
[JsonDerivedType(typeof(NavBeaconScan))]
|
||||||
|
[JsonDerivedType(typeof(SAAScanComplete))]
|
||||||
|
[JsonDerivedType(typeof(SAASignalsFound))]
|
||||||
|
[JsonDerivedType(typeof(Scan))]
|
||||||
|
[JsonDerivedType(typeof(ScanBaryCentre))]
|
||||||
|
[JsonDerivedType(typeof(Screenshot))]
|
||||||
|
[JsonDerivedType(typeof(SellExplorationData))]
|
||||||
|
[JsonDerivedType(typeof(CarrierBankTransfer))]
|
||||||
|
[JsonDerivedType(typeof(CarrierBuy))]
|
||||||
|
[JsonDerivedType(typeof(CarrierCancelDecommission))]
|
||||||
|
[JsonDerivedType(typeof(CarrierCrewServices))]
|
||||||
|
[JsonDerivedType(typeof(CarrierDecommission))]
|
||||||
|
[JsonDerivedType(typeof(CarrierDepositFuel))]
|
||||||
|
[JsonDerivedType(typeof(CarrierDockingPermission))]
|
||||||
|
[JsonDerivedType(typeof(CarrierFinance))]
|
||||||
|
[JsonDerivedType(typeof(CarrierJump))]
|
||||||
|
[JsonDerivedType(typeof(CarrierJumpCancelled))]
|
||||||
|
[JsonDerivedType(typeof(CarrierJumpRequest))]
|
||||||
|
[JsonDerivedType(typeof(CarrierModulePack))]
|
||||||
|
[JsonDerivedType(typeof(CarrierShipPack))]
|
||||||
|
[JsonDerivedType(typeof(CarrierStats))]
|
||||||
|
[JsonDerivedType(typeof(CarrierTradeOrder))]
|
||||||
|
[JsonDerivedType(typeof(FCMaterlas))]
|
||||||
|
[JsonDerivedType(typeof(InvalidJson))]
|
||||||
|
[JsonDerivedType(typeof(BackPack))]
|
||||||
|
[JsonDerivedType(typeof(BackpackChange))]
|
||||||
|
[JsonDerivedType(typeof(BackpackMaterials))]
|
||||||
|
[JsonDerivedType(typeof(BookDropship))]
|
||||||
|
[JsonDerivedType(typeof(BookTaxi))]
|
||||||
|
[JsonDerivedType(typeof(BuyMicroResources))]
|
||||||
|
[JsonDerivedType(typeof(BuySuit))]
|
||||||
|
[JsonDerivedType(typeof(BuyWeapon))]
|
||||||
|
[JsonDerivedType(typeof(CancelDropship))]
|
||||||
|
[JsonDerivedType(typeof(CancelTaxi))]
|
||||||
|
[JsonDerivedType(typeof(CollectItems))]
|
||||||
|
[JsonDerivedType(typeof(CreateSuitLoadout))]
|
||||||
|
[JsonDerivedType(typeof(DeleteSuitLoadout))]
|
||||||
|
[JsonDerivedType(typeof(Disembark))]
|
||||||
|
[JsonDerivedType(typeof(DropItems))]
|
||||||
|
[JsonDerivedType(typeof(DropShipDeploy))]
|
||||||
|
[JsonDerivedType(typeof(Embark))]
|
||||||
|
[JsonDerivedType(typeof(FCMaterials))]
|
||||||
|
[JsonDerivedType(typeof(LoadoutEquipModule))]
|
||||||
|
[JsonDerivedType(typeof(LoadoutRemoveModule))]
|
||||||
|
[JsonDerivedType(typeof(RenameSuitLoadout))]
|
||||||
|
[JsonDerivedType(typeof(ScanOrganic))]
|
||||||
|
[JsonDerivedType(typeof(SellMicroResources))]
|
||||||
|
[JsonDerivedType(typeof(SellOrganicData))]
|
||||||
|
[JsonDerivedType(typeof(SellSuit))]
|
||||||
|
[JsonDerivedType(typeof(SellWeapon))]
|
||||||
|
[JsonDerivedType(typeof(ShipLockerMaterials))]
|
||||||
|
[JsonDerivedType(typeof(SuitLoadout))]
|
||||||
|
[JsonDerivedType(typeof(SwitchSuitLoadout))]
|
||||||
|
[JsonDerivedType(typeof(TradeMicroResources))]
|
||||||
|
[JsonDerivedType(typeof(TransferMicroResources))]
|
||||||
|
[JsonDerivedType(typeof(UpgradeSuit))]
|
||||||
|
[JsonDerivedType(typeof(UpgradeWeapon))]
|
||||||
|
[JsonDerivedType(typeof(UseConsumable))]
|
||||||
|
[JsonDerivedType(typeof(AfmuRepairs))]
|
||||||
|
[JsonDerivedType(typeof(ApproachSettlement))]
|
||||||
|
[JsonDerivedType(typeof(CargoTransfer))]
|
||||||
|
[JsonDerivedType(typeof(ChangeCrewRole))]
|
||||||
|
[JsonDerivedType(typeof(CockpitBreached))]
|
||||||
|
[JsonDerivedType(typeof(CommitCrime))]
|
||||||
|
[JsonDerivedType(typeof(Continued))]
|
||||||
|
[JsonDerivedType(typeof(CrewLaunchFighter))]
|
||||||
|
[JsonDerivedType(typeof(CrewMemberJoins))]
|
||||||
|
[JsonDerivedType(typeof(CrewMemberQuits))]
|
||||||
|
[JsonDerivedType(typeof(CrewMemberRoleChange))]
|
||||||
|
[JsonDerivedType(typeof(CrimeVictim))]
|
||||||
|
[JsonDerivedType(typeof(DataScanned))]
|
||||||
|
[JsonDerivedType(typeof(DatalinkScan))]
|
||||||
|
[JsonDerivedType(typeof(DatalinkVoucher))]
|
||||||
|
[JsonDerivedType(typeof(DockFighter))]
|
||||||
|
[JsonDerivedType(typeof(DockSRV))]
|
||||||
|
[JsonDerivedType(typeof(EndCrewSession))]
|
||||||
|
[JsonDerivedType(typeof(FighterRebuilt))]
|
||||||
|
[JsonDerivedType(typeof(Friends))]
|
||||||
|
[JsonDerivedType(typeof(FuelScoop))]
|
||||||
|
[JsonDerivedType(typeof(JetConeBoost))]
|
||||||
|
[JsonDerivedType(typeof(JetConeDamage))]
|
||||||
|
[JsonDerivedType(typeof(JoinACrew))]
|
||||||
|
[JsonDerivedType(typeof(KickCrewMember))]
|
||||||
|
[JsonDerivedType(typeof(LaunchDrone))]
|
||||||
|
[JsonDerivedType(typeof(LaunchFighter))]
|
||||||
|
[JsonDerivedType(typeof(LaunchSRV))]
|
||||||
|
[JsonDerivedType(typeof(ModuleInfo))]
|
||||||
|
[JsonDerivedType(typeof(Music))]
|
||||||
|
[JsonDerivedType(typeof(NpcCrewPaidWage))]
|
||||||
|
[JsonDerivedType(typeof(NpcCrewRank))]
|
||||||
|
[JsonDerivedType(typeof(Promotion))]
|
||||||
|
[JsonDerivedType(typeof(ProspectedAsteroid))]
|
||||||
|
[JsonDerivedType(typeof(QuitACrew))]
|
||||||
|
[JsonDerivedType(typeof(RebootRepair))]
|
||||||
|
[JsonDerivedType(typeof(ReceiveText))]
|
||||||
|
[JsonDerivedType(typeof(RepairDrone))]
|
||||||
|
[JsonDerivedType(typeof(ReservoirReplenished))]
|
||||||
|
[JsonDerivedType(typeof(Resurrect))]
|
||||||
|
[JsonDerivedType(typeof(Scanned))]
|
||||||
|
[JsonDerivedType(typeof(SelfDestruct))]
|
||||||
|
[JsonDerivedType(typeof(SendText))]
|
||||||
|
[JsonDerivedType(typeof(Shutdown))]
|
||||||
|
[JsonDerivedType(typeof(Synthesis))]
|
||||||
|
[JsonDerivedType(typeof(SystemsShutdown))]
|
||||||
|
[JsonDerivedType(typeof(USSDrop))]
|
||||||
|
[JsonDerivedType(typeof(VehicleSwitch))]
|
||||||
|
[JsonDerivedType(typeof(WingAdd))]
|
||||||
|
[JsonDerivedType(typeof(WingInvite))]
|
||||||
|
[JsonDerivedType(typeof(WingJoin))]
|
||||||
|
[JsonDerivedType(typeof(WingLeave))]
|
||||||
|
[JsonDerivedType(typeof(PowerplayCollect))]
|
||||||
|
[JsonDerivedType(typeof(PowerplayDefect))]
|
||||||
|
[JsonDerivedType(typeof(PowerplayDeliver))]
|
||||||
|
[JsonDerivedType(typeof(PowerplayFastTrack))]
|
||||||
|
[JsonDerivedType(typeof(PowerplayJoin))]
|
||||||
|
[JsonDerivedType(typeof(PowerplayLeave))]
|
||||||
|
[JsonDerivedType(typeof(PowerplaySalary))]
|
||||||
|
[JsonDerivedType(typeof(PowerplayVote))]
|
||||||
|
[JsonDerivedType(typeof(PowerplayVoucher))]
|
||||||
|
[JsonDerivedType(typeof(AppliedToSquadron))]
|
||||||
|
[JsonDerivedType(typeof(DisbandedSquadron))]
|
||||||
|
[JsonDerivedType(typeof(InvitedToSquadron))]
|
||||||
|
[JsonDerivedType(typeof(JoinedSquadron))]
|
||||||
|
[JsonDerivedType(typeof(KickedFromSquadron))]
|
||||||
|
[JsonDerivedType(typeof(LeftSquadron))]
|
||||||
|
[JsonDerivedType(typeof(SharedBookmarkToSquadron))]
|
||||||
|
[JsonDerivedType(typeof(SquadronCreated))]
|
||||||
|
[JsonDerivedType(typeof(SquadronDemotion))]
|
||||||
|
[JsonDerivedType(typeof(SquadronPromotion))]
|
||||||
|
[JsonDerivedType(typeof(SquadronStartup))]
|
||||||
|
[JsonDerivedType(typeof(WonATrophyForSquadron))]
|
||||||
|
[JsonDerivedType(typeof(Cargo))]
|
||||||
|
[JsonDerivedType(typeof(ClearSavedGame))]
|
||||||
|
[JsonDerivedType(typeof(Commander))]
|
||||||
|
[JsonDerivedType(typeof(FileHeader))]
|
||||||
|
[JsonDerivedType(typeof(LoadGame))]
|
||||||
|
[JsonDerivedType(typeof(Loadout))]
|
||||||
|
[JsonDerivedType(typeof(Materials))]
|
||||||
|
[JsonDerivedType(typeof(Missions))]
|
||||||
|
[JsonDerivedType(typeof(NewCommander))]
|
||||||
|
[JsonDerivedType(typeof(Passengers))]
|
||||||
|
[JsonDerivedType(typeof(Startup.Powerplay))]
|
||||||
|
[JsonDerivedType(typeof(Progress))]
|
||||||
|
[JsonDerivedType(typeof(Rank))]
|
||||||
|
[JsonDerivedType(typeof(Reputation))]
|
||||||
|
[JsonDerivedType(typeof(Statistics))]
|
||||||
|
[JsonDerivedType(typeof(BuyAmmo))]
|
||||||
|
[JsonDerivedType(typeof(BuyDrones))]
|
||||||
|
[JsonDerivedType(typeof(CargoDepot))]
|
||||||
|
[JsonDerivedType(typeof(ClearImpound))]
|
||||||
|
[JsonDerivedType(typeof(CommunityGoal))]
|
||||||
|
[JsonDerivedType(typeof(CommunityGoalDiscard))]
|
||||||
|
[JsonDerivedType(typeof(CommunityGoalJoin))]
|
||||||
|
[JsonDerivedType(typeof(CommunityGoalReward))]
|
||||||
|
[JsonDerivedType(typeof(CrewAssign))]
|
||||||
|
[JsonDerivedType(typeof(CrewFire))]
|
||||||
|
[JsonDerivedType(typeof(CrewHire))]
|
||||||
|
[JsonDerivedType(typeof(EngineerApply))]
|
||||||
|
[JsonDerivedType(typeof(EngineerContribution))]
|
||||||
|
[JsonDerivedType(typeof(EngineerCraft))]
|
||||||
|
[JsonDerivedType(typeof(EngineerLegacyConvert))]
|
||||||
|
[JsonDerivedType(typeof(EngineerProgress))]
|
||||||
|
[JsonDerivedType(typeof(FetchRemoteModule))]
|
||||||
|
[JsonDerivedType(typeof(Market))]
|
||||||
|
[JsonDerivedType(typeof(MassModuleStore))]
|
||||||
|
[JsonDerivedType(typeof(MaterialTrade))]
|
||||||
|
[JsonDerivedType(typeof(MissionAbandoned))]
|
||||||
|
[JsonDerivedType(typeof(MissionAccepted))]
|
||||||
|
[JsonDerivedType(typeof(MissionCompleted))]
|
||||||
|
[JsonDerivedType(typeof(MissionFailed))]
|
||||||
|
[JsonDerivedType(typeof(MissionRedirected))]
|
||||||
|
[JsonDerivedType(typeof(ModuleBuy))]
|
||||||
|
[JsonDerivedType(typeof(ModuleRetrieve))]
|
||||||
|
[JsonDerivedType(typeof(ModuleSell))]
|
||||||
|
[JsonDerivedType(typeof(ModuleSellRemote))]
|
||||||
|
[JsonDerivedType(typeof(ModuleStore))]
|
||||||
|
[JsonDerivedType(typeof(ModuleSwap))]
|
||||||
|
[JsonDerivedType(typeof(Outfitting))]
|
||||||
|
[JsonDerivedType(typeof(PayBounties))]
|
||||||
|
[JsonDerivedType(typeof(PayFines))]
|
||||||
|
[JsonDerivedType(typeof(PayLegacyFines))]
|
||||||
|
[JsonDerivedType(typeof(RedeemVoucher))]
|
||||||
|
[JsonDerivedType(typeof(RefuelAll))]
|
||||||
|
[JsonDerivedType(typeof(RefuelPartial))]
|
||||||
|
[JsonDerivedType(typeof(Repair))]
|
||||||
|
[JsonDerivedType(typeof(RepairAll))]
|
||||||
|
[JsonDerivedType(typeof(RestockVehicle))]
|
||||||
|
[JsonDerivedType(typeof(ScientificResearch))]
|
||||||
|
[JsonDerivedType(typeof(SearchAndRescue))]
|
||||||
|
[JsonDerivedType(typeof(SellDrones))]
|
||||||
|
[JsonDerivedType(typeof(SellShipOnRebuy))]
|
||||||
|
[JsonDerivedType(typeof(SetUserShipName))]
|
||||||
|
[JsonDerivedType(typeof(Shipyard))]
|
||||||
|
[JsonDerivedType(typeof(ShipyardBuy))]
|
||||||
|
[JsonDerivedType(typeof(ShipyardNew))]
|
||||||
|
[JsonDerivedType(typeof(ShipyardSell))]
|
||||||
|
[JsonDerivedType(typeof(ShipyardSwap))]
|
||||||
|
[JsonDerivedType(typeof(ShipyardTransfer))]
|
||||||
|
[JsonDerivedType(typeof(StoredModules))]
|
||||||
|
[JsonDerivedType(typeof(StoredShips))]
|
||||||
|
[JsonDerivedType(typeof(TechnologyBroker))]
|
||||||
|
[JsonDerivedType(typeof(AsteroidCracked))]
|
||||||
|
[JsonDerivedType(typeof(BuyTradeData))]
|
||||||
|
[JsonDerivedType(typeof(CollectCargo))]
|
||||||
|
[JsonDerivedType(typeof(EjectCargo))]
|
||||||
|
[JsonDerivedType(typeof(MarketBuy))]
|
||||||
|
[JsonDerivedType(typeof(MarketSell))]
|
||||||
|
[JsonDerivedType(typeof(MiningRefined))]
|
||||||
|
[JsonDerivedType(typeof(ApproachBody))]
|
||||||
|
[JsonDerivedType(typeof(Docked))]
|
||||||
|
[JsonDerivedType(typeof(DockingCancelled))]
|
||||||
|
[JsonDerivedType(typeof(DockingDenied))]
|
||||||
|
[JsonDerivedType(typeof(DockingGranted))]
|
||||||
|
[JsonDerivedType(typeof(DockingRequested))]
|
||||||
|
[JsonDerivedType(typeof(DockingTimeout))]
|
||||||
|
[JsonDerivedType(typeof(FSDJump))]
|
||||||
|
[JsonDerivedType(typeof(FSDTarget))]
|
||||||
|
[JsonDerivedType(typeof(LeaveBody))]
|
||||||
|
[JsonDerivedType(typeof(Liftoff))]
|
||||||
|
[JsonDerivedType(typeof(Location))]
|
||||||
|
[JsonDerivedType(typeof(NavRoute))]
|
||||||
|
[JsonDerivedType(typeof(NavRouteClear))]
|
||||||
|
[JsonDerivedType(typeof(StartJump))]
|
||||||
|
[JsonDerivedType(typeof(SupercruiseDestinationDrop))]
|
||||||
|
[JsonDerivedType(typeof(SupercruiseEntry))]
|
||||||
|
[JsonDerivedType(typeof(SupercruiseExit))]
|
||||||
|
[JsonDerivedType(typeof(Touchdown))]
|
||||||
|
[JsonDerivedType(typeof(Undocked))]
|
||||||
|
[JsonDerivedType(typeof(MarketFile))]
|
||||||
|
[JsonDerivedType(typeof(ModuleInfoFile))]
|
||||||
|
[JsonDerivedType(typeof(NavRouteFile))]
|
||||||
|
[JsonDerivedType(typeof(OutfittingFile))]
|
||||||
|
[JsonDerivedType(typeof(ShipyardFile))]
|
||||||
|
[JsonDerivedType(typeof(Status))]
|
||||||
public class JournalBase
|
public class JournalBase
|
||||||
{
|
{
|
||||||
[JsonPropertyName("timestamp")]
|
[JsonPropertyName("timestamp")]
|
||||||
public string Timestamp { get; init; }
|
public DateTimeOffset Timestamp { get; init; }
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public DateTimeOffset TimestampDateTime
|
|
||||||
{
|
|
||||||
get => ParseDateTime(Timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonPropertyName("event")]
|
[JsonPropertyName("event")]
|
||||||
public string Event { get; init; }
|
public string Event { get; init; }
|
||||||
|
|
||||||
[JsonExtensionData]
|
[JsonExtensionData]
|
||||||
public Dictionary<string, object> AdditionalProperties { get; init; }
|
public Dictionary<string, object> AdditionalProperties { get; init; }
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public string Json
|
|
||||||
{
|
|
||||||
get => json;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (json == null || string.IsNullOrWhiteSpace(json))
|
|
||||||
{
|
|
||||||
json = value;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception("Journal property \"Json\" can only be set while empty.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string json;
|
|
||||||
|
|
||||||
// For use by Journal object classes for .*DateTime properties, like TimestampeDateTime, above.
|
|
||||||
internal static DateTimeOffset ParseDateTime(string value)
|
|
||||||
{
|
|
||||||
if (DateTime.TryParseExact(value, "yyyy-MM-ddTHH:mm:ssZ", null, DateTimeStyles.AssumeUniversal, out var dateTimeValue))
|
|
||||||
{
|
|
||||||
return dateTimeValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new DateTime();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,34 +1,12 @@
|
||||||
using System.Text;
|
using System.Text.Json.Nodes;
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace Observatory.Framework.Files.Journal;
|
namespace Observatory.Framework.Files.Journal;
|
||||||
|
|
||||||
public static class JournalUtilities
|
public static class JournalUtilities
|
||||||
{
|
{
|
||||||
public static string GetEventType(string line)
|
public static string? GetEventType(JsonObject? line)
|
||||||
{
|
{
|
||||||
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(line));
|
return line.ContainsKey("event") ? line["event"]?.ToString() : null;
|
||||||
var result = string.Empty;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
while (reader.Read())
|
|
||||||
{
|
|
||||||
if (reader.TokenType == JsonTokenType.PropertyName && reader.GetString() == "event")
|
|
||||||
{
|
|
||||||
reader.Read();
|
|
||||||
result = reader.GetString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
|
|
||||||
result = "InvalidJson";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string CleanScanEvent(string line)
|
public static string CleanScanEvent(string line)
|
||||||
|
|
@ -38,6 +16,6 @@ public static class JournalUtilities
|
||||||
|
|
||||||
public const string ObsoleteMessage = "Unused in Elite Dangerous 3.7+, may appear in legacy journal data.";
|
public const string ObsoleteMessage = "Unused in Elite Dangerous 3.7+, may appear in legacy journal data.";
|
||||||
|
|
||||||
public const string UnusedMessage = "Documented by Frontier, but no occurances of this value ever found in real journal data.";
|
public const string UnusedMessage =
|
||||||
|
"Documented by Frontier, but no occurances of this value ever found in real journal data.";
|
||||||
}
|
}
|
||||||
|
|
@ -22,23 +22,14 @@ public class Docked : JournalBase
|
||||||
[Obsolete(JournalUtilities.ObsoleteMessage), JsonConverter(typeof(LegacyFactionConverter<Faction>))]
|
[Obsolete(JournalUtilities.ObsoleteMessage), JsonConverter(typeof(LegacyFactionConverter<Faction>))]
|
||||||
public Faction Faction
|
public Faction Faction
|
||||||
{
|
{
|
||||||
private get
|
private get => StationFaction;
|
||||||
{
|
init => StationFaction = value;
|
||||||
return StationFaction;
|
|
||||||
}
|
|
||||||
init
|
|
||||||
{
|
|
||||||
StationFaction = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete(JournalUtilities.ObsoleteMessage)]
|
[Obsolete(JournalUtilities.ObsoleteMessage)]
|
||||||
public string FactionState
|
public string FactionState
|
||||||
{
|
{
|
||||||
private get
|
private get => StationFaction.FactionState;
|
||||||
{
|
|
||||||
return StationFaction.FactionState;
|
|
||||||
}
|
|
||||||
|
|
||||||
init
|
init
|
||||||
{
|
{
|
||||||
|
|
@ -51,24 +42,24 @@ public class Docked : JournalBase
|
||||||
[Obsolete(JournalUtilities.ObsoleteMessage)]
|
[Obsolete(JournalUtilities.ObsoleteMessage)]
|
||||||
public string Government
|
public string Government
|
||||||
{
|
{
|
||||||
private get { return StationGovernment; }
|
private get => StationGovernment;
|
||||||
init { StationGovernment = value; }
|
init => StationGovernment = value;
|
||||||
}
|
}
|
||||||
public string StationGovernment_Localised { get; init; }
|
public string StationGovernment_Localised { get; init; }
|
||||||
|
|
||||||
[Obsolete(JournalUtilities.ObsoleteMessage)]
|
[Obsolete(JournalUtilities.ObsoleteMessage)]
|
||||||
public string Government_Localised
|
public string Government_Localised
|
||||||
{
|
{
|
||||||
private get { return StationGovernment_Localised; }
|
private get => StationGovernment_Localised;
|
||||||
init { StationGovernment_Localised = value; }
|
init => StationGovernment_Localised = value;
|
||||||
}
|
}
|
||||||
public string StationAllegiance { get; init; }
|
public string StationAllegiance { get; init; }
|
||||||
|
|
||||||
[Obsolete(JournalUtilities.ObsoleteMessage)]
|
[Obsolete(JournalUtilities.ObsoleteMessage)]
|
||||||
public string Allegiance
|
public string Allegiance
|
||||||
{
|
{
|
||||||
private get { return StationAllegiance; }
|
private get => StationAllegiance;
|
||||||
init { StationAllegiance = value; }
|
init => StationAllegiance = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonConverter(typeof(StationServiceConverter))]
|
[JsonConverter(typeof(StationServiceConverter))]
|
||||||
|
|
@ -78,16 +69,16 @@ public class Docked : JournalBase
|
||||||
[Obsolete(JournalUtilities.ObsoleteMessage)]
|
[Obsolete(JournalUtilities.ObsoleteMessage)]
|
||||||
public string Economy
|
public string Economy
|
||||||
{
|
{
|
||||||
private get { return StationEconomy; }
|
private get => StationEconomy;
|
||||||
init { StationEconomy = value; }
|
init => StationEconomy = value;
|
||||||
}
|
}
|
||||||
public string StationEconomy_Localised { get; init; }
|
public string StationEconomy_Localised { get; init; }
|
||||||
|
|
||||||
[Obsolete(JournalUtilities.ObsoleteMessage)]
|
[Obsolete(JournalUtilities.ObsoleteMessage)]
|
||||||
public string Economy_Localised
|
public string Economy_Localised
|
||||||
{
|
{
|
||||||
private get { return StationEconomy_Localised; }
|
private get => StationEconomy_Localised;
|
||||||
init { StationEconomy_Localised = value; }
|
init => StationEconomy_Localised = value;
|
||||||
}
|
}
|
||||||
public ImmutableList<StationEconomy> StationEconomies { get; init; }
|
public ImmutableList<StationEconomy> StationEconomies { get; init; }
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue