diff --git a/AutoTrackR2/App.xaml.cs b/AutoTrackR2/App.xaml.cs index 7441812..54e759c 100644 --- a/AutoTrackR2/App.xaml.cs +++ b/AutoTrackR2/App.xaml.cs @@ -12,7 +12,7 @@ namespace AutoTrackR2 /// </summary> public partial class App : System.Windows.Application { - private static Mutex _mutex = null; + private static Mutex? _mutex = null; private static bool _mutexOwned = false; private static readonly string CrashLogPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @@ -33,7 +33,11 @@ namespace AutoTrackR2 try { // Ensure crash log directory exists - Directory.CreateDirectory(Path.GetDirectoryName(CrashLogPath)); + string? crashLogDir = Path.GetDirectoryName(CrashLogPath); + if (!string.IsNullOrEmpty(crashLogDir)) + { + Directory.CreateDirectory(crashLogDir); + } // Set up unhandled exception handlers AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; @@ -94,36 +98,34 @@ namespace AutoTrackR2 $"Target Site: {ex?.TargetSite}\n" + "----------------------------------------\n"; - File.AppendAllText(CrashLogPath, logMessage); + // Ensure directory exists + string? crashLogDir = Path.GetDirectoryName(CrashLogPath); + if (!string.IsNullOrEmpty(crashLogDir)) + { + Directory.CreateDirectory(crashLogDir); + } - // Show error message to user - MessageBox.Show( - "AutoTrackR2 has encountered an error. A crash log has been created.\n" + - $"Location: {CrashLogPath}", - "AutoTrackR2 Error", - MessageBoxButton.OK, - MessageBoxImage.Error - ); + // Write to log file + File.AppendAllText(CrashLogPath, logMessage); } catch (Exception logEx) { - // If logging fails, at least show a basic error message - MessageBox.Show( - "AutoTrackR2 has encountered an error and failed to create a crash log.\n" + - $"Error: {ex?.Message}", - "AutoTrackR2 Error", - MessageBoxButton.OK, - MessageBoxImage.Error - ); + // If we can't log to file, at least try to show a message box + Console.WriteLine($"Failed to write crash log: {logEx.Message}"); + MessageBox.Show($"Failed to write crash log: {logEx.Message}", "AutoTrackR2 Error", MessageBoxButton.OK, MessageBoxImage.Error); } } protected override void OnExit(ExitEventArgs e) { - // Clean up resources - _killStreakManager?.Cleanup(); - _mutex?.Dispose(); - base.OnExit(e); + if (_mutexOwned) + { + // Clean up resources + _streamlinkHandler?.Dispose(); + _killStreakManager?.Dispose(); + _mutex?.Dispose(); + base.OnExit(e); + } } } } diff --git a/AutoTrackR2/ConfigPage.xaml b/AutoTrackR2/ConfigPage.xaml index 55374eb..d06a794 100644 --- a/AutoTrackR2/ConfigPage.xaml +++ b/AutoTrackR2/ConfigPage.xaml @@ -1,181 +1,163 @@ <UserControl x:Class="AutoTrackR2.ConfigPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - Height="410" + Height="380" Width="700"> - <Grid Background="{DynamicResource BackgroundLightBrush}"> - <!-- Main Layout Grid --> - <Grid> - <Grid.RowDefinitions> - <RowDefinition Height="*"/> - <RowDefinition Height="Auto"/> - </Grid.RowDefinitions> - - <Grid.ColumnDefinitions> - <!-- Left column for the main content area --> - <ColumnDefinition Width="*"/> - <!-- Right column for the buttons --> - <ColumnDefinition Width="Auto"/> - </Grid.ColumnDefinitions> - - <!-- Section for Config Fields --> - <StackPanel Grid.Row="0" - VerticalAlignment="Top" - Margin="20,10,20,0"> - <!-- Log File --> - <StackPanel Margin="0,5,0,10" - Orientation="Horizontal"> - <TextBlock Text="ⓘ" - ToolTip="Set this to the Game.log file in your StarCitizen\LIVE directory." - Foreground="{DynamicResource TextBrush}" - FontSize="20" - Margin="0,0,3,5"/> - <TextBlock Text="Log File:" - Foreground="{DynamicResource TextBrush}" - FontSize="16" - Margin="0,5,0,5" - FontFamily="{StaticResource Roboto}"/> - <StackPanel Orientation="Horizontal"> - <TextBox Name="LogFilePath" - Width="260" - Height="30" - Margin="10,0,0,0" - Style="{StaticResource RoundedTextBox}"/> - <Button Content="Browse" - Width="75" - Height="30" - FontFamily="{StaticResource Orbitron}" - Margin="5,0" - Style="{StaticResource ButtonStyle}" - Click="LogFileBrowseButton_Click"/> - <Button Content="Open" - Width="75" - Height="30" - FontFamily="{StaticResource Orbitron}" - Margin="0,0" - Style="{StaticResource ButtonStyle}" - Click="LogFileOpenButton_Click"/> - </StackPanel> - </StackPanel> - - <!-- API URL --> - <StackPanel Margin="0,0,0,10" - Orientation="Horizontal"> - <TextBlock Text="ⓘ" - ToolTip="Need a URL? No idea what to do? Contact heavy_bob on Discord!" - Foreground="{DynamicResource TextBrush}" - FontSize="20" - Margin="0,3,3,5"/> - <TextBlock Text="API URL:" - Foreground="{DynamicResource TextBrush}" - FontSize="16" - Margin="0,5,0,5"/> - <StackPanel Orientation="Horizontal"> - <TextBox Name="ApiUrl" - Width="260" - Height="30" - Margin="10,0,0,0" - Style="{StaticResource RoundedTextBox}"/> - <Button Content="Test" - Width="75" - Height="30" - FontFamily="{StaticResource Orbitron}" - Margin="5,0" - Style="{StaticResource ButtonStyle}" - Click="TestApiButton_Click"/> - </StackPanel> - </StackPanel> - - <!-- API Key --> - <StackPanel Margin="0,0,0,10" - Orientation="Horizontal"> - <TextBlock Text="ⓘ" - ToolTip="Need a key? No idea what to do? Contact heavy_bob on Discord!" - Foreground="{DynamicResource TextBrush}" - FontSize="20" - Margin="0,3,3,5"/> - <TextBlock Text="API Key:" - Foreground="{DynamicResource TextBrush}" - FontSize="16" - Margin="0,5,0,5"/> - <PasswordBox Name="ApiKey" - Width="260" - Height="30" - Margin="10,0,0,0" - Style="{StaticResource RoundedPasswordBox}"/> - </StackPanel> - - <!-- Video Path --> - <StackPanel Margin="0,0,0,10" - Orientation="Horizontal"> - <TextBlock Text="ⓘ" - ToolTip="The directory where your clipping software saves kills. Check the README." - Foreground="{DynamicResource TextBrush}" - FontSize="20" - Margin="0,3,3,5"/> - <TextBlock Text="Video Path:" - Foreground="{DynamicResource TextBrush}" - FontSize="16" - Margin="0,5,0,5"/> - <StackPanel Orientation="Horizontal"> - <TextBox Name="VideoPath" - Width="260" - Height="30" - Margin="10,0,0,0" - Style="{StaticResource RoundedTextBox}"/> - <Button Content="Browse" - Width="75" - Height="30" - FontFamily="{StaticResource Orbitron}" - Margin="5,0" - Style="{StaticResource ButtonStyle}" - Click="VideoPathBrowseButton_Click"/> - <Button Content="Open" - Width="75" - Height="30" - FontFamily="{StaticResource Orbitron}" - Margin="0,0" - Style="{StaticResource ButtonStyle}" - Click="VideoPathOpenButton_Click"/> - </StackPanel> - </StackPanel> - - <!-- Toggle Settings Grid --> - <Grid Margin="0,0,0,10"> - <Grid.ColumnDefinitions> - <ColumnDefinition Width="*"/> - <ColumnDefinition Width="*"/> - </Grid.ColumnDefinitions> - <Grid.RowDefinitions> - <RowDefinition Height="Auto"/> - <RowDefinition Height="Auto"/> - <RowDefinition Height="Auto"/> - <RowDefinition Height="Auto"/> - <RowDefinition Height="Auto"/> - </Grid.RowDefinitions> - - <!-- Kill Streak Test Button --> - <!-- - <Button Grid.Column="0" - Grid.Row="0" - Grid.ColumnSpan="2" - Content="Test Kill Streak Sounds" + <!-- Main Content --> + <StackPanel Margin="20,5,20,0"> + <!-- Log File --> + <StackPanel Margin="0,0,0,5" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="Set this to the Game.log file in your StarCitizen\LIVE directory." + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,0,3,5"/> + <TextBlock Text="Log File:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,5,0,5" + FontFamily="{StaticResource Roboto}"/> + <StackPanel Orientation="Horizontal"> + <TextBox Name="LogFilePath" + Width="260" + Height="30" + Margin="10,0,0,0" + Style="{StaticResource RoundedTextBox}"/> + <Button Content="Browse" + Width="75" Height="30" FontFamily="{StaticResource Orbitron}" - Margin="0,0,0,10" + Margin="5,0" Style="{StaticResource ButtonStyle}" - Click="TestKillStreakButton_Click"/> - --> + Click="LogFileBrowseButton_Click"/> + <Button Content="Open" + Width="75" + Height="30" + FontFamily="{StaticResource Orbitron}" + Margin="0,0" + Style="{StaticResource ButtonStyle}" + Click="LogFileOpenButton_Click"/> + </StackPanel> + </StackPanel> - <!-- Left Column Controls --> + <!-- API URL --> + <StackPanel Margin="0,0,0,5" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="Need a URL? No idea what to do? Contact heavy_bob on Discord!" + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,3,3,5"/> + <TextBlock Text="API URL:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,5,0,5"/> + <StackPanel Orientation="Horizontal"> + <TextBox Name="ApiUrl" + Width="260" + Height="30" + Margin="10,0,0,0" + Style="{StaticResource RoundedTextBox}"/> + <Button Content="Test" + Width="75" + Height="30" + FontFamily="{StaticResource Orbitron}" + Margin="5,0" + Style="{StaticResource ButtonStyle}" + Click="TestApiButton_Click"/> + </StackPanel> + </StackPanel> - <!-- Visor Wipe Toggle --> - <StackPanel Grid.Column="0" - Grid.Row="0" - Orientation="Horizontal" - Margin="0,0,5,5"> + <!-- API Key --> + <StackPanel Margin="0,0,0,5" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="Need a key? No idea what to do? Contact heavy_bob on Discord!" + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,3,3,5"/> + <TextBlock Text="API Key:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,5,0,5"/> + <PasswordBox Name="ApiKey" + Width="260" + Height="30" + Margin="10,0,0,0" + Style="{StaticResource RoundedPasswordBox}"/> + </StackPanel> + + <!-- Video Path --> + <StackPanel Margin="0,0,0,5" + Orientation="Horizontal"> + <TextBlock Text="ⓘ" + ToolTip="The directory where your clipping software saves kills. Check the README." + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,3,3,5"/> + <TextBlock Text="Video Path:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,5,0,5"/> + <StackPanel Orientation="Horizontal"> + <TextBox Name="VideoPath" + Width="260" + Height="30" + Margin="10,0,0,0" + Style="{StaticResource RoundedTextBox}"/> + <Button Content="Browse" + Width="75" + Height="30" + FontFamily="{StaticResource Orbitron}" + Margin="5,0" + Style="{StaticResource ButtonStyle}" + Click="VideoPathBrowseButton_Click"/> + <Button Content="Open" + Width="75" + Height="30" + FontFamily="{StaticResource Orbitron}" + Margin="0,0" + Style="{StaticResource ButtonStyle}" + Click="VideoPathOpenButton_Click"/> + </StackPanel> + </StackPanel> + + <!-- Toggle Settings Grid --> + <Grid> + <Grid.ColumnDefinitions> + <ColumnDefinition Width="*"/> + <ColumnDefinition Width="*"/> + </Grid.ColumnDefinitions> + + <!-- Left Column --> + <StackPanel Grid.Column="0" + Margin="0,0,5,0"> + <!-- Kill Streak --> + <StackPanel Orientation="Horizontal" + Margin="0,0,0,3"> + <TextBlock Text="ⓘ" + ToolTip="Enable or disable kill streak sound effects." + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,4,3,0"/> + <TextBlock Text="Kill Streak:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,7,10,0"/> + <Slider x:Name="KillStreakSlider" + Width="100" + Minimum="0" + Maximum="1" + Style="{StaticResource ToggleSliderStyle}" + ValueChanged="KillStreakSlider_ValueChanged"/> + </StackPanel> + + <!-- Visor Wipe --> + <StackPanel Orientation="Horizontal" + Margin="0,0,0,3"> <TextBlock Text="ⓘ" ToolTip="Perform a Visor Wipe animation on player kill. Requires AHKv2." Foreground="{DynamicResource TextBrush}" @@ -185,23 +167,17 @@ Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,7,0,0"/> - <Slider Name="VisorWipeSlider" + <Slider x:Name="VisorWipeSlider" + Width="100" Minimum="0" Maximum="1" - TickFrequency="1" - IsSnapToTickEnabled="True" - Value="0" Style="{StaticResource ToggleSliderStyle}" - Margin="10,-4,0,0" - Width="100" ValueChanged="VisorWipeSlider_ValueChanged"/> </StackPanel> - <!-- Video Record Toggle --> - <StackPanel Grid.Column="0" - Grid.Row="1" - Orientation="Horizontal" - Margin="0,0,5,5"> + <!-- Video Record --> + <StackPanel Orientation="Horizontal" + Margin="0,0,0,3"> <TextBlock Text="ⓘ" ToolTip="Record video on player kill. Requires AHKv2." Foreground="{DynamicResource TextBrush}" @@ -211,23 +187,17 @@ Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,7,0,0"/> - <Slider Name="VideoRecordSlider" + <Slider x:Name="VideoRecordSlider" + Width="100" Minimum="0" Maximum="1" - TickFrequency="1" - IsSnapToTickEnabled="True" - Value="0" Style="{StaticResource ToggleSliderStyle}" - Margin="10,-4,0,0" - Width="100" ValueChanged="VideoRecordSlider_ValueChanged"/> </StackPanel> - <!-- Offline Mode Toggle --> - <StackPanel Grid.Column="0" - Grid.Row="2" - Orientation="Horizontal" - Margin="0,0,5,5"> + <!-- Offline Mode --> + <StackPanel Orientation="Horizontal" + Margin="0,0,0,3"> <TextBlock Text="ⓘ" ToolTip="With Offline Mode enabled, kills will not be submitted to the configured API." Foreground="{DynamicResource TextBrush}" @@ -237,25 +207,21 @@ Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,7,0,0"/> - <Slider Name="OfflineModeSlider" + <Slider x:Name="OfflineModeSlider" + Width="100" Minimum="0" Maximum="1" - TickFrequency="1" - IsSnapToTickEnabled="True" - Value="0" Style="{StaticResource ToggleSliderStyle}" - Margin="10,-4,0,0" - Width="100" ValueChanged="OfflineModeSlider_ValueChanged"/> </StackPanel> + </StackPanel> - <!-- Right Column Controls --> - - <!-- Streamlink Toggle --> - <StackPanel Grid.Column="1" - Grid.Row="0" - Orientation="Horizontal" - Margin="5,0,0,5"> + <!-- Right Column --> + <StackPanel Grid.Column="1" + Margin="5,0,0,0"> + <!-- Streamlink --> + <StackPanel Orientation="Horizontal" + Margin="0,0,0,3"> <TextBlock Text="ⓘ" ToolTip="Use Streamlink for video recording." Foreground="{DynamicResource TextBrush}" @@ -265,23 +231,17 @@ Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,7,0,0"/> - <Slider Name="StreamlinkSlider" + <Slider x:Name="StreamlinkSlider" + Width="100" Minimum="0" Maximum="1" - TickFrequency="1" - IsSnapToTickEnabled="True" - Value="0" Style="{StaticResource ToggleSliderStyle}" - Margin="10,-4,0,0" - Width="100" ValueChanged="StreamlinkSlider_ValueChanged"/> </StackPanel> - <!-- Streamlink Duration --> - <StackPanel Grid.Column="1" - Grid.Row="1" - Orientation="Horizontal" - Margin="5,0,0,5"> + <!-- Duration --> + <StackPanel Orientation="Horizontal" + Margin="0,0,0,3"> <TextBlock Text="ⓘ" ToolTip="Duration of Streamlink recordings in seconds." Foreground="{DynamicResource TextBrush}" @@ -291,15 +251,11 @@ Foreground="{DynamicResource TextBrush}" FontSize="16" Margin="0,7,0,0"/> - <Slider Name="StreamlinkDurationSlider" + <Slider x:Name="StreamlinkDurationSlider" + Width="100" Minimum="10" Maximum="600" - TickFrequency="30" - IsSnapToTickEnabled="True" - Value="30" Style="{StaticResource SliderStyle}" - Margin="10,-4,0,0" - Width="100" ValueChanged="StreamlinkDurationSlider_ValueChanged"/> <TextBlock Name="StreamlinkDurationText" Text="30s" @@ -309,50 +265,49 @@ </StackPanel> <!-- Test Streamlink Button --> - <Button Grid.Column="1" - Grid.Row="2" - Content="Test Streamlink" + <Button Content="Test Streamlink" Width="120" Height="30" FontFamily="{StaticResource Orbitron}" - Margin="5,0,0,5" + Margin="0,0,0,3" Style="{StaticResource ButtonStyle}" Click="TestStreamlinkButton_Click"/> + </StackPanel> + </Grid> - <!-- Theme Slider --> - <StackPanel Grid.Column="0" - Grid.Row="3" - Grid.ColumnSpan="2" - Orientation="Horizontal" - Margin="0,0,0,5"> - <TextBlock Text="Theme:" - Foreground="{DynamicResource TextBrush}" - FontSize="16" - Margin="0,7,10,0"/> - <Slider x:Name="ThemeSlider" - Minimum="0" - Value="0" - TickFrequency="1" - IsSnapToTickEnabled="True" - ValueChanged="ThemeSlider_ValueChanged" - Width="350" - Style="{StaticResource ThreePositionSlider}"/> - </StackPanel> - </Grid> + <!-- Theme Slider --> + <StackPanel Orientation="Horizontal" + Margin="0,5,0,0"> + <TextBlock Text="ⓘ" + ToolTip="Select theme for the application." + Foreground="{DynamicResource TextBrush}" + FontSize="20" + Margin="0,4,3,0"/> + <TextBlock Text="Theme:" + Foreground="{DynamicResource TextBrush}" + FontSize="16" + Margin="0,7,10,0"/> + <Slider x:Name="ThemeSlider" + Width="350" + Minimum="0" + Value="0" + TickFrequency="1" + IsSnapToTickEnabled="True" + ValueChanged="ThemeSlider_ValueChanged" + Style="{StaticResource SliderStyle}"/> </StackPanel> + </StackPanel> - <!-- Save Button --> - <Button x:Name="SaveButton" - Grid.Row="1" - Content="Save" - Width="100" - Height="40" - Style="{StaticResource ButtonStyle}" - FontFamily="{StaticResource Orbitron}" - HorizontalAlignment="Right" - VerticalAlignment="Bottom" - Margin="0,0,80,20" - Click="SaveButton_Click"/> - </Grid> + <!-- Save Button --> + <Button x:Name="SaveButton" + Content="Save" + Width="100" + Height="40" + Style="{StaticResource ButtonStyle}" + FontFamily="{StaticResource Orbitron}" + HorizontalAlignment="Right" + VerticalAlignment="Bottom" + Margin="0,0,80,10" + Click="SaveButton_Click"/> </Grid> </UserControl> diff --git a/AutoTrackR2/ConfigPage.xaml.cs b/AutoTrackR2/ConfigPage.xaml.cs index e7cd94b..0c8065a 100644 --- a/AutoTrackR2/ConfigPage.xaml.cs +++ b/AutoTrackR2/ConfigPage.xaml.cs @@ -26,8 +26,6 @@ public partial class ConfigPage : UserControl Dictionary<string, Theme>? _themes = null; private KillStreakManager? _testKillStreakManager; - private bool _isTestRunning = false; - private int _currentTestStep = 0; public ConfigPage(MainWindow mainWindow) { @@ -44,6 +42,7 @@ public partial class ConfigPage : UserControl ThemeSlider.Value = ConfigManager.Theme; StreamlinkSlider.Value = ConfigManager.StreamlinkEnabled; StreamlinkDurationSlider.Value = ConfigManager.StreamlinkDuration; + KillStreakSlider.Value = ConfigManager.KillStreakEnabled; // Initialize Streamlink slider style if (StreamlinkSlider.Value == 0) @@ -333,6 +332,18 @@ public partial class ConfigPage : UserControl } } + private void KillStreakSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) + { + if (KillStreakSlider.Value == 0) + { + KillStreakSlider.Style = (Style)Application.Current.FindResource("FalseToggleStyle"); + } + else + { + KillStreakSlider.Style = (Style)Application.Current.FindResource("ToggleSliderStyle"); + } + } + private void SaveButton_Click(object sender, RoutedEventArgs e) { ConfigManager.ApiKey = ApiKey.Password; @@ -343,6 +354,9 @@ public partial class ConfigPage : UserControl ConfigManager.VideoRecord = (int)VideoRecordSlider.Value; ConfigManager.OfflineMode = (int)OfflineModeSlider.Value; ConfigManager.Theme = (int)ThemeSlider.Value; + ConfigManager.StreamlinkEnabled = (int)StreamlinkSlider.Value; + ConfigManager.StreamlinkDuration = (int)StreamlinkDurationSlider.Value; + ConfigManager.KillStreakEnabled = (int)KillStreakSlider.Value; // Save the current config values ConfigManager.SaveConfig(); diff --git a/AutoTrackR2/KillHistoryManager.cs b/AutoTrackR2/KillHistoryManager.cs index fd8fa1a..abf8b61 100644 --- a/AutoTrackR2/KillHistoryManager.cs +++ b/AutoTrackR2/KillHistoryManager.cs @@ -58,6 +58,7 @@ public class KillHistoryManager catch (Exception ex) { // If there's any error reading the file, consider it malformed + Console.WriteLine($"Error reading CSV file: {ex.Message}"); string backupPath = Path.Combine( Path.GetDirectoryName(_killHistoryPath)!, "Kill-log.old" @@ -100,8 +101,11 @@ public class KillHistoryManager using var writer = new StreamWriter(fileStream); writer.Write(csv.ToString()); - // Trigger kill streak sound - _killStreakManager.OnKill(); + // Trigger kill streak sound only if enabled + if (ConfigManager.KillStreakEnabled == 1) + { + _killStreakManager.OnKill(); + } } catch (IOException ex) { diff --git a/AutoTrackR2/KillStreakManager.cs b/AutoTrackR2/KillStreakManager.cs index 6d4fb28..98650dd 100644 --- a/AutoTrackR2/KillStreakManager.cs +++ b/AutoTrackR2/KillStreakManager.cs @@ -5,7 +5,7 @@ using NAudio.Wave; namespace AutoTrackR2; -public class KillStreakManager +public class KillStreakManager : IDisposable { private readonly Queue<string> _soundQueue = new(); private readonly System.Timers.Timer _killStreakTimer = new(5000); // 5 seconds between kills for streak @@ -15,6 +15,7 @@ public class KillStreakManager private readonly object _lock = new(); private WaveOutEvent? _waveOut; private bool _isPlaying = false; + private bool _disposed = false; public KillStreakManager(string soundsPath) { @@ -27,13 +28,15 @@ public class KillStreakManager { lock (_lock) { + if (_disposed) return; + _currentKills++; _totalKills++; _killStreakTimer.Stop(); _killStreakTimer.Start(); // Handle multi-kill announcements - string multiKillSound = _currentKills switch + string? multiKillSound = _currentKills switch { 2 => "double_kill.mp3", 3 => "triple_kill.mp3", @@ -48,7 +51,7 @@ public class KillStreakManager }; // Handle spree announcements - string spreeSound = _totalKills switch + string? spreeSound = _totalKills switch { 5 => "killing_spree.mp3", 10 => "killing_frenzy.mp3", @@ -88,6 +91,8 @@ public class KillStreakManager { lock (_lock) { + if (_disposed) return; + _totalKills = 0; _currentKills = 0; _killStreakTimer.Stop(); @@ -99,77 +104,97 @@ public class KillStreakManager { lock (_lock) { + if (_disposed) return; + _currentKills = 0; _killStreakTimer.Stop(); Console.WriteLine("Kill streak reset due to timeout"); } } - public void Cleanup() - { - lock (_lock) - { - _killStreakTimer.Stop(); - _killStreakTimer.Dispose(); - _waveOut?.Dispose(); - _waveOut = null; - } - } - private void PlayNextSound() { - if (_soundQueue.Count > 0) + if (_soundQueue.Count == 0 || _disposed) return; + + string soundPath = _soundQueue.Dequeue(); + Console.WriteLine($"Attempting to play sound: {soundPath}"); + + try { - string soundPath = _soundQueue.Dequeue(); - Console.WriteLine($"Attempting to play sound: {soundPath}"); - try + if (!File.Exists(soundPath)) { - if (File.Exists(soundPath)) + Console.WriteLine($"Sound file not found: {soundPath}"); + _isPlaying = false; + if (_soundQueue.Count > 0) { - // Stop any currently playing sound - _waveOut?.Stop(); - _waveOut?.Dispose(); - - // Create a new WaveOutEvent - _waveOut = new WaveOutEvent(); - - // Create a new AudioFileReader for the MP3 file - using var audioFile = new AudioFileReader(soundPath); - _waveOut.Init(audioFile); - - // Set up event handler for when playback finishes - _waveOut.PlaybackStopped += (sender, e) => - { - _isPlaying = false; - if (_soundQueue.Count > 0) - { - PlayNextSound(); - } - }; - - _isPlaying = true; - _waveOut.Play(); - - Console.WriteLine($"Successfully played sound: {soundPath}"); + PlayNextSound(); } - else + return; + } + + // Stop any currently playing sound + _waveOut?.Stop(); + _waveOut?.Dispose(); + _waveOut = null; + + // Create a new WaveOutEvent + _waveOut = new WaveOutEvent(); + + // Create a new AudioFileReader for the MP3 file + using var audioFile = new AudioFileReader(soundPath); + _waveOut.Init(audioFile); + + // Set up event handler for when playback finishes + _waveOut.PlaybackStopped += (sender, e) => + { + lock (_lock) { - Console.WriteLine($"Sound file not found: {soundPath}"); + if (_disposed) return; + _isPlaying = false; if (_soundQueue.Count > 0) { PlayNextSound(); } } - } - catch (Exception ex) + }; + + _isPlaying = true; + _waveOut.Play(); + + Console.WriteLine($"Successfully played sound: {soundPath}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error playing sound {soundPath}: {ex.Message}"); + _isPlaying = false; + if (_soundQueue.Count > 0) { - Console.WriteLine($"Error playing sound {soundPath}: {ex.Message}"); - _isPlaying = false; - if (_soundQueue.Count > 0) - { - PlayNextSound(); - } + PlayNextSound(); + } + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + + if (disposing) + { + lock (_lock) + { + _disposed = true; + _killStreakTimer.Stop(); + _killStreakTimer.Dispose(); + _waveOut?.Stop(); + _waveOut?.Dispose(); + _waveOut = null; } } } diff --git a/AutoTrackR2/MainWindow.xaml.cs b/AutoTrackR2/MainWindow.xaml.cs index 2e95f60..32ac47b 100644 --- a/AutoTrackR2/MainWindow.xaml.cs +++ b/AutoTrackR2/MainWindow.xaml.cs @@ -198,6 +198,7 @@ namespace AutoTrackR2 public static int Theme { get; set; } public static int StreamlinkEnabled { get; set; } public static int StreamlinkDuration { get; set; } = 30; + public static int KillStreakEnabled { get; set; } = 1; // Default to enabled static ConfigManager() { @@ -253,6 +254,8 @@ namespace AutoTrackR2 StreamlinkEnabled = int.Parse(line.Substring("StreamlinkEnabled=".Length).Trim()); else if (line.StartsWith("StreamlinkDuration=")) StreamlinkDuration = int.Parse(line.Substring("StreamlinkDuration=".Length).Trim()); + else if (line.StartsWith("KillStreakEnabled=")) + KillStreakEnabled = int.Parse(line.Substring("KillStreakEnabled=".Length).Trim()); } } } @@ -286,6 +289,7 @@ namespace AutoTrackR2 writer.WriteLine($"Theme={Theme}"); writer.WriteLine($"StreamlinkEnabled={StreamlinkEnabled}"); writer.WriteLine($"StreamlinkDuration={StreamlinkDuration}"); + writer.WriteLine($"KillStreakEnabled={KillStreakEnabled}"); } } } diff --git a/AutoTrackR2/StreamlinkHandler.cs b/AutoTrackR2/StreamlinkHandler.cs index 9fa4e0e..3d79566 100644 --- a/AutoTrackR2/StreamlinkHandler.cs +++ b/AutoTrackR2/StreamlinkHandler.cs @@ -3,13 +3,33 @@ using System.IO; namespace AutoTrackR2; -public class StreamlinkHandler +public class StreamlinkHandler : IDisposable { + private bool _disposed = false; + public StreamlinkHandler() { TrackREventDispatcher.StreamlinkRecordEvent += HandleStreamlinkRecord; } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + + if (disposing) + { + TrackREventDispatcher.StreamlinkRecordEvent -= HandleStreamlinkRecord; + } + + _disposed = true; + } + public static bool IsStreamlinkInstalled() { try diff --git a/AutoTrackR2/WebHandler.cs b/AutoTrackR2/WebHandler.cs index 244d24b..737ebb4 100644 --- a/AutoTrackR2/WebHandler.cs +++ b/AutoTrackR2/WebHandler.cs @@ -144,7 +144,7 @@ public static class WebHandler apiKillData.rsi = "-1"; } - if (!apiKillData.enlisted.Contains(",")) + if (!string.IsNullOrEmpty(apiKillData.enlisted) && !apiKillData.enlisted.Contains(",")) { //Get second whitespace in string var index = apiKillData.enlisted.IndexOf(" ", apiKillData.enlisted.IndexOf(" ", StringComparison.Ordinal) + 1, StringComparison.Ordinal);