mirror of
https://github.com/9ParsonsB/Pulsar.git
synced 2025-07-01 16:33:43 -04:00
Initial Commit
This commit is contained in:
13
Explorer/CriteriaLoadException.cs
Normal file
13
Explorer/CriteriaLoadException.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Explorer;
|
||||
|
||||
internal class CriteriaLoadException : Exception
|
||||
{
|
||||
public CriteriaLoadException(string message, string script)
|
||||
{
|
||||
Message = message;
|
||||
OriginalScript = script;
|
||||
}
|
||||
|
||||
new public readonly string Message;
|
||||
public readonly string OriginalScript;
|
||||
}
|
382
Explorer/CustomCriteriaManager.cs
Normal file
382
Explorer/CustomCriteriaManager.cs
Normal file
@ -0,0 +1,382 @@
|
||||
using System.Text;
|
||||
using NLua;
|
||||
using NLua.Exceptions;
|
||||
using Observatory.Framework.Files.Journal.Exploration;
|
||||
|
||||
namespace Explorer;
|
||||
|
||||
internal class CustomCriteriaManager
|
||||
{
|
||||
private Lua LuaState;
|
||||
private Dictionary<string,LuaFunction> CriteriaFunctions;
|
||||
private Dictionary<string, string> CriteriaWithErrors = new();
|
||||
Action<Exception, string> ErrorLogger;
|
||||
private uint ScanCount;
|
||||
|
||||
public CustomCriteriaManager(Action<Exception, string> errorLogger)
|
||||
{
|
||||
ErrorLogger = errorLogger;
|
||||
CriteriaFunctions = new();
|
||||
ScanCount = 0;
|
||||
}
|
||||
|
||||
public void RefreshCriteria(string criteriaPath)
|
||||
{
|
||||
LuaState = new();
|
||||
LuaState.State.Encoding = Encoding.UTF8;
|
||||
LuaState.LoadCLRPackage();
|
||||
|
||||
#region Iterators
|
||||
|
||||
// Empty function for nil iterators
|
||||
LuaState.DoString("function nil_iterator() end");
|
||||
|
||||
//Materials and AtmosphereComposition
|
||||
LuaState.DoString(@"
|
||||
function materials (material_list)
|
||||
if material_list then
|
||||
local i = 0
|
||||
local count = material_list.Count
|
||||
return function ()
|
||||
i = i + 1
|
||||
if i <= count then
|
||||
return { name = material_list[i - 1].Name, percent = material_list[i - 1].Percent }
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil_iterator
|
||||
end
|
||||
end");
|
||||
|
||||
//Rings - internal filterable iterator
|
||||
LuaState.DoString(@"
|
||||
function _ringsFiltered (ring_list, filter_by)
|
||||
if ring_list then
|
||||
local i = 0
|
||||
local count = ring_list.Count
|
||||
return function ()
|
||||
i = i + 1
|
||||
while i <= count do
|
||||
local ring = ring_list[i - 1]
|
||||
if (filter_by == nil or string.find(ring.Name, filter_by)) then
|
||||
return { name = ring.Name, ringclass = ring.RingClass, massmt = ring.MassMT, innerrad = ring.InnerRad, outerrad = ring.OuterRad }
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil_iterator
|
||||
end
|
||||
end");
|
||||
|
||||
//Rings - internal filterable hasX check
|
||||
LuaState.DoString(@"
|
||||
function _hasRingsFiltered (ring_list, filter_by)
|
||||
if ring_list then
|
||||
local i = 0
|
||||
local count = ring_list.Count
|
||||
while i < count do
|
||||
if string.find(ring_list[i].Name, filter_by) then
|
||||
return true
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
return false
|
||||
end");
|
||||
|
||||
//Rings - iterate all - nil filter
|
||||
LuaState.DoString(@"
|
||||
function rings (ring_list)
|
||||
return _ringsFiltered(ring_list, nil)
|
||||
end");
|
||||
|
||||
//Rings - iterate proper rings only
|
||||
LuaState.DoString(@"
|
||||
function ringsOnly (ring_list)
|
||||
return _ringsFiltered(ring_list, 'Ring')
|
||||
end");
|
||||
|
||||
//Rings - iterate belts only
|
||||
LuaState.DoString(@"
|
||||
function beltsOnly (ring_list)
|
||||
return _ringsFiltered(ring_list, 'Belt')
|
||||
end");
|
||||
|
||||
//Bodies in system
|
||||
LuaState.DoString(@"
|
||||
function bodies (system_list)
|
||||
if system_list then
|
||||
local i = 0
|
||||
local count = system_list.Count
|
||||
return function ()
|
||||
i = i + 1
|
||||
if i <= count then
|
||||
return system_list[i - 1]
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil_iterator
|
||||
end
|
||||
end");
|
||||
|
||||
//Parent bodies
|
||||
LuaState.DoString(@"
|
||||
function allparents (parent_list)
|
||||
if parent_list then
|
||||
local i = 0
|
||||
local count
|
||||
if parent_list then count = parent_list.Count else count = 0 end
|
||||
return function ()
|
||||
i = i + 1
|
||||
if i <= count then
|
||||
return { parenttype = parent_list[i - 1].ParentType, body = parent_list[i - 1].Body, scan = parent_list[i - 1].Scan }
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil_iterator
|
||||
end
|
||||
end");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Convenience Functions
|
||||
|
||||
//Rings - has > 0 belts
|
||||
LuaState.DoString(@"
|
||||
function hasBelts (ring_list)
|
||||
return _hasRingsFiltered(ring_list, 'Belt')
|
||||
end");
|
||||
|
||||
//Rings - has > 0 proper rings
|
||||
LuaState.DoString(@"
|
||||
function hasRings (ring_list)
|
||||
return _hasRingsFiltered(ring_list, 'Ring')
|
||||
end");
|
||||
|
||||
LuaState.DoString(@"
|
||||
function isStar (scan)
|
||||
return scan.StarType and scan.StarType ~= ''
|
||||
end");
|
||||
|
||||
LuaState.DoString(@"
|
||||
function isPlanet (scan)
|
||||
return scan.PlanetClass ~= nil
|
||||
end");
|
||||
|
||||
LuaState.DoString(@"
|
||||
function hasAtmosphere (scan)
|
||||
return scan.AtmosphereComposition ~= nil
|
||||
end");
|
||||
|
||||
LuaState.DoString(@"
|
||||
function hasLandableAtmosphere (scan)
|
||||
return scan.Landable and scan.AtmosphereComposition ~= nil
|
||||
end");
|
||||
|
||||
#endregion
|
||||
|
||||
CriteriaFunctions.Clear();
|
||||
CriteriaWithErrors.Clear();
|
||||
var criteria = File.Exists(criteriaPath) ? File.ReadAllLines(criteriaPath) : Array.Empty<string>();
|
||||
StringBuilder script = new();
|
||||
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < criteria.Length; i++)
|
||||
{
|
||||
if (criteria[i].Trim().StartsWith("::"))
|
||||
{
|
||||
var scriptDescription = criteria[i].Trim().Replace("::", string.Empty);
|
||||
if (scriptDescription.ToLower() == "criteria" || scriptDescription.ToLower().StartsWith("criteria="))
|
||||
{
|
||||
var functionName = $"Criteria{i}";
|
||||
script.AppendLine($"function {functionName} (scan, parents, system, biosignals, geosignals)");
|
||||
i++;
|
||||
do
|
||||
{
|
||||
if (i >= criteria.Length)
|
||||
throw new Exception("Unterminated multi-line criteria.\r\nAre you missing an ::End::?");
|
||||
|
||||
script.AppendLine(criteria[i]);
|
||||
i++;
|
||||
} while (!criteria[i].Trim().ToLower().StartsWith("::end::"));
|
||||
script.AppendLine("end");
|
||||
|
||||
LuaState.DoString(script.ToString());
|
||||
CriteriaFunctions.Add(GetUniqueDescription(functionName, scriptDescription), LuaState[functionName] as LuaFunction);
|
||||
script.Clear();
|
||||
}
|
||||
else if (scriptDescription.ToLower() == "global")
|
||||
{
|
||||
i++;
|
||||
do
|
||||
{
|
||||
script.AppendLine(criteria[i]);
|
||||
i++;
|
||||
} while (!criteria[i].Trim().ToLower().StartsWith("::end::"));
|
||||
LuaState.DoString(script.ToString());
|
||||
script.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
|
||||
var functionName = $"Criteria{i}";
|
||||
|
||||
script.AppendLine($"function {functionName} (scan, parents, system, biosignals, geosignals)");
|
||||
script.AppendLine($" local result = {criteria[i]}");
|
||||
script.AppendLine(" local detail = ''");
|
||||
|
||||
if (criteria.Length > i + 1 && criteria[i + 1].Trim().ToLower() == "::detail::")
|
||||
{
|
||||
i++; i++;
|
||||
// Gate detail evaluation on result to allow safe use of criteria-checked values in detail string.
|
||||
script.AppendLine(" if result then");
|
||||
script.AppendLine($" detail = {criteria[i]}");
|
||||
script.AppendLine(" end");
|
||||
}
|
||||
|
||||
script.AppendLine($" return result, '{scriptDescription}', detail");
|
||||
script.AppendLine("end");
|
||||
|
||||
LuaState.DoString(script.ToString());
|
||||
CriteriaFunctions.Add(GetUniqueDescription(functionName, scriptDescription), LuaState[functionName] as LuaFunction);
|
||||
script.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var originalScript = script.ToString().Trim();
|
||||
|
||||
originalScript = originalScript.Remove(originalScript.LastIndexOf(Environment.NewLine));
|
||||
originalScript = originalScript[(originalScript.IndexOf(Environment.NewLine) + Environment.NewLine.Length)..];
|
||||
originalScript = originalScript.Replace('\t', ' ');
|
||||
|
||||
StringBuilder errorDetail = new();
|
||||
errorDetail.AppendLine("Error Reading Custom Criteria File:")
|
||||
.AppendLine(originalScript)
|
||||
.AppendLine("To correct this problem, make changes to the Lua source file, save it and either re-run read-all or scan another body. It will be automatically reloaded."); ErrorLogger(e, errorDetail.ToString());
|
||||
CriteriaFunctions.Clear(); // Don't use partial parse.
|
||||
throw new CriteriaLoadException(e.Message, originalScript);
|
||||
}
|
||||
}
|
||||
|
||||
public List<(string, string, bool)> CheckInterest(Scan scan, Dictionary<ulong, Dictionary<int, Scan>> scanHistory, Dictionary<ulong, Dictionary<int, FSSBodySignals>> signalHistory, ExplorerSettings settings)
|
||||
{
|
||||
List<(string, string, bool)> results = new();
|
||||
ScanCount++;
|
||||
|
||||
foreach (var criteriaFunction in CriteriaFunctions)
|
||||
{
|
||||
// Skip criteria which have previously thrown an error. We can't remove them from the dictionary while iterating it.
|
||||
if (CriteriaWithErrors.ContainsKey(criteriaFunction.Key)) continue;
|
||||
|
||||
var scanList = scanHistory[scan.SystemAddress].Values.ToList();
|
||||
|
||||
int bioSignals;
|
||||
int geoSignals;
|
||||
|
||||
if (signalHistory.ContainsKey(scan.SystemAddress) && signalHistory[scan.SystemAddress].ContainsKey(scan.BodyID))
|
||||
{
|
||||
bioSignals = signalHistory[scan.SystemAddress][scan.BodyID].Signals.Where(s => s.Type == "$SAA_SignalType_Biological;").Select(s => s.Count).FirstOrDefault();
|
||||
geoSignals = signalHistory[scan.SystemAddress][scan.BodyID].Signals.Where(s => s.Type == "$SAA_SignalType_Geological;").Select(s => s.Count).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
bioSignals = 0;
|
||||
geoSignals = 0;
|
||||
}
|
||||
|
||||
|
||||
List<Parent> parents;
|
||||
|
||||
if (scan.Parent != null)
|
||||
{
|
||||
parents = new();
|
||||
foreach (var parent in scan.Parent)
|
||||
{
|
||||
var parentScan = scanList.Where(s => s.BodyID == parent.Body);
|
||||
|
||||
parents.Add(new Parent
|
||||
{
|
||||
ParentType = parent.ParentType.ToString(),
|
||||
Body = parent.Body,
|
||||
Scan = parentScan.Any() ? parentScan.First() : null
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
parents = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = criteriaFunction.Value.Call(scan, parents, scanList, bioSignals, geoSignals);
|
||||
if (result.Length == 3 && ((bool?)result[0]).GetValueOrDefault(false))
|
||||
{
|
||||
results.Add((result[1].ToString(), result[2].ToString(), false));
|
||||
}
|
||||
else if (result.Length == 2)
|
||||
{
|
||||
results.Add((result[0].ToString(), result[1].ToString(), false));
|
||||
}
|
||||
}
|
||||
catch (LuaScriptException e)
|
||||
{
|
||||
results.Add((e.Message, scan.Json, false));
|
||||
|
||||
StringBuilder errorDetail = new();
|
||||
errorDetail.AppendLine($"while processing custom criteria '{criteriaFunction.Key}' on scan:")
|
||||
.AppendLine(scan.Json)
|
||||
.AppendLine("To correct this problem, make changes to the Lua source file, save it and either re-run read-all or scan another body. It will be automatically reloaded.");
|
||||
ErrorLogger(e, errorDetail.ToString());
|
||||
CriteriaWithErrors.Add(criteriaFunction.Key, e.Message + Environment.NewLine + errorDetail);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any erroring criteria. They will be repopulated next time the file is parsed.
|
||||
if (CriteriaWithErrors.Count > 0)
|
||||
{
|
||||
foreach (var criteriaKey in CriteriaWithErrors.Keys)
|
||||
{
|
||||
if (CriteriaFunctions.ContainsKey(criteriaKey)) CriteriaFunctions.Remove(criteriaKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (ScanCount > 99)
|
||||
{
|
||||
ScanCount = 0;
|
||||
LuaGC();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private string GetUniqueDescription(string functionName, string scriptDescription)
|
||||
{
|
||||
var uniqueDescription = functionName;
|
||||
if (scriptDescription.ToLower().StartsWith("criteria="))
|
||||
{
|
||||
uniqueDescription += scriptDescription.Replace("criteria=", "=", StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
return uniqueDescription;
|
||||
}
|
||||
|
||||
private void LuaGC()
|
||||
{
|
||||
LuaState?.DoString("collectgarbage()");
|
||||
}
|
||||
|
||||
internal class Parent
|
||||
{
|
||||
public string ParentType;
|
||||
public int Body;
|
||||
public Scan Scan;
|
||||
}
|
||||
|
||||
}
|
389
Explorer/DefaultCriteria.cs
Normal file
389
Explorer/DefaultCriteria.cs
Normal file
@ -0,0 +1,389 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Observatory.Framework.Files.Journal.Exploration;
|
||||
using Observatory.Framework.Files.ParameterTypes;
|
||||
|
||||
namespace Explorer;
|
||||
|
||||
internal static class DefaultCriteria
|
||||
{
|
||||
public static List<(string Description, string Detail, bool SystemWide)> CheckInterest(Scan scan, Dictionary<ulong, Dictionary<int, Scan>> scanHistory, Dictionary<ulong, Dictionary<int, FSSBodySignals>> signalHistory, ExplorerSettings settings)
|
||||
{
|
||||
List<(string, string, bool)> results = new();
|
||||
var textInfo = new CultureInfo("en-US", false).TextInfo;
|
||||
|
||||
var isRing = scan.BodyName.Contains("Ring");
|
||||
|
||||
#if DEBUG
|
||||
// results.Add("Test Scan Event", "Test Detail");
|
||||
#endif
|
||||
|
||||
#region Landable Checks
|
||||
if (scan.Landable)
|
||||
{
|
||||
if (settings.LandableTerraformable && scan.TerraformState?.Length > 0)
|
||||
{
|
||||
results.Add($"Landable and {scan.TerraformState}");
|
||||
}
|
||||
|
||||
if (settings.LandableRing && scan.Rings?.Count > 0)
|
||||
{
|
||||
results.Add("Ringed Landable Body");
|
||||
}
|
||||
|
||||
if (settings.LandableAtmosphere && scan.Atmosphere.Length > 0)
|
||||
{
|
||||
results.Add("Landable with Atmosphere", textInfo.ToTitleCase(scan.Atmosphere));
|
||||
}
|
||||
|
||||
if (settings.LandableHighG && scan.SurfaceGravity > 29.4)
|
||||
{
|
||||
results.Add("Landable with High Gravity", $"Surface gravity: {scan.SurfaceGravity / 9.81:0.00}g");
|
||||
}
|
||||
|
||||
if (settings.LandableLarge && scan.Radius > 18000000)
|
||||
{
|
||||
results.Add("Landable Large Planet", $"Radius: {scan.Radius / 1000:0}km");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Value Checks
|
||||
if (settings.HighValueMappable)
|
||||
{
|
||||
IList<string> HighValueNonTerraformablePlanetClasses = new[] {
|
||||
"Earthlike body",
|
||||
"Ammonia world",
|
||||
"Water world",
|
||||
};
|
||||
|
||||
if (HighValueNonTerraformablePlanetClasses.Contains(scan.PlanetClass) || scan.TerraformState?.Length > 0)
|
||||
{
|
||||
var info = new StringBuilder();
|
||||
|
||||
if (!scan.WasMapped)
|
||||
{
|
||||
if (!scan.WasDiscovered)
|
||||
info.Append("Undiscovered ");
|
||||
else
|
||||
info.Append("Unmapped ");
|
||||
}
|
||||
|
||||
if (scan.TerraformState?.Length > 0)
|
||||
info.Append("Terraformable ");
|
||||
|
||||
results.Add("High-Value Body", $"{info}{textInfo.ToTitleCase(scan.PlanetClass)}, {scan.DistanceFromArrivalLS:N0}Ls");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Parent Relative Checks
|
||||
|
||||
if (scan.SystemAddress != 0 && scan.SemiMajorAxis != 0 &&
|
||||
scanHistory[scan.SystemAddress].ContainsKey(scan.Parent[0].Body))
|
||||
{
|
||||
var parent = scanHistory[scan.SystemAddress][scan.Parent[0].Body];
|
||||
|
||||
if (settings.CloseOrbit && !isRing && parent.Radius * 3 > scan.SemiMajorAxis)
|
||||
{
|
||||
results.Add("Close Orbit", $"Orbital Radius: {scan.SemiMajorAxis / 1000:N0}km, Parent Radius: {parent.Radius / 1000:N0}km");
|
||||
}
|
||||
|
||||
if (settings.ShepherdMoon && !isRing && parent.Rings?.Any() == true && parent.Rings.Last().OuterRad > scan.SemiMajorAxis && !parent.Rings.Last().Name.Contains("Belt"))
|
||||
{
|
||||
results.Add("Shepherd Moon", $"Orbit: {scan.SemiMajorAxis / 1000:N0}km, Ring Radius: {parent.Rings.Last().OuterRad / 1000:N0}km");
|
||||
}
|
||||
|
||||
if (settings.CloseRing && parent.Rings?.Count > 0)
|
||||
{
|
||||
foreach (var ring in parent.Rings)
|
||||
{
|
||||
var separation = Math.Min(Math.Abs(scan.SemiMajorAxis - ring.OuterRad), Math.Abs(ring.InnerRad - scan.SemiMajorAxis));
|
||||
if (separation < scan.Radius * 10)
|
||||
{
|
||||
var ringTypeName = ring.Name.Contains("Belt") ? "Belt" : "Ring";
|
||||
var isLandable = scan.Landable ? ", Landable" : "";
|
||||
results.Add($"Close {ringTypeName} Proximity",
|
||||
$"Orbit: {scan.SemiMajorAxis / 1000:N0}km, Radius: {scan.Radius / 1000:N0}km, Distance from {ringTypeName.ToLower()}: {separation / 1000:N0}km{isLandable}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
if (settings.DiverseLife && signalHistory.ContainsKey(scan.SystemAddress) && signalHistory[scan.SystemAddress].ContainsKey(scan.BodyID))
|
||||
{
|
||||
var bioSignals = signalHistory[scan.SystemAddress][scan.BodyID].Signals.Where(s => s.Type == "$SAA_SignalType_Biological;");
|
||||
|
||||
if (bioSignals.Count() > 0 && bioSignals.First().Count > 7)
|
||||
{
|
||||
results.Add("Diverse Life", $"Biological Signals: {bioSignals.First().Count}");
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.WideRing && scan.Rings?.Count > 0)
|
||||
{
|
||||
foreach (var ring in scan.Rings.Where(r => !r.Name.Contains("Belt")))
|
||||
{
|
||||
var ringWidth = ring.OuterRad - ring.InnerRad;
|
||||
if (ringWidth > scan.Radius * 5)
|
||||
{
|
||||
var ringName = ring.Name.Replace(scan.BodyName, "").Trim();
|
||||
results.Add("Wide Ring", $"{ringName}: Width: {ringWidth / 299792458:N2}Ls / {ringWidth / 1000:N0}km, Parent Radius: {scan.Radius / 1000:N0}km");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.SmallObject && scan.StarType == null && scan.PlanetClass != null && scan.PlanetClass != "Barycentre" && scan.Radius < 300000)
|
||||
{
|
||||
results.Add("Small Object", $"Radius: {scan.Radius / 1000:N0}km");
|
||||
}
|
||||
|
||||
if (settings.HighEccentricity && scan.Eccentricity > 0.9)
|
||||
{
|
||||
results.Add("Highly Eccentric Orbit", $"Eccentricity: {Math.Round(scan.Eccentricity, 4)}");
|
||||
}
|
||||
|
||||
if (settings.Nested && !isRing && scan.Parent?.Count > 1 && scan.Parent[0].ParentType == ParentType.Planet && scan.Parent[1].ParentType == ParentType.Planet)
|
||||
{
|
||||
results.Add("Nested Moon");
|
||||
}
|
||||
|
||||
if (settings.FastRotation && scan.RotationPeriod != 0 && !scan.TidalLock && Math.Abs(scan.RotationPeriod) < 28800 && !isRing && string.IsNullOrEmpty(scan.StarType))
|
||||
{
|
||||
results.Add("Non-locked Body with Fast Rotation", $"Period: {scan.RotationPeriod/3600:N1} hours");
|
||||
}
|
||||
|
||||
if (settings.FastOrbit && scan.OrbitalPeriod != 0 && Math.Abs(scan.OrbitalPeriod) < 28800 && !isRing)
|
||||
{
|
||||
results.Add("Fast Orbit", $"Orbital Period: {Math.Abs(scan.OrbitalPeriod / 3600):N1} hours");
|
||||
}
|
||||
|
||||
// Close binary pair
|
||||
if ((settings.CloseBinary || settings.CollidingBinary) && scan.Parent?[0].ParentType == ParentType.Null && scan.Radius / scan.SemiMajorAxis > 0.4)
|
||||
{
|
||||
var binaryPartner = scanHistory[scan.SystemAddress].Where(priorScan => priorScan.Value.Parent?[0].Body == scan.Parent?[0].Body && scan.BodyID != priorScan.Key);
|
||||
|
||||
if (binaryPartner.Count() == 1)
|
||||
{
|
||||
if (binaryPartner.First().Value.Radius / binaryPartner.First().Value.SemiMajorAxis > 0.4)
|
||||
{
|
||||
if (settings.CollidingBinary && binaryPartner.First().Value.Radius + scan.Radius >= binaryPartner.First().Value.SemiMajorAxis * (1 - binaryPartner.First().Value.Eccentricity) + scan.SemiMajorAxis * (1 - scan.Eccentricity))
|
||||
{
|
||||
results.Add("COLLIDING binary", $"Orbit: {Math.Truncate((double)scan.SemiMajorAxis / 1000):N0}km, Radius: {Math.Truncate((double)scan.Radius / 1000):N0}km, Partner: {binaryPartner.First().Value.BodyName}");
|
||||
}
|
||||
else if (settings.CloseBinary)
|
||||
{
|
||||
results.Add("Close binary relative to body size", $"Orbit: {Math.Truncate((double)scan.SemiMajorAxis / 1000):N0}km, Radius: {Math.Truncate((double)scan.Radius / 1000):N0}km, Partner: {binaryPartner.First().Value.BodyName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.GoodFSDBody && scan.Landable)
|
||||
{
|
||||
List<string> boostMaterials = new()
|
||||
{
|
||||
"Carbon",
|
||||
"Germanium",
|
||||
"Arsenic",
|
||||
"Niobium",
|
||||
"Yttrium",
|
||||
"Polonium"
|
||||
};
|
||||
|
||||
if (boostMaterials.RemoveMatchedMaterials(scan) == 1)
|
||||
{
|
||||
results.Add("5 of 6 Premium Boost Materials", $"Missing material: {boostMaterials[0]}");
|
||||
}
|
||||
}
|
||||
|
||||
if ((settings.GreenSystem || settings.GoldSystem) && scan.Materials != null)
|
||||
{
|
||||
List<string> boostMaterials = new()
|
||||
{
|
||||
"Carbon",
|
||||
"Germanium",
|
||||
"Arsenic",
|
||||
"Niobium",
|
||||
"Yttrium",
|
||||
"Polonium"
|
||||
};
|
||||
|
||||
List<string> allSurfaceMaterials = new()
|
||||
{
|
||||
"Antimony", "Arsenic", "Cadmium", "Carbon",
|
||||
"Chromium", "Germanium", "Iron", "Manganese",
|
||||
"Mercury", "Molybdenum", "Nickel", "Niobium",
|
||||
"Phosphorus", "Polonium", "Ruthenium", "Selenium",
|
||||
"Sulphur", "Technetium", "Tellurium", "Tin",
|
||||
"Tungsten", "Vanadium", "Yttrium", "Zinc",
|
||||
"Zirconium"
|
||||
};
|
||||
|
||||
var systemBodies = scanHistory[scan.SystemAddress];
|
||||
|
||||
var notifyGreen = false;
|
||||
var notifyGold = false;
|
||||
|
||||
foreach (var body in systemBodies.Values)
|
||||
{
|
||||
|
||||
// If we enter either check and the count is already zero then a
|
||||
// previous body in system triggered the check, suppress notification.
|
||||
if (settings.GreenSystem && body.Materials != null)
|
||||
{
|
||||
if (boostMaterials.Count == 0)
|
||||
notifyGreen = false;
|
||||
else if (boostMaterials.RemoveMatchedMaterials(body) == 0)
|
||||
notifyGreen = true;
|
||||
}
|
||||
|
||||
if (settings.GoldSystem && body.Materials != null)
|
||||
{
|
||||
if (allSurfaceMaterials.Count == 0)
|
||||
notifyGold = false;
|
||||
else if (allSurfaceMaterials.RemoveMatchedMaterials(body) == 0)
|
||||
notifyGold = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (notifyGreen)
|
||||
results.Add("All Premium Boost Materials In System", string.Empty, true);
|
||||
|
||||
if (notifyGold)
|
||||
results.Add("All Surface Materials In System", string.Empty, true);
|
||||
}
|
||||
|
||||
if (settings.UncommonSecondary && scan.BodyID > 0 && !string.IsNullOrWhiteSpace(scan.StarType) && scan.DistanceFromArrivalLS > 10)
|
||||
{
|
||||
var excludeTypes = new List<string> { "O", "B", "A", "F", "G", "K", "M", "L", "T", "Y", "TTS" };
|
||||
if (!excludeTypes.Contains(scan.StarType.ToUpper()))
|
||||
{
|
||||
results.Add("Uncommon Secondary Star Type", $"{GetUncommonStarTypeName(scan.StarType)}, Distance: {scan.DistanceFromArrivalLS:N0}Ls");
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static string GetUncommonStarTypeName(string starType)
|
||||
{
|
||||
string name;
|
||||
|
||||
switch (starType.ToLower())
|
||||
{
|
||||
case "b_bluewhitesupergiant":
|
||||
name = "B Blue-White Supergiant";
|
||||
break;
|
||||
case "a_bluewhitesupergiant":
|
||||
name = "A Blue-White Supergiant";
|
||||
break;
|
||||
case "f_whitesupergiant":
|
||||
name = "F White Supergiant";
|
||||
break;
|
||||
case "g_whitesupergiant":
|
||||
name = "G White Supergiant";
|
||||
break;
|
||||
case "k_orangegiant":
|
||||
name = "K Orange Giant";
|
||||
break;
|
||||
case "m_redgiant":
|
||||
name = "M Red Giant";
|
||||
break;
|
||||
case "m_redsupergiant":
|
||||
name = "M Red Supergiant";
|
||||
break;
|
||||
case "aebe":
|
||||
name = "Herbig Ae/Be";
|
||||
break;
|
||||
case "w":
|
||||
case "wn":
|
||||
case "wnc":
|
||||
case "wc":
|
||||
case "wo":
|
||||
name = "Wolf-Rayet";
|
||||
break;
|
||||
case "c":
|
||||
case "cs":
|
||||
case "cn":
|
||||
case "cj":
|
||||
case "ch":
|
||||
case "chd":
|
||||
name = "Carbon Star";
|
||||
break;
|
||||
case "s":
|
||||
name = "S-Type Star";
|
||||
break;
|
||||
case "ms":
|
||||
name = "MS-Type Star";
|
||||
break;
|
||||
case "d":
|
||||
case "da":
|
||||
case "dab":
|
||||
case "dao":
|
||||
case "daz":
|
||||
case "dav":
|
||||
case "db":
|
||||
case "dbz":
|
||||
case "dbv":
|
||||
case "do":
|
||||
case "dov":
|
||||
case "dq":
|
||||
case "dc":
|
||||
case "dcv":
|
||||
case "dx":
|
||||
name = "White Dwarf";
|
||||
break;
|
||||
case "n":
|
||||
name = "Neutron Star";
|
||||
break;
|
||||
case "h":
|
||||
name = "Black Hole";
|
||||
break;
|
||||
case "supermassiveblackhole":
|
||||
name = "Supermassive Black Hole";
|
||||
break;
|
||||
case "x":
|
||||
name = "Exotic Star";
|
||||
break;
|
||||
case "rogueplanet":
|
||||
name = "Rogue Planet";
|
||||
break;
|
||||
case "tts":
|
||||
case "t":
|
||||
name = "T Tauri Type";
|
||||
break;
|
||||
default:
|
||||
name = starType + "-Type Star";
|
||||
break;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes materials from the list if found on the specified body.
|
||||
/// </summary>
|
||||
/// <param name="materials"></param>
|
||||
/// <param name="body"></param>
|
||||
/// <returns>Count of materials remaining in list.</returns>
|
||||
private static int RemoveMatchedMaterials(this List<string> materials, Scan body)
|
||||
{
|
||||
foreach (var material in body.Materials)
|
||||
{
|
||||
var matchedMaterial = materials.Find(mat => mat.Equals(material.Name, StringComparison.OrdinalIgnoreCase));
|
||||
if (matchedMaterial != null)
|
||||
{
|
||||
materials.Remove(matchedMaterial);
|
||||
}
|
||||
}
|
||||
return materials.Count;
|
||||
}
|
||||
|
||||
private static void Add(this List<(string, string, bool)> results, string description, string detail = "", bool systemWide = false)
|
||||
{
|
||||
results.Add((description, detail, systemWide));
|
||||
}
|
||||
}
|
304
Explorer/Explorer.cs
Normal file
304
Explorer/Explorer.cs
Normal file
@ -0,0 +1,304 @@
|
||||
using System.Reflection;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Files.Journal.Exploration;
|
||||
|
||||
namespace Explorer;
|
||||
|
||||
internal class Explorer
|
||||
{
|
||||
private IObservatoryCore ObservatoryCore;
|
||||
private ExplorerWorker ExplorerWorker;
|
||||
private Dictionary<ulong, Dictionary<int, Scan>> SystemBodyHistory;
|
||||
private Dictionary<ulong, Dictionary<int, FSSBodySignals>> BodySignalHistory;
|
||||
private Dictionary<ulong, Dictionary<int, ScanBaryCentre>> BarycentreHistory;
|
||||
private CustomCriteriaManager CustomCriteriaManager;
|
||||
private DateTime CriteriaLastModified;
|
||||
private string currentSystem = string.Empty;
|
||||
|
||||
internal Explorer(ExplorerWorker explorerWorker, IObservatoryCore core)
|
||||
{
|
||||
SystemBodyHistory = new();
|
||||
BodySignalHistory = new();
|
||||
BarycentreHistory = new();
|
||||
ExplorerWorker = explorerWorker;
|
||||
ObservatoryCore = core;
|
||||
CustomCriteriaManager = new(core.GetPluginErrorLogger(explorerWorker));
|
||||
CriteriaLastModified = new DateTime(0);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
SystemBodyHistory.Clear();
|
||||
BodySignalHistory.Clear();
|
||||
BarycentreHistory.Clear();
|
||||
}
|
||||
|
||||
public void RecordSignal(FSSBodySignals bodySignals)
|
||||
{
|
||||
if (!BodySignalHistory.ContainsKey(bodySignals.SystemAddress))
|
||||
{
|
||||
BodySignalHistory.Add(bodySignals.SystemAddress, new Dictionary<int, FSSBodySignals>());
|
||||
}
|
||||
|
||||
if (!BodySignalHistory[bodySignals.SystemAddress].ContainsKey(bodySignals.BodyID))
|
||||
{
|
||||
BodySignalHistory[bodySignals.SystemAddress].Add(bodySignals.BodyID, bodySignals);
|
||||
}
|
||||
}
|
||||
|
||||
public void RecordBarycentre(ScanBaryCentre scan)
|
||||
{
|
||||
if (!BarycentreHistory.ContainsKey(scan.SystemAddress))
|
||||
{
|
||||
BarycentreHistory.Add(scan.SystemAddress, new Dictionary<int, ScanBaryCentre>());
|
||||
}
|
||||
|
||||
if (!BarycentreHistory[scan.SystemAddress].ContainsKey(scan.BodyID))
|
||||
{
|
||||
BarycentreHistory[scan.SystemAddress].Add(scan.BodyID, scan);
|
||||
}
|
||||
}
|
||||
|
||||
private static string IncrementOrdinal(string ordinal)
|
||||
{
|
||||
var ordChar = ordinal.ToCharArray().Last();
|
||||
|
||||
if (new[] {'Z', '9'}.Contains(ordChar))
|
||||
{
|
||||
ordinal = IncrementOrdinal(ordinal.Length == 1 ? " " : string.Empty + ordinal[..^1]);
|
||||
ordChar = (char)(ordChar - 10);
|
||||
}
|
||||
|
||||
return ordinal[..^1] + (char)(ordChar + 1);
|
||||
}
|
||||
|
||||
private static string DecrementOrdinal(string ordinal)
|
||||
{
|
||||
var ordChar = ordinal.ToCharArray().Last();
|
||||
|
||||
if (new[] { 'A', '0' }.Contains(ordChar))
|
||||
{
|
||||
ordinal = DecrementOrdinal(ordinal.Length == 1 ? " " : string.Empty + ordinal[..^1]);
|
||||
ordChar = (char)(ordChar + 10);
|
||||
}
|
||||
|
||||
return ordinal[..^1] + (char)(ordChar - 1);
|
||||
}
|
||||
|
||||
public Scan ConvertBarycentre(ScanBaryCentre barycentre, Scan childScan)
|
||||
{
|
||||
var childAffix = childScan.BodyName
|
||||
.Replace(childScan.StarSystem, string.Empty).Trim();
|
||||
|
||||
string baryName;
|
||||
|
||||
if (!string.IsNullOrEmpty(childAffix))
|
||||
{
|
||||
var childOrdinal = childAffix.ToCharArray().Last();
|
||||
|
||||
// If the ID is one higher than the barycentre than this is the "first" child body
|
||||
var lowChild = childScan.BodyID - barycentre.BodyID == 1;
|
||||
|
||||
string baryAffix;
|
||||
|
||||
// Barycentre ordinal always labelled as low before high, e.g. "AB"
|
||||
if (lowChild)
|
||||
{
|
||||
baryAffix = childAffix + "-" + IncrementOrdinal(childAffix);
|
||||
}
|
||||
else
|
||||
{
|
||||
baryAffix = DecrementOrdinal(childAffix) + "-" + childAffix;
|
||||
}
|
||||
|
||||
baryName = barycentre.StarSystem + " " + baryAffix;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Without ordinals it's complicated to determine what the ordinal *should* be.
|
||||
// Just name the barycentre after the child object.
|
||||
baryName = childScan.BodyName + " Barycentre";
|
||||
}
|
||||
|
||||
Scan barycentreScan = new()
|
||||
{
|
||||
Timestamp = barycentre.Timestamp,
|
||||
BodyName = baryName,
|
||||
Parents = childScan.Parents.RemoveAt(0),
|
||||
PlanetClass = "Barycentre",
|
||||
StarSystem = barycentre.StarSystem,
|
||||
SystemAddress = barycentre.SystemAddress,
|
||||
BodyID = barycentre.BodyID,
|
||||
SemiMajorAxis = barycentre.SemiMajorAxis,
|
||||
Eccentricity = barycentre.Eccentricity,
|
||||
OrbitalInclination = barycentre.OrbitalInclination,
|
||||
Periapsis = barycentre.Periapsis,
|
||||
OrbitalPeriod = barycentre.OrbitalPeriod,
|
||||
AscendingNode = barycentre.AscendingNode,
|
||||
MeanAnomaly = barycentre.MeanAnomaly,
|
||||
Json = barycentre.Json
|
||||
};
|
||||
|
||||
return barycentreScan;
|
||||
}
|
||||
public void SetSystem(string potentialNewSystem)
|
||||
{
|
||||
if (string.IsNullOrEmpty(currentSystem) || currentSystem != potentialNewSystem)
|
||||
{
|
||||
currentSystem = potentialNewSystem;
|
||||
if (ExplorerWorker.settings.OnlyShowCurrentSystem && !ObservatoryCore.IsLogMonitorBatchReading)
|
||||
{
|
||||
ObservatoryCore.SetGridItems(ExplorerWorker, [
|
||||
typeof(ExplorerUIResults).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Select(p => p.Name)
|
||||
.ToDictionary(p => p, p => string.Empty)
|
||||
]);
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessScan(Scan scanEvent, bool readAll)
|
||||
{
|
||||
if (!readAll)
|
||||
{
|
||||
|
||||
// if (File.Exists(criteriaFilePath))
|
||||
// {
|
||||
// var fileModified = new FileInfo(criteriaFilePath).LastWriteTime;
|
||||
//
|
||||
// if (fileModified != CriteriaLastModified)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// CustomCriteriaManager.RefreshCriteria(criteriaFilePath);
|
||||
// }
|
||||
// catch (CriteriaLoadException e)
|
||||
// {
|
||||
// var exceptionResult = new ExplorerUIResults
|
||||
// {
|
||||
// BodyName = "Error Reading Custom Criteria File",
|
||||
// Time = DateTime.Now.ToString("G"),
|
||||
// Description = e.Message,
|
||||
// Details = e.OriginalScript
|
||||
// };
|
||||
// ObservatoryCore.AddGridItem(ExplorerWorker, exceptionResult);
|
||||
// }
|
||||
//
|
||||
// CriteriaLastModified = fileModified;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
Dictionary<int, Scan> systemBodies;
|
||||
if (SystemBodyHistory.ContainsKey(scanEvent.SystemAddress))
|
||||
{
|
||||
systemBodies = SystemBodyHistory[scanEvent.SystemAddress];
|
||||
if (systemBodies.ContainsKey(scanEvent.BodyID))
|
||||
{
|
||||
if (scanEvent.SystemAddress != 0)
|
||||
{
|
||||
//We've already checked this object.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
systemBodies = new();
|
||||
SystemBodyHistory.Add(scanEvent.SystemAddress, systemBodies);
|
||||
}
|
||||
|
||||
if (SystemBodyHistory.Count > 1000)
|
||||
{
|
||||
foreach (var entry in SystemBodyHistory.Where(entry => entry.Key != scanEvent.SystemAddress).ToList())
|
||||
{
|
||||
SystemBodyHistory.Remove(entry.Key);
|
||||
}
|
||||
SystemBodyHistory.TrimExcess();
|
||||
}
|
||||
|
||||
if (scanEvent.SystemAddress != 0 && !systemBodies.ContainsKey(scanEvent.BodyID))
|
||||
systemBodies.Add(scanEvent.BodyID, scanEvent);
|
||||
|
||||
var results = DefaultCriteria.CheckInterest(scanEvent, SystemBodyHistory, BodySignalHistory, ExplorerWorker.settings);
|
||||
|
||||
if (BarycentreHistory.ContainsKey(scanEvent.SystemAddress) && scanEvent.Parent != null && BarycentreHistory[scanEvent.SystemAddress].ContainsKey(scanEvent.Parent[0].Body))
|
||||
{
|
||||
ProcessScan(ConvertBarycentre(BarycentreHistory[scanEvent.SystemAddress][scanEvent.Parent[0].Body], scanEvent), readAll);
|
||||
}
|
||||
|
||||
// if (ExplorerWorker.settings.EnableCustomCriteria)
|
||||
// results.AddRange(CustomCriteriaManager.CheckInterest(scanEvent, SystemBodyHistory, BodySignalHistory, ExplorerWorker.settings));
|
||||
|
||||
if (results.Count > 0)
|
||||
{
|
||||
StringBuilder notificationDetail = new();
|
||||
StringBuilder notificationExtendedDetail = new();
|
||||
foreach (var result in results)
|
||||
{
|
||||
var scanResult = new ExplorerUIResults
|
||||
{
|
||||
BodyName = result.SystemWide ? scanEvent.StarSystem : scanEvent.BodyName,
|
||||
Time = scanEvent.TimestampDateTime.ToString("G"),
|
||||
Description = result.Description,
|
||||
Details = result.Detail
|
||||
};
|
||||
ObservatoryCore.AddGridItem(ExplorerWorker, scanResult);
|
||||
notificationDetail.AppendLine(result.Description);
|
||||
notificationExtendedDetail.AppendLine(result.Detail);
|
||||
}
|
||||
|
||||
string bodyAffix;
|
||||
|
||||
if (scanEvent.StarSystem != null && scanEvent.BodyName.StartsWith(scanEvent.StarSystem))
|
||||
{
|
||||
bodyAffix = scanEvent.BodyName.Replace(scanEvent.StarSystem, string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
bodyAffix = string.Empty;
|
||||
}
|
||||
|
||||
var bodyLabel = SecurityElement.Escape(scanEvent.PlanetClass == "Barycentre" ? "Barycentre" : "Body");
|
||||
|
||||
string spokenAffix;
|
||||
|
||||
if (bodyAffix.Length > 0)
|
||||
{
|
||||
if (bodyAffix.Contains("Ring"))
|
||||
{
|
||||
var ringIndex = bodyAffix.Length - 6;
|
||||
// spokenAffix =
|
||||
// "<say-as interpret-as=\"spell-out\">" + bodyAffix[..ringIndex]
|
||||
// + "</say-as><break strength=\"weak\"/>" + SplitOrdinalForSsml(bodyAffix.Substring(ringIndex, 1))
|
||||
// + bodyAffix[(ringIndex + 1)..];
|
||||
}
|
||||
else
|
||||
{
|
||||
//spokenAffix = SplitOrdinalForSsml(bodyAffix);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bodyLabel = "Primary Star";
|
||||
spokenAffix = string.Empty;
|
||||
}
|
||||
|
||||
throw new NotImplementedException("Scan Complete Notification Not Implemented");
|
||||
// NotificationArgs args = new()
|
||||
// {
|
||||
// Title = bodyLabel + bodyAffix,
|
||||
// TitleSsml = $"<speak version=\"1.0\" xmlns=\"http://www.w3.org/2001/10/synthesis\" xml:lang=\"en-US\"><voice name=\"\">{bodyLabel} {spokenAffix}</voice></speak>",
|
||||
// Detail = notificationDetail.ToString(),
|
||||
// Sender = ExplorerWorker.ShortName,
|
||||
// ExtendedDetails = notificationExtendedDetail.ToString(),
|
||||
// CoalescingId = scanEvent.BodyID,
|
||||
// };
|
||||
//
|
||||
// ObservatoryCore.SendNotification(args);
|
||||
}
|
||||
}
|
||||
}
|
17
Explorer/Explorer.csproj
Normal file
17
Explorer/Explorer.csproj
Normal file
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>Explorer</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLua" Version="1.7.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ObservatoryFramework\ObservatoryFramework.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
25
Explorer/Explorer.sln
Normal file
25
Explorer/Explorer.sln
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
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
|
78
Explorer/ExplorerSettings.cs
Normal file
78
Explorer/ExplorerSettings.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using Observatory.Framework;
|
||||
|
||||
namespace Explorer;
|
||||
|
||||
public class ExplorerSettings
|
||||
{
|
||||
[SettingDisplayName("Only Show Current System")]
|
||||
public bool OnlyShowCurrentSystem { get; set; }
|
||||
|
||||
[SettingDisplayName("Landable & Terraformable")]
|
||||
public bool LandableTerraformable { get; set; }
|
||||
|
||||
[SettingDisplayName("Landable w/ Atmosphere")]
|
||||
public bool LandableAtmosphere { get; set; }
|
||||
|
||||
[SettingDisplayName("Landable High-g")]
|
||||
public bool LandableHighG { get; set; }
|
||||
|
||||
[SettingDisplayName("Landable Large")]
|
||||
public bool LandableLarge { get; set; }
|
||||
|
||||
[SettingDisplayName("Close Orbit")]
|
||||
public bool CloseOrbit { get; set; }
|
||||
|
||||
[SettingDisplayName("Shepherd Moon")]
|
||||
public bool ShepherdMoon { get; set; }
|
||||
|
||||
[SettingDisplayName("Wide Ring")]
|
||||
public bool WideRing { get; set; }
|
||||
|
||||
[SettingDisplayName("Close Binary")]
|
||||
public bool CloseBinary { get; set; }
|
||||
|
||||
[SettingDisplayName("Colliding Binary")]
|
||||
public bool CollidingBinary { get; set; }
|
||||
|
||||
[SettingDisplayName("Close Ring Proximity")]
|
||||
public bool CloseRing { get; set; }
|
||||
|
||||
[SettingDisplayName("Codex Discoveries")]
|
||||
public bool Codex { get; set; }
|
||||
|
||||
[SettingDisplayName("Uncommon Secondary Star")]
|
||||
public bool UncommonSecondary { get; set; }
|
||||
|
||||
[SettingDisplayName("Landable w/ Ring")]
|
||||
public bool LandableRing { get; set; }
|
||||
|
||||
[SettingDisplayName("Nested Moon")]
|
||||
public bool Nested { get; set; }
|
||||
|
||||
[SettingDisplayName("Small Object")]
|
||||
public bool SmallObject { get; set; }
|
||||
|
||||
[SettingDisplayName("Fast Rotation")]
|
||||
public bool FastRotation { get; set; }
|
||||
|
||||
[SettingDisplayName("Fast Orbit")]
|
||||
public bool FastOrbit { get; set; }
|
||||
|
||||
[SettingDisplayName("High Eccentricity")]
|
||||
public bool HighEccentricity { get; set; }
|
||||
|
||||
[SettingDisplayName("Diverse Life")]
|
||||
public bool DiverseLife { get; set; }
|
||||
|
||||
[SettingDisplayName("Good FSD Injection")]
|
||||
public bool GoodFSDBody { get; set; }
|
||||
|
||||
[SettingDisplayName("All FSD Mats In System")]
|
||||
public bool GreenSystem { get; set; }
|
||||
|
||||
[SettingDisplayName("All Surface Mats In System")]
|
||||
public bool GoldSystem { get; set; }
|
||||
|
||||
[SettingDisplayName("High-Value Body")]
|
||||
public bool HighValueMappable { get; set; }
|
||||
}
|
12
Explorer/ExplorerUIResults.cs
Normal file
12
Explorer/ExplorerUIResults.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Explorer;
|
||||
|
||||
public class ExplorerUIResults
|
||||
{
|
||||
public string Time { get; set; }
|
||||
|
||||
public string BodyName { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public string Details { get; set; }
|
||||
}
|
121
Explorer/Worker.cs
Normal file
121
Explorer/Worker.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reflection;
|
||||
using Observatory.Framework;
|
||||
using Observatory.Framework.Files.Journal;
|
||||
using Observatory.Framework.Files.Journal.Exploration;
|
||||
using Observatory.Framework.Files.Journal.FleetCarrier;
|
||||
using Observatory.Framework.Files.Journal.Travel;
|
||||
|
||||
namespace Explorer;
|
||||
|
||||
public class ExplorerWorker : IObservatoryWorker
|
||||
{
|
||||
public ExplorerWorker()
|
||||
{
|
||||
settings = new()
|
||||
{
|
||||
CloseBinary = true,
|
||||
CloseOrbit = true,
|
||||
CloseRing = true,
|
||||
CollidingBinary = true,
|
||||
FastRotation = true,
|
||||
HighEccentricity = true,
|
||||
LandableAtmosphere = true,
|
||||
LandableHighG = true,
|
||||
LandableLarge = true,
|
||||
LandableRing = true,
|
||||
LandableTerraformable = true,
|
||||
Nested = true,
|
||||
ShepherdMoon = true,
|
||||
SmallObject = true,
|
||||
WideRing = true
|
||||
};
|
||||
resultsGrid = new();
|
||||
}
|
||||
|
||||
private Explorer explorer;
|
||||
private ObservableCollection<object> resultsGrid;
|
||||
private IObservatoryCore Core;
|
||||
|
||||
private bool recordProcessedSinceBatchStart;
|
||||
|
||||
public string Name => "Observatory Explorer";
|
||||
|
||||
public string ShortName => "Explorer";
|
||||
|
||||
public string Version => typeof(ExplorerWorker).Assembly.GetName().Version.ToString();
|
||||
|
||||
private PluginUI pluginUI;
|
||||
|
||||
public PluginUI PluginUI => pluginUI;
|
||||
|
||||
public void Load(IObservatoryCore observatoryCore)
|
||||
{
|
||||
explorer = new Explorer(this, observatoryCore);
|
||||
resultsGrid.Add(new ExplorerUIResults());
|
||||
pluginUI = new PluginUI(resultsGrid);
|
||||
Core = observatoryCore;
|
||||
}
|
||||
|
||||
public void JournalEvent<TJournal>(TJournal journal) where TJournal : JournalBase
|
||||
{
|
||||
switch (journal)
|
||||
{
|
||||
case Scan scan:
|
||||
explorer.ProcessScan(scan, Core.IsLogMonitorBatchReading && recordProcessedSinceBatchStart);
|
||||
// Set this *after* the first scan processes so that we get the current custom criteria file.
|
||||
if (Core.IsLogMonitorBatchReading) recordProcessedSinceBatchStart = true;
|
||||
break;
|
||||
case FSSBodySignals signals:
|
||||
explorer.RecordSignal(signals);
|
||||
break;
|
||||
case ScanBaryCentre barycentre:
|
||||
explorer.RecordBarycentre(barycentre);
|
||||
break;
|
||||
case FSDJump fsdjump:
|
||||
if (fsdjump is CarrierJump && !((CarrierJump)fsdjump).Docked)
|
||||
break;
|
||||
explorer.SetSystem(fsdjump.StarSystem);
|
||||
break;
|
||||
case Location location:
|
||||
explorer.SetSystem(location.StarSystem);
|
||||
break;
|
||||
case DiscoveryScan discoveryScan:
|
||||
break;
|
||||
case FSSDiscoveryScan discoveryScan:
|
||||
break;
|
||||
case FSSSignalDiscovered signalDiscovered:
|
||||
break;
|
||||
case NavBeaconScan beaconScan:
|
||||
break;
|
||||
case SAAScanComplete scanComplete:
|
||||
break;
|
||||
case SAASignalsFound signalsFound:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void LogMonitorStateChanged(LogMonitorStateChangedEventArgs args)
|
||||
{
|
||||
if (LogMonitorStateChangedEventArgs.IsBatchRead(args.NewState))
|
||||
{
|
||||
// Beginning a batch read. Clear grid.
|
||||
recordProcessedSinceBatchStart = false;
|
||||
Core.SetGridItems(this, [
|
||||
typeof(ExplorerUIResults).GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Select(p => p.Name)
|
||||
.ToDictionary(p => p, p => string.Empty)
|
||||
]);
|
||||
explorer.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public object Settings
|
||||
{
|
||||
get => settings;
|
||||
set => settings = (ExplorerSettings)value;
|
||||
}
|
||||
|
||||
internal ExplorerSettings settings;
|
||||
}
|
Reference in New Issue
Block a user