diff --git a/AutoTrackR2/HomePage.xaml.cs b/AutoTrackR2/HomePage.xaml.cs index 84ee0f7..80bbd06 100644 --- a/AutoTrackR2/HomePage.xaml.cs +++ b/AutoTrackR2/HomePage.xaml.cs @@ -25,6 +25,9 @@ public partial class HomePage : UserControl private bool _isLogHandlerRunning = false; private int _counter = 1; private System.Timers.Timer _counterTimer; + private bool _isInitializing = false; + private System.Timers.Timer? _initializationTimer; + private bool _wasStarCitizenRunningOnStart = false; public HomePage() { @@ -42,6 +45,18 @@ public partial class HomePage : UserControl AdjustFontSize(KillTallyTextBox); AddKillHistoryKillsToUI(); + // Check if Star Citizen is already running + _wasStarCitizenRunningOnStart = IsStarCitizenRunning(); + if (_wasStarCitizenRunningOnStart) + { + UpdateStatusIndicator(true); + InitializeLogHandler(); + } + else + { + UpdateStatusIndicator(false); + } + // Initialize and start the status check timer _statusCheckTimer = new System.Timers.Timer(1000); // Check every second _statusCheckTimer.Elapsed += CheckStarCitizenStatus; @@ -51,16 +66,6 @@ public partial class HomePage : UserControl _counterTimer = new System.Timers.Timer(1000); // Update every second _counterTimer.Elapsed += UpdateCounter; _counterTimer.Start(); - - // Check if Star Citizen is already running and initialize accordingly - if (IsStarCitizenRunning()) - { - Dispatcher.Invoke(() => - { - UpdateStatusIndicator(true); - InitializeLogHandler(); // Then initialize the log handler - }); - } } private void CheckStarCitizenStatus(object? sender, ElapsedEventArgs e) @@ -68,14 +73,44 @@ public partial class HomePage : UserControl bool isRunning = IsStarCitizenRunning(); Dispatcher.Invoke(() => { - UpdateStatusIndicator(isRunning); + if (_isInitializing) + { + return; // Don't update status while initializing + } if (isRunning) { if (!_isLogHandlerRunning) { - // Game is running, start log monitoring and read initial states - InitializeLogHandler(); + if (_wasStarCitizenRunningOnStart) + { + // Game was already running on start, initialize immediately + UpdateStatusIndicator(true); + InitializeLogHandler(); + } + else + { + // Game started after app launch, use initialization delay + _isInitializing = true; + UpdateStatusIndicator(false, true); // Set to yellow for initialization + + _initializationTimer = new System.Timers.Timer(20000); // 20 seconds + _initializationTimer.Elapsed += (sender, e) => + { + _isInitializing = false; + _initializationTimer?.Stop(); + _initializationTimer?.Dispose(); + _initializationTimer = null; + + Dispatcher.Invoke(() => + { + // Game is running, start log monitoring and read initial states + UpdateStatusIndicator(true); + InitializeLogHandler(); + }); + }; + _initializationTimer.Start(); + } } } else @@ -96,16 +131,23 @@ public partial class HomePage : UserControl _logHandler?.StopMonitoring(); _isLogHandlerRunning = false; } + + UpdateStatusIndicator(false); } }); } - private void UpdateStatusIndicator(bool isRunning) + private void UpdateStatusIndicator(bool isRunning, bool isInitializing = false) { - if (isRunning) + if (isInitializing) + { + StatusLight.Fill = new SolidColorBrush(Colors.Yellow); + StatusText.Text = "TrackR\nInitializing"; + } + else if (isRunning) { StatusLight.Fill = new SolidColorBrush(Colors.Green); - StatusText.Text = "TrackR\nActive"; + StatusText.Text = "TrackR\nRunning"; } else { @@ -192,7 +234,8 @@ public partial class HomePage : UserControl TrackRver = "2.10", Enlisted = playerData?.JoinDate, KillTime = ((DateTimeOffset)DateTime.ParseExact(actorDeathData.Timestamp, "yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal)).ToUnixTimeSeconds().ToString(), - PFP = playerData?.PFPURL ?? "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg" + PFP = playerData?.PFPURL ?? "https://cdn.robertsspaceindustries.com/static/images/account/avatar_default_big.jpg", + Hash = WebHandler.GenerateKillHash(actorDeathData.VictimPilot, ((DateTimeOffset)DateTime.ParseExact(actorDeathData.Timestamp, "yyyy-MM-ddTHH:mm:ss.fffZ", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal)).ToUnixTimeSeconds()) }; switch (LocalPlayerData.CurrentGameMode) @@ -205,6 +248,13 @@ public partial class HomePage : UserControl break; } + // Check if this is a duplicate kill + if (WebHandler.IsDuplicateKill(killData.Hash)) + { + Console.WriteLine("Duplicate kill detected, skipping..."); + return; + } + // Add kill to UI Dispatcher.Invoke(() => { diff --git a/AutoTrackR2/KillHistoryManager.cs b/AutoTrackR2/KillHistoryManager.cs index eb6c3a2..65f33ad 100644 --- a/AutoTrackR2/KillHistoryManager.cs +++ b/AutoTrackR2/KillHistoryManager.cs @@ -7,8 +7,8 @@ namespace AutoTrackR2; public class KillHistoryManager { private string _killHistoryPath; - private readonly string _headers = "KillTime,EnemyPilot,EnemyShip,Enlisted,RecordNumber,OrgAffiliation,Player,Weapon,Ship,Method,Mode,GameVersion,TrackRver,Logged,PFP\n"; - + private readonly string _headers = "KillTime,EnemyPilot,EnemyShip,Enlisted,RecordNumber,OrgAffiliation,Player,Weapon,Ship,Method,Mode,GameVersion,TrackRver,Logged,PFP,Hash\n"; + public KillHistoryManager(string logPath) { _killHistoryPath = logPath; @@ -89,8 +89,8 @@ public class KillHistoryManager // Append the new kill data to the CSV file var csv = new StringBuilder(); - csv.AppendLine($"\"{killData.KillTime}\",\"{killData.EnemyPilot}\",\"{killData.EnemyShip}\",\"{killData.Enlisted}\",\"{killData.RecordNumber}\",\"{killData.OrgAffiliation}\",\"{killData.Player}\",\"{killData.Weapon}\",\"{killData.Ship}\",\"{killData.Method}\",\"{killData.Mode}\",\"{killData.GameVersion}\",\"{killData.TrackRver}\",\"{killData.Logged}\",\"{killData.PFP}\""); - + csv.AppendLine($"\"{killData.KillTime}\",\"{killData.EnemyPilot}\",\"{killData.EnemyShip}\",\"{killData.Enlisted}\",\"{killData.RecordNumber}\",\"{killData.OrgAffiliation}\",\"{killData.Player}\",\"{killData.Weapon}\",\"{killData.Ship}\",\"{killData.Method}\",\"{killData.Mode}\",\"{killData.GameVersion}\",\"{killData.TrackRver}\",\"{killData.Logged}\",\"{killData.PFP}\",\"{killData.Hash}\""); + // Check file can be written to try { @@ -138,7 +138,8 @@ public class KillHistoryManager GameVersion = data?[11], TrackRver = data?[12], Logged = data?[13], - PFP = data?[14] + PFP = data?[14], + Hash = data?[15] }); } diff --git a/AutoTrackR2/LogHandler.cs b/AutoTrackR2/LogHandler.cs index 2e2ab91..73c0f7f 100644 --- a/AutoTrackR2/LogHandler.cs +++ b/AutoTrackR2/LogHandler.cs @@ -30,8 +30,11 @@ public class LogHandler private CancellationTokenSource? _cancellationTokenSource; private GameProcessState _gameProcessState = GameProcessState.NotRunning; private bool _isMonitoring = false; + private bool _isInitializing = false; + private System.Timers.Timer? _initializationTimer; public bool IsMonitoring => _isMonitoring; + public bool IsInitializing => _isInitializing; // Handlers that should be run on every log entry // Overlap with _startupEventHandlers is fine @@ -62,6 +65,37 @@ public class LogHandler throw new FileNotFoundException("Log file not found", _logPath); } + // Check if Star Citizen is running + if (!IsStarCitizenRunning()) + { + StartInitializationDelay(); + return; + } + + InitializeLogHandler(); + } + + private void StartInitializationDelay() + { + _isInitializing = true; + _initializationTimer = new System.Timers.Timer(20000); // 20 seconds + _initializationTimer.Elapsed += (sender, e) => + { + _isInitializing = false; + _initializationTimer?.Stop(); + _initializationTimer?.Dispose(); + _initializationTimer = null; + + if (IsStarCitizenRunning()) + { + InitializeLogHandler(); + } + }; + _initializationTimer.Start(); + } + + private void InitializeLogHandler() + { _fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); _reader = new StreamReader(_fileStream); @@ -75,6 +109,11 @@ public class LogHandler StartMonitoring(); } + private bool IsStarCitizenRunning() + { + return Process.GetProcessesByName("StarCitizen").Length > 0; + } + public void StartMonitoring() { if (_isMonitoring) return; @@ -156,14 +195,26 @@ public class LogHandler GameProcessState newGameProcessState = process != null ? GameProcessState.Running : GameProcessState.NotRunning; if (newGameProcessState == GameProcessState.Running && _gameProcessState == GameProcessState.NotRunning) { - // Game process went from NotRunning to Running, so reload the Game.log file - Console.WriteLine("Game process started, reloading log file"); + // Game process went from NotRunning to Running, wait 20 seconds before reloading + Console.WriteLine("Game process started, waiting 20 seconds before initializing..."); + _isInitializing = true; - _reader?.Close(); - _fileStream?.Close(); + _initializationTimer = new System.Timers.Timer(20000); // 20 seconds + _initializationTimer.Elapsed += (sender, e) => + { + _isInitializing = false; + _initializationTimer?.Stop(); + _initializationTimer?.Dispose(); + _initializationTimer = null; - _fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - _reader = new StreamReader(_fileStream); + Console.WriteLine("Initialization delay complete, reloading log file"); + _reader?.Close(); + _fileStream?.Close(); + + _fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + _reader = new StreamReader(_fileStream); + }; + _initializationTimer.Start(); } _gameProcessState = newGameProcessState; } diff --git a/AutoTrackR2/Util.cs b/AutoTrackR2/Util.cs index f783330..1ba1ada 100644 --- a/AutoTrackR2/Util.cs +++ b/AutoTrackR2/Util.cs @@ -29,4 +29,5 @@ public struct KillData public string? TrackRver; public string? Logged; public string? PFP; + public string? Hash; } \ No newline at end of file diff --git a/AutoTrackR2/WebHandler.cs b/AutoTrackR2/WebHandler.cs index 0d93b48..244d24b 100644 --- a/AutoTrackR2/WebHandler.cs +++ b/AutoTrackR2/WebHandler.cs @@ -6,11 +6,19 @@ using System.Text.RegularExpressions; using AutoTrackR2.LogEventHandlers; using System.Globalization; using System.Security.Cryptography; +using System.Collections.Generic; namespace AutoTrackR2; public static class WebHandler { + private static HashSet<string> _recordedKillHashes = new HashSet<string>(); + + public static bool IsDuplicateKill(string hash) + { + return _recordedKillHashes.Contains(hash); + } + class APIKillData { public string? victim_ship { get; set; } @@ -28,7 +36,7 @@ public static class WebHandler public string hash { get; set; } = string.Empty; } - private static string GenerateKillHash(string victimName, long timestamp) + public static string GenerateKillHash(string victimName, long timestamp) { // Combine victim name and timestamp string combined = $"{victimName}_{timestamp}"; @@ -105,6 +113,15 @@ public static class WebHandler public static async Task SubmitKill(KillData killData) { var timestamp = long.Parse(killData.KillTime!); + var hash = GenerateKillHash(killData.EnemyPilot!, timestamp); + + // Check if this kill has already been recorded + if (_recordedKillHashes.Contains(hash)) + { + Console.WriteLine("Duplicate kill detected, skipping..."); + return; + } + var apiKillData = new APIKillData { victim_ship = killData.EnemyShip, @@ -119,7 +136,7 @@ public static class WebHandler trackr_version = killData.TrackRver, location = killData.Location, time = timestamp, - hash = GenerateKillHash(killData.EnemyPilot!, timestamp) + hash = hash }; if (string.IsNullOrEmpty(apiKillData.rsi)) @@ -175,6 +192,9 @@ public static class WebHandler var responseContent = await response.Content.ReadAsStringAsync(); Console.WriteLine($"Response: {responseContent}"); + // Add the hash to our recorded hashes + _recordedKillHashes.Add(hash); + // Only process streamer data if streamlink is enabled if (ConfigManager.StreamlinkEnabled == 1) {