Pushing last changes

This commit is contained in:
Heavy Bob 2025-07-07 04:01:49 +10:00
commit 60fbaa99ac
11 changed files with 328 additions and 183 deletions

View file

@ -10,10 +10,14 @@
<Configurations>Debug;Release;Test</Configurations> <Configurations>Debug;Release;Test</Configurations>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<ApplicationIcon>AutoTrackR2.ico</ApplicationIcon> <ApplicationIcon>AutoTrackR2.ico</ApplicationIcon>
<Version>2.12</Version> <Version>2.13</Version>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<GenerateAssemblyInfo>true</GenerateAssemblyInfo> <GenerateAssemblyInfo>true</GenerateAssemblyInfo>
<Deterministic>true</Deterministic> <Deterministic>true</Deterministic>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>false</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>false</IncludeNativeLibrariesForSelfExtract>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -48,4 +52,8 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.6" />
</ItemGroup>
</Project> </Project>

View file

@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoTrackR2", "AutoTrackR2.csproj", "{E22C9485-B9D3-1444-D963-459CE2284B32}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E22C9485-B9D3-1444-D963-459CE2284B32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E22C9485-B9D3-1444-D963-459CE2284B32}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E22C9485-B9D3-1444-D963-459CE2284B32}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E22C9485-B9D3-1444-D963-459CE2284B32}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BBD4E112-AC22-4175-9EAB-009EB91E395B}
EndGlobalSection
EndGlobal

View file

@ -14,7 +14,15 @@ public static class Weapons
"*repair*", "*repair*",
"*cutter*", "*cutter*",
"*tractor*", "*tractor*",
"*carryable*" "*carryable*",
"*shotgun*",
"*sniper*",
"*rifle*",
"*smg*",
"*pistol*",
"*sniper*",
"*lmg*",
"*volt*",
}; };
public static bool IsKnownWeapon(string weaponName) public static bool IsKnownWeapon(string weaponName)

View file

@ -34,6 +34,9 @@ public partial class HomePage : UserControl
{ {
InitializeComponent(); InitializeComponent();
// Initialize default values
LocalPlayerData.PlayerShip = "Player";
if (string.IsNullOrEmpty(ConfigManager.KillHistoryFile)) if (string.IsNullOrEmpty(ConfigManager.KillHistoryFile))
{ {
throw new InvalidOperationException("KillHistoryFile path is not configured."); throw new InvalidOperationException("KillHistoryFile path is not configured.");
@ -119,11 +122,11 @@ public partial class HomePage : UserControl
{ {
// Game is not running, set everything to Unknown // Game is not running, set everything to Unknown
GameModeTextBox.Text = "Unknown"; GameModeTextBox.Text = "Unknown";
PlayerShipTextBox.Text = "Unknown"; PlayerShipTextBox.Text = "Player";
PilotNameTextBox.Text = "Unknown"; PilotNameTextBox.Text = "Unknown";
LocationTextBox.Text = "Unknown"; LocationTextBox.Text = "Unknown";
LocalPlayerData.CurrentGameMode = GameMode.Unknown; LocalPlayerData.CurrentGameMode = GameMode.Unknown;
LocalPlayerData.PlayerShip = string.Empty; LocalPlayerData.PlayerShip = "Player";
LocalPlayerData.Username = string.Empty; LocalPlayerData.Username = string.Empty;
LocalPlayerData.LastSeenVehicleLocation = "Unknown"; LocalPlayerData.LastSeenVehicleLocation = "Unknown";
@ -197,6 +200,28 @@ public partial class HomePage : UserControl
}); });
}; };
// Vehicle Control
TrackREventDispatcher.VehicleControlEvent += (data) =>
{
Dispatcher.Invoke(() =>
{
PlayerShipTextBox.Text = data.Ship ?? "Player";
AdjustFontSize(PlayerShipTextBox);
LocalPlayerData.PlayerShip = data.Ship ?? "Player";
});
};
// Jump Drive State Changed (Location Only)
TrackREventDispatcher.JumpDriveStateChangedEvent += (data) =>
{
Dispatcher.Invoke(() =>
{
LocalPlayerData.LastSeenVehicleLocation = data.Location;
LocationTextBox.Text = data.Location;
AdjustFontSize(LocationTextBox);
});
};
// Game Mode // Game Mode
TrackREventDispatcher.PlayerChangedGameModeEvent += (mode) => TrackREventDispatcher.PlayerChangedGameModeEvent += (mode) =>
{ {

View file

@ -1,4 +1,5 @@
using System.Globalization; using Microsoft.Data.Sqlite;
using System.Globalization;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Linq; using System.Linq;
@ -11,8 +12,7 @@ namespace AutoTrackR2;
public class KillHistoryManager public class KillHistoryManager
{ {
private readonly string _killHistoryPath; private readonly string _dbPath;
private readonly string _headers = "KillTime,EnemyPilot,EnemyShip,Enlisted,RecordNumber,OrgAffiliation,Player,Weapon,Ship,Method,Mode,GameVersion,TrackRver,Logged,PFP,Hash\n";
private readonly KillStreakManager _killStreakManager; private readonly KillStreakManager _killStreakManager;
private readonly ConcurrentQueue<KillData> _killQueue; private readonly ConcurrentQueue<KillData> _killQueue;
private readonly CancellationTokenSource _cancellationTokenSource; private readonly CancellationTokenSource _cancellationTokenSource;
@ -23,13 +23,9 @@ public class KillHistoryManager
{ {
var appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "AutoTrackR2"); var appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "AutoTrackR2");
Directory.CreateDirectory(appDataPath); // Ensure the directory exists Directory.CreateDirectory(appDataPath); // Ensure the directory exists
_killHistoryPath = Path.Combine(appDataPath, "Kill-log.csv"); _dbPath = Path.Combine(appDataPath, "kills.db");
// Create the CSV file with headers if it doesn't exist InitializeDatabase();
if (!File.Exists(_killHistoryPath))
{
File.WriteAllText(_killHistoryPath, _headers);
}
_killStreakManager = new KillStreakManager(soundsPath); _killStreakManager = new KillStreakManager(soundsPath);
_killQueue = new ConcurrentQueue<KillData>(); _killQueue = new ConcurrentQueue<KillData>();
@ -39,6 +35,34 @@ public class KillHistoryManager
_processingTask = Task.Run(ProcessKillQueue); _processingTask = Task.Run(ProcessKillQueue);
} }
private void InitializeDatabase()
{
using var connection = new SqliteConnection($"Data Source={_dbPath}");
connection.Open();
var command = connection.CreateCommand();
command.CommandText = @"
CREATE TABLE IF NOT EXISTS kills (
Hash TEXT PRIMARY KEY,
KillTime TEXT,
EnemyPilot TEXT,
EnemyShip TEXT,
Enlisted TEXT,
RecordNumber TEXT,
OrgAffiliation TEXT,
Weapon TEXT,
Ship TEXT,
Method TEXT,
Location TEXT,
Mode TEXT,
GameVersion TEXT,
TrackRver TEXT,
Logged TEXT,
PFP TEXT
);
";
command.ExecuteNonQuery();
}
private async Task ProcessKillQueue() private async Task ProcessKillQueue()
{ {
while (!_cancellationTokenSource.Token.IsCancellationRequested) while (!_cancellationTokenSource.Token.IsCancellationRequested)
@ -70,56 +94,41 @@ public class KillHistoryManager
{ {
try try
{ {
// Ensure all fields are properly escaped for CSV using var connection = new SqliteConnection($"Data Source={_dbPath}");
var fields = new[] await connection.OpenAsync();
{ var command = connection.CreateCommand();
kill.KillTime.ToString(), command.CommandText = @"
EscapeCsvField(kill.EnemyPilot), INSERT OR IGNORE INTO kills (
EscapeCsvField(kill.EnemyShip), Hash, KillTime, EnemyPilot, EnemyShip, Enlisted, RecordNumber, OrgAffiliation, Weapon, Ship, Method, Location, Mode, GameVersion, TrackRver, Logged, PFP
EscapeCsvField(kill.Enlisted), ) VALUES (
EscapeCsvField(kill.RecordNumber), $Hash, $KillTime, $EnemyPilot, $EnemyShip, $Enlisted, $RecordNumber, $OrgAffiliation, $Weapon, $Ship, $Method, $Location, $Mode, $GameVersion, $TrackRver, $Logged, $PFP
EscapeCsvField(kill.OrgAffiliation), );
EscapeCsvField(kill.Player), ";
EscapeCsvField(kill.Weapon), command.Parameters.AddWithValue("$Hash", kill.Hash ?? "");
EscapeCsvField(kill.Ship), command.Parameters.AddWithValue("$KillTime", kill.KillTime ?? "");
EscapeCsvField(kill.Method), command.Parameters.AddWithValue("$EnemyPilot", kill.EnemyPilot ?? "");
EscapeCsvField(kill.Mode), command.Parameters.AddWithValue("$EnemyShip", kill.EnemyShip ?? "");
EscapeCsvField(kill.GameVersion), command.Parameters.AddWithValue("$Enlisted", kill.Enlisted ?? "");
EscapeCsvField(kill.TrackRver), command.Parameters.AddWithValue("$RecordNumber", kill.RecordNumber ?? "");
EscapeCsvField(kill.Logged), command.Parameters.AddWithValue("$OrgAffiliation", kill.OrgAffiliation ?? "");
EscapeCsvField(kill.PFP), command.Parameters.AddWithValue("$Weapon", kill.Weapon ?? "");
EscapeCsvField(kill.Hash) command.Parameters.AddWithValue("$Ship", kill.Ship ?? "");
}; command.Parameters.AddWithValue("$Method", kill.Method ?? "");
command.Parameters.AddWithValue("$Location", kill.Location ?? "");
var csvLine = string.Join(",", fields); command.Parameters.AddWithValue("$Mode", kill.Mode ?? "");
command.Parameters.AddWithValue("$GameVersion", kill.GameVersion ?? "");
// Use FileShare.Read to allow other processes to read while we write command.Parameters.AddWithValue("$TrackRver", kill.TrackRver ?? "");
using var stream = new FileStream(_killHistoryPath, FileMode.Append, FileAccess.Write, FileShare.Read); command.Parameters.AddWithValue("$Logged", kill.Logged ?? "");
using var writer = new StreamWriter(stream); command.Parameters.AddWithValue("$PFP", kill.PFP ?? "");
await writer.WriteLineAsync(csvLine); await command.ExecuteNonQueryAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
Debug.WriteLine($"Error writing kill to CSV: {ex.Message}"); Debug.WriteLine($"Error writing kill to SQLite: {ex.Message}");
throw; throw;
} }
} }
private string EscapeCsvField(string field)
{
if (string.IsNullOrEmpty(field)) return "";
// If the field contains any special characters, wrap it in quotes
if (field.Contains(",") || field.Contains("\"") || field.Contains("\n") || field.Contains("\r"))
{
// Double up any quotes
field = field.Replace("\"", "\"\"");
return $"\"{field}\"";
}
return field;
}
public void AddKill(KillData kill) public void AddKill(KillData kill)
{ {
_killQueue.Enqueue(kill); _killQueue.Enqueue(kill);
@ -145,47 +154,38 @@ public class KillHistoryManager
public List<KillData> GetKills() public List<KillData> GetKills()
{ {
var kills = new List<KillData>(); var kills = new List<KillData>();
using var connection = new SqliteConnection($"Data Source={_dbPath}");
using var reader = new StreamReader(new FileStream(_killHistoryPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); connection.Open();
reader.ReadLine(); // Skip headers var command = connection.CreateCommand();
command.CommandText = "SELECT Hash, KillTime, EnemyPilot, EnemyShip, Enlisted, RecordNumber, OrgAffiliation, Weapon, Ship, Method, Location, Mode, GameVersion, TrackRver, Logged, PFP FROM kills";
while (reader.Peek() >= 0) using var reader = command.ExecuteReader();
while (reader.Read())
{ {
var line = reader.ReadLine();
// Remove extra quotes from CSV data
// Todo: These quotes are for handling commas in the data, but not sure if they're necessary
line = line?.Replace("\"", string.Empty);
var data = line?.Split(',');
kills.Add(new KillData kills.Add(new KillData
{ {
KillTime = data?[0], Hash = reader.GetString(0),
EnemyPilot = data?[1], KillTime = reader.GetString(1),
EnemyShip = data?[2], EnemyPilot = reader.GetString(2),
Enlisted = data?[3], EnemyShip = reader.GetString(3),
RecordNumber = data?[4], Enlisted = reader.GetString(4),
OrgAffiliation = data?[5], RecordNumber = reader.GetString(5),
Player = data?[6], OrgAffiliation = reader.GetString(6),
Weapon = data?[7], Weapon = reader.GetString(7),
Ship = data?[8], Ship = reader.GetString(8),
Method = data?[9], Method = reader.GetString(9),
Mode = data?[10], Location = reader.GetString(10),
GameVersion = data?[11], Mode = reader.GetString(11),
TrackRver = data?[12], GameVersion = reader.GetString(12),
Logged = data?[13], TrackRver = reader.GetString(13),
PFP = data?[14], Logged = reader.GetString(14),
Hash = data?[15] PFP = reader.GetString(15)
}); });
} }
// Apply KillFeedLimit if specified // Apply KillFeedLimit if specified
if (ConfigManager.KillFeedLimit.HasValue && ConfigManager.KillFeedLimit.Value > 0) if (ConfigManager.KillFeedLimit.HasValue && ConfigManager.KillFeedLimit.Value > 0)
{ {
kills = kills.TakeLast(ConfigManager.KillFeedLimit.Value).ToList(); kills = kills.TakeLast(ConfigManager.KillFeedLimit.Value).ToList();
} }
return kills; return kills;
} }
@ -193,57 +193,45 @@ public class KillHistoryManager
{ {
string currentMonth = DateTime.Now.ToString("MMM", CultureInfo.InvariantCulture); string currentMonth = DateTime.Now.ToString("MMM", CultureInfo.InvariantCulture);
var kills = new List<KillData>(); var kills = new List<KillData>();
using var connection = new SqliteConnection($"Data Source={_dbPath}");
// Read all kills directly from file, ignoring KillFeedLimit connection.Open();
using var reader = new StreamReader(new FileStream(_killHistoryPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); var command = connection.CreateCommand();
reader.ReadLine(); // Skip headers command.CommandText = "SELECT Hash, KillTime, EnemyPilot, EnemyShip, Enlisted, RecordNumber, OrgAffiliation, Weapon, Ship, Method, Location, Mode, GameVersion, TrackRver, Logged, PFP FROM kills";
using var reader = command.ExecuteReader();
while (reader.Peek() >= 0) while (reader.Read())
{ {
var line = reader.ReadLine(); var kill = new KillData
{
// Remove extra quotes from CSV data Hash = reader.GetString(0),
line = line?.Replace("\"", string.Empty); KillTime = reader.GetString(1),
EnemyPilot = reader.GetString(2),
var data = line?.Split(','); EnemyShip = reader.GetString(3),
Enlisted = reader.GetString(4),
RecordNumber = reader.GetString(5),
OrgAffiliation = reader.GetString(6),
Weapon = reader.GetString(7),
Ship = reader.GetString(8),
Method = reader.GetString(9),
Location = reader.GetString(10),
Mode = reader.GetString(11),
GameVersion = reader.GetString(12),
TrackRver = reader.GetString(13),
Logged = reader.GetString(14),
PFP = reader.GetString(15)
};
// Check if the kill is from the current month before adding it // Check if the kill is from the current month before adding it
var killTime = data?[0]; if (!string.IsNullOrEmpty(kill.KillTime))
if (string.IsNullOrEmpty(killTime)) continue;
// Try to parse as Unix timestamp first
if (long.TryParse(killTime, out long unixTime))
{ {
var date = DateTimeOffset.FromUnixTimeSeconds(unixTime); if (long.TryParse(kill.KillTime, out long unixTime))
if (date.ToString("MMM", CultureInfo.InvariantCulture) != currentMonth) continue; {
var date = DateTimeOffset.FromUnixTimeSeconds(unixTime).DateTime;
if (date.ToString("MMM", CultureInfo.InvariantCulture) == currentMonth)
{
kills.Add(kill);
}
}
} }
else if (!killTime.Contains(currentMonth))
{
// Fall back to checking if it contains the month name (old format)
continue;
}
kills.Add(new KillData
{
KillTime = killTime,
EnemyPilot = data?[1],
EnemyShip = data?[2],
Enlisted = data?[3],
RecordNumber = data?[4],
OrgAffiliation = data?[5],
Player = data?[6],
Weapon = data?[7],
Ship = data?[8],
Method = data?[9],
Mode = data?[10],
GameVersion = data?[11],
TrackRver = data?[12],
Logged = data?[13],
PFP = data?[14],
Hash = data?[15]
});
} }
return kills; return kills;
} }
} }

View file

@ -12,12 +12,12 @@ public class JumpDriveStateChangedEvent : ILogEventHandler
{ {
public Regex Pattern { get; } public Regex Pattern { get; }
private Regex _cleanUpPattern = new Regex(@"(.+?)_\d+$"); private Regex _cleanUpPattern = new Regex(@"(.+?)_\d+$");
public JumpDriveStateChangedEvent() public JumpDriveStateChangedEvent()
{ {
Pattern = new Regex(@"<Jump Drive State Changed>.*.adam: (?<ShipName>.*.) in zone (?<Location>.*.)\)"); Pattern = new Regex(@"<Jump Drive State Changed>.*.adam: (?<ShipName>.*.) in zone (?<Location>.*.)\)");
} }
public void Handle(LogEntry entry) public void Handle(LogEntry entry)
{ {
if (entry.Message is null) return; if (entry.Message is null) return;
@ -34,6 +34,13 @@ public class JumpDriveStateChangedEvent : ILogEventHandler
{ {
data.ShipName = match.Groups[1].Value; data.ShipName = match.Groups[1].Value;
} }
// Skip applying loadout if ship name matches "Default"
if (data.ShipName?.Equals("Default", StringComparison.OrdinalIgnoreCase) == true)
{
return;
}
if (!string.IsNullOrEmpty(data.ShipName) && !string.IsNullOrEmpty(data.Location)) if (!string.IsNullOrEmpty(data.ShipName) && !string.IsNullOrEmpty(data.Location))
{ {
TrackREventDispatcher.OnJumpDriveStateChangedEvent(data); TrackREventDispatcher.OnJumpDriveStateChangedEvent(data);

View file

@ -6,29 +6,35 @@ public class RequestJumpFailedEvent : ILogEventHandler
{ {
public Regex Pattern { get; } public Regex Pattern { get; }
private Regex _cleanUpPattern = new Regex(@"(.+?)_\d+$"); private Regex _cleanUpPattern = new Regex(@"(.+?)_\d+$");
public RequestJumpFailedEvent() public RequestJumpFailedEvent()
{ {
Pattern = new Regex(@"<Request Jump Failed>.*.adam: (?<ShipName>.*.) in zone (?<Location>.*.)\)"); Pattern = new Regex(@"<Request Jump Failed>.*.adam: (?<ShipName>.*.) in zone (?<Location>.*.)\)");
} }
public void Handle(LogEntry entry) public void Handle(LogEntry entry)
{ {
if (entry.Message is null) return; if (entry.Message is null) return;
var match = Pattern.Match(entry.Message); var match = Pattern.Match(entry.Message);
if (!match.Success) return; if (!match.Success) return;
var data = new JumpDriveStateChangedData var data = new JumpDriveStateChangedData
{ {
Location = match.Groups["Location"].Value Location = match.Groups["Location"].Value
}; };
match = _cleanUpPattern.Match(match.Groups["ShipName"].Value); match = _cleanUpPattern.Match(match.Groups["ShipName"].Value);
if (match.Success) if (match.Success)
{ {
data.ShipName = match.Groups[1].Value; data.ShipName = match.Groups[1].Value;
} }
// Skip applying loadout if ship name matches "Default"
if (data.ShipName?.Equals("Default", StringComparison.OrdinalIgnoreCase) == true)
{
return;
}
if (!string.IsNullOrEmpty(data.ShipName) && !string.IsNullOrEmpty(data.Location)) if (!string.IsNullOrEmpty(data.ShipName) && !string.IsNullOrEmpty(data.Location))
{ {
TrackREventDispatcher.OnJumpDriveStateChangedEvent(data); TrackREventDispatcher.OnJumpDriveStateChangedEvent(data);

View file

@ -0,0 +1,80 @@
using System.Text.RegularExpressions;
using AutoTrackR2.Constants;
namespace AutoTrackR2.LogEventHandlers;
public struct VehicleControlData
{
public string Action { get; set; } // "SetDriver" or "ClearDriver"
public string VehicleName { get; set; }
public string? Ship { get; set; }
}
public class VehicleControlEvent : ILogEventHandler
{
public Regex Pattern { get; }
private Regex _cleanUpPattern = new Regex(@"(.+?)_\d+$");
public VehicleControlEvent()
{
// Pattern for entering vehicle (granted control)
Pattern = new Regex(@"granted control token for '(?<vehicle_name>[^']+)'", RegexOptions.Compiled);
}
public void Handle(LogEntry entry)
{
if (entry.Message is null) return;
// Check for vehicle entry (granted control)
var enterMatch = Pattern.Match(entry.Message);
if (enterMatch.Success)
{
var vehicleName = enterMatch.Groups["vehicle_name"].Value;
var cleanedShipName = CleanShipName(vehicleName);
LocalPlayerData.PlayerShip = cleanedShipName;
var data = new VehicleControlData
{
Action = "SetDriver",
VehicleName = vehicleName,
Ship = cleanedShipName
};
TrackREventDispatcher.OnVehicleControlEvent(data);
return;
}
}
private string CleanShipName(string vehicleName)
{
var cleanMatch = _cleanUpPattern.Match(vehicleName);
return cleanMatch.Success ? cleanMatch.Groups[1].Value : vehicleName;
}
}
public class VehicleControlClearEvent : ILogEventHandler
{
public Regex Pattern { get; }
public VehicleControlClearEvent()
{
// Pattern for exiting vehicle (releasing control)
Pattern = new Regex(@"releasing control token", RegexOptions.Compiled);
}
public void Handle(LogEntry entry)
{
if (entry.Message is null) return;
var match = Pattern.Match(entry.Message);
if (match.Success)
{
LocalPlayerData.PlayerShip = "Player";
var data = new VehicleControlData
{
Action = "ClearDriver",
VehicleName = "Player",
Ship = "Player"
};
TrackREventDispatcher.OnVehicleControlEvent(data);
}
}
}

View file

@ -1,64 +1,55 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using AutoTrackR2.Constants;
namespace AutoTrackR2.LogEventHandlers; namespace AutoTrackR2.LogEventHandlers;
public struct VehicleDestructionData public struct VehicleDestructionData
{ {
public string Vehicle { get; set; } public string VehicleName { get; set; }
public string VehicleId { get; set; }
public string Team { get; set; }
public string VehicleZone { get; set; } public string VehicleZone { get; set; }
public float PosX { get; set; } public string? Ship { get; set; }
public float PosY { get; set; }
public float PosZ { get; set; }
public string Driver { get; set; }
public int DestroyLevelFrom { get; set; }
public int DestroyLevelTo { get; set; }
public string CausedBy { get; set; }
public string DamageType { get; set; }
} }
public class VehicleDestructionEvent : ILogEventHandler public class VehicleDestructionEvent : ILogEventHandler
{ {
public Regex Pattern { get; } public Regex Pattern { get; }
private Regex _shipManufacturerPattern;
private Regex _cleanUpPattern = new Regex(@"(.+?)_\d+$");
public VehicleDestructionEvent() public VehicleDestructionEvent()
{ {
const string patternStr = """ Pattern = new Regex(@"<Vehicle Destruction> Vehicle '(?<vehicle_name>[^']+)' \[(?<vehicle_id>\d+)\] \[(?<team>[^\]]+)\] in zone '(?<vehicle_zone>[^']+)' has been destroyed");
<(?<timestamp>[^>]+)> \[Notice\] <Vehicle Destruction> CVehicle::OnAdvanceDestroyLevel: _shipManufacturerPattern = new Regex($"^({string.Join("|", ShipManufacturers.List)})");
Vehicle '(?<vehicle>[^']+)' \[\d+\] in zone '(?<vehicle_zone>[^']+)'
\[pos x: (?<pos_x>[-\d\.]+), y: (?<pos_y>[-\d\.]+), z: (?<pos_z>[-\d\.]+)
vel x: [^,]+, y: [^,]+, z: [^\]]+\] driven by '(?<driver>[^']+)' \[\d+\]
advanced from destroy level (?<destroy_level_from>\d+) to (?<destroy_level_to>\d+)
caused by '(?<caused_by>[^']+)' \[\d+\] with '(?<damage_type>[^']+)'
""";
Pattern = new Regex(Regex.Replace(patternStr, @"\t|\n|\r", ""));
} }
public void Handle(LogEntry entry) public void Handle(LogEntry entry)
{ {
if (entry.Message == null) if (entry.Message is null) return;
{
return;
}
var match = Pattern.Match(entry.Message); var match = Pattern.Match(entry.Message);
if (!match.Success) if (!match.Success) return;
{
return;
}
var data = new VehicleDestructionData var data = new VehicleDestructionData
{ {
Vehicle = match.Groups["vehicle"].Value, VehicleName = match.Groups["vehicle_name"].Value,
VehicleId = match.Groups["vehicle_id"].Value,
Team = match.Groups["team"].Value,
VehicleZone = match.Groups["vehicle_zone"].Value, VehicleZone = match.Groups["vehicle_zone"].Value,
PosX = float.Parse(match.Groups["pos_x"].Value),
PosY = float.Parse(match.Groups["pos_y"].Value),
PosZ = float.Parse(match.Groups["pos_z"].Value),
Driver = match.Groups["driver"].Value,
DestroyLevelFrom = int.Parse(match.Groups["destroy_level_from"].Value),
DestroyLevelTo = int.Parse(match.Groups["destroy_level_to"].Value),
CausedBy = match.Groups["caused_by"].Value,
DamageType = match.Groups["damage_type"].Value,
}; };
// Extract ship name from vehicle name if it's a ship
var shipMatch = _shipManufacturerPattern.Match(data.VehicleName);
if (shipMatch.Success)
{
var cleanMatch = _cleanUpPattern.Match(data.VehicleName);
if (cleanMatch.Success)
{
data.Ship = cleanMatch.Groups[1].Value;
}
}
TrackREventDispatcher.OnVehicleDestructionEvent(data); TrackREventDispatcher.OnVehicleDestructionEvent(data);
} }
} }

View file

@ -47,7 +47,9 @@ public class LogHandler
new JumpDriveStateChangedEvent(), new JumpDriveStateChangedEvent(),
new RequestJumpFailedEvent(), new RequestJumpFailedEvent(),
new VehicleDestructionEvent(), new VehicleDestructionEvent(),
new ActorDeathEvent() new ActorDeathEvent(),
new VehicleControlEvent(),
new VehicleControlClearEvent()
]; ];
public LogHandler(string? logPath) public LogHandler(string? logPath)

View file

@ -18,37 +18,36 @@ public static class TrackREventDispatcher
{ {
InstancedInteriorEvent?.Invoke(data); InstancedInteriorEvent?.Invoke(data);
} }
// Player changed GameMode (AC or PU) // Player changed GameMode (AC or PU)
public static event Action<GameMode>? PlayerChangedGameModeEvent; public static event Action<GameMode>? PlayerChangedGameModeEvent;
public static void OnPlayerChangedGameModeEvent(GameMode mode) public static void OnPlayerChangedGameModeEvent(GameMode mode)
{ {
PlayerChangedGameModeEvent?.Invoke(mode); PlayerChangedGameModeEvent?.Invoke(mode);
} }
// Game version has been detected // Game version has been detected
public static event Action<string>? GameVersionEvent; public static event Action<string>? GameVersionEvent;
public static void OnGameVersionEvent(string value) public static void OnGameVersionEvent(string value)
{ {
GameVersionEvent?.Invoke(value); GameVersionEvent?.Invoke(value);
} }
// Actor has died // Actor has died
public static event Action<ActorDeathData>? ActorDeathEvent; public static event Action<ActorDeathData>? ActorDeathEvent;
public static void OnActorDeathEvent(ActorDeathData data) public static void OnActorDeathEvent(ActorDeathData data)
{ {
ActorDeathEvent?.Invoke(data); ActorDeathEvent?.Invoke(data);
} }
// Vehicle has been destroyed // Vehicle has been destroyed
public static event Action<VehicleDestructionData>? VehicleDestructionEvent; public static event Action<VehicleDestructionData>? VehicleDestructionEvent;
public static void OnVehicleDestructionEvent(VehicleDestructionData data) public static void OnVehicleDestructionEvent(VehicleDestructionData data)
{ {
VehicleDestructionEvent?.Invoke(data); VehicleDestructionEvent?.Invoke(data);
} }
// Jump Drive state has changed // Jump Drive state has changed
// Todo: Add proper data for this event. Right now only ship name is used.
public static event Action<JumpDriveStateChangedData>? JumpDriveStateChangedEvent; public static event Action<JumpDriveStateChangedData>? JumpDriveStateChangedEvent;
public static void OnJumpDriveStateChangedEvent(JumpDriveStateChangedData data) public static void OnJumpDriveStateChangedEvent(JumpDriveStateChangedData data)
{ {
@ -61,4 +60,11 @@ public static class TrackREventDispatcher
{ {
StreamlinkRecordEvent?.Invoke(streamerHandle); StreamlinkRecordEvent?.Invoke(streamerHandle);
} }
// Vehicle control has changed
public static event Action<VehicleControlData>? VehicleControlEvent;
public static void OnVehicleControlEvent(VehicleControlData data)
{
VehicleControlEvent?.Invoke(data);
}
} }