diff --git a/Lithium/.vscode/settings.json b/Lithium/.vscode/settings.json new file mode 100644 index 0000000..013007b --- /dev/null +++ b/Lithium/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dotnet.preferCSharpExtension": true +} \ No newline at end of file diff --git a/Lithium/A1/App.xaml b/Lithium/A1/App.xaml index d00be54..3bd40a1 100644 --- a/Lithium/A1/App.xaml +++ b/Lithium/A1/App.xaml @@ -1,7 +1,7 @@  + StartupUri="../MainWindow.xaml"> diff --git a/Lithium/ConfigLoader.cs b/Lithium/ConfigLoader.cs index f80d2ce..76e4647 100644 --- a/Lithium/ConfigLoader.cs +++ b/Lithium/ConfigLoader.cs @@ -1,10 +1,19 @@ -using System; +using System; using System.IO; namespace Lua { + public class QuickSwapBinding + { + public int[] Keys { get; set; } + public string TargetOperator { get; set; } + public bool IsHeld { get; set; } + } + internal static class ConfigLoader { + public static System.Collections.Generic.List quickSwaps = new System.Collections.Generic.List(); + // AA0001 public static bool debitFlag1 = false; // AA0002 @@ -58,6 +67,7 @@ namespace Lua jitterBurstTime = 1800; jitterCooldown = 3000; textColor = ConsoleColor.Magenta; + quickSwaps.Clear(); SetConsoleColor(); @@ -108,6 +118,8 @@ namespace Lua // AA0026 else if (Helper.ParseConsoleColorFromAssignment(line, "textColor", out var col)) textColor = col; + else if (Helper.ParseQuickSwap(line, out var qsKeys, out var qsOp)) + quickSwaps.Add(new QuickSwapBinding { Keys = qsKeys, TargetOperator = qsOp }); } // AA0027 diff --git a/Lithium/HotkeyHandler.cs b/Lithium/HotkeyHandler.cs index 3363126..ea545cc 100644 --- a/Lithium/HotkeyHandler.cs +++ b/Lithium/HotkeyHandler.cs @@ -1,4 +1,5 @@ -using Lua; +using Lua; +using System; using System.Threading; namespace Lithium @@ -12,25 +13,50 @@ namespace Lithium private static bool _nextSetHeld, _prevSetHeld, _refreshHeld, _switchSideHeld, _capsHeld; // AA0043 private static MainWindow _mainWindow; + + // AA0044 - Added: Cancellation token for graceful shutdown + private static CancellationTokenSource _cancellationSource; - // AA0044 - public static void Start(MainWindow window) + // AA0045 - Updated: Accept cancellation token + public static void Start(MainWindow window, CancellationToken cancellationToken = default) { _mainWindow = window; + _cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - // AA0045 - while (true) + try { - HandleToggleCapsLock(); - HandleNextSet(); - HandlePrevSet(); - HandleRefresh(); - HandleSwitchSide(); - Thread.Sleep(8); + while (!_cancellationSource.Token.IsCancellationRequested) + { + try + { + HandleToggleCapsLock(); + HandleNextSet(); + HandlePrevSet(); + HandleRefresh(); + HandleSwitchSide(); + HandleQuickSwaps(); + Thread.Sleep(8); + } + catch (Exception ex) + { + // Log error but continue running + System.Diagnostics.Debug.WriteLine($"HotkeyHandler error: {ex.Message}"); + } + } + } + finally + { + _cancellationSource?.Dispose(); } } + + // AA0046 - Static method to stop the handler gracefully + public static void Stop() + { + _cancellationSource?.Cancel(); + } - // AA0046 + // AA0047 private static void HandleToggleCapsLock() { bool capsDown = Utils.IsPressed(CAPSLOCK); @@ -42,7 +68,7 @@ namespace Lithium if (!capsDown) _capsHeld = false; } - // AA0047 + // AA0048 private static void HandleNextSet() { if (Utils.IsPressed(ConfigLoader.nextSetKey) && !_nextSetHeld) @@ -56,7 +82,7 @@ namespace Lithium } } - // AA0048 + // AA0049 private static void HandlePrevSet() { if (Utils.IsPressed(ConfigLoader.prevSetKey) && !_prevSetHeld) @@ -70,7 +96,7 @@ namespace Lithium } } - // AA0049 + // AA0050 private static void HandleRefresh() { if (Utils.IsPressed(ConfigLoader.refreshKey) && !_refreshHeld) @@ -84,7 +110,7 @@ namespace Lithium } } - // AA0050 + // AA0051 private static void HandleSwitchSide() { if (Utils.IsPressed(ConfigLoader.switchSideKey) && !_switchSideHeld) @@ -97,5 +123,74 @@ namespace Lithium _switchSideHeld = false; } } + + private static void HandleQuickSwaps() + { + if (ConfigLoader.quickSwaps == null || ConfigLoader.quickSwaps.Count == 0) return; + + // Find all bindings that are currently fully pressed + var activeBindings = new System.Collections.Generic.List(); + foreach (var qs in ConfigLoader.quickSwaps) + { + bool allPressed = true; + foreach (var k in qs.Keys) + { + if (!Lua.Utils.IsPressed(k)) + { + allPressed = false; + break; + } + } + if (allPressed) + { + activeBindings.Add(qs); + } + } + + // Filter out bindings that are subsets of other active bindings + var triggeredBindings = new System.Collections.Generic.List(); + foreach (var ab in activeBindings) + { + bool hasSuperset = false; + foreach (var other in activeBindings) + { + if (other != ab && other.Keys.Length > ab.Keys.Length) + { + bool isSubset = true; + foreach (var k in ab.Keys) + { + bool found = false; + foreach (var ok in other.Keys) + { + if (ok == k) { found = true; break; } + } + if (!found) { isSubset = false; break; } + } + if (isSubset) { hasSuperset = true; break; } + } + } + if (!hasSuperset) + { + triggeredBindings.Add(ab); + } + } + + // Update held status and trigger actions + foreach (var qs in ConfigLoader.quickSwaps) + { + if (triggeredBindings.Contains(qs)) + { + if (!qs.IsHeld) + { + qs.IsHeld = true; + _mainWindow.HandleQuickSwap(qs.TargetOperator); + } + } + else + { + qs.IsHeld = false; + } + } + } } } \ No newline at end of file diff --git a/Lithium/Lithium.sln b/Lithium/Lithium.sln new file mode 100644 index 0000000..9bb97d5 --- /dev/null +++ b/Lithium/Lithium.sln @@ -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}") = "Lithium", "Lithium.csproj", "{B20F7FF2-33B9-1A60-831A-DE88D5488463}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B20F7FF2-33B9-1A60-831A-DE88D5488463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B20F7FF2-33B9-1A60-831A-DE88D5488463}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B20F7FF2-33B9-1A60-831A-DE88D5488463}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B20F7FF2-33B9-1A60-831A-DE88D5488463}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {751AD755-CC15-4830-AF9B-8C42B731B22E} + EndGlobalSection +EndGlobal diff --git a/Lithium/MainWindow.xaml b/Lithium/MainWindow.xaml index 81010d9..1932cdb 100644 --- a/Lithium/MainWindow.xaml +++ b/Lithium/MainWindow.xaml @@ -1,4 +1,4 @@ - + + + + @@ -173,6 +193,7 @@ + @@ -267,7 +288,7 @@ - + @@ -289,8 +310,13 @@ + + + + + - + 0x01: Left Mouse, 0x02: Right Mouse, 0x04: Middle Mouse @@ -304,7 +330,7 @@ Full list: https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-c - + diff --git a/Lithium/MainWindow.xaml.cs b/Lithium/MainWindow.xaml.cs index d4e3dc3..5342af6 100644 --- a/Lithium/MainWindow.xaml.cs +++ b/Lithium/MainWindow.xaml.cs @@ -1,6 +1,7 @@ -using Lua; +using Lua; using System; using System.IO; +using System.Linq; using System.Net.Http; using System.Text; using System.Text.Json; @@ -23,6 +24,10 @@ namespace Lithium private DateTime nextSetAllowed = DateTime.MinValue; // AA0114 private DateTime prevSetAllowed = DateTime.MinValue; + + // Track threads for cleanup + private Thread _hotkeyThread; + private Thread _recoilThread; // AA0115 public MainWindow() @@ -45,18 +50,42 @@ namespace Lithium // AA0117 LoadSettingsToUI(); - StartThread(HotkeyLoop); - StartThread(RecoilLoop); + _hotkeyThread = StartThread(HotkeyLoop); + _recoilThread = StartThread(RecoilLoop); + + // Clean up on window close + Closing += MainWindow_Closing; + } + + // Handle window closing - dispose resources + private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + try + { + // Dispose speech synthesizer + Utils.Dispose(); + + // Stop hotkey handler gracefully + HotkeyHandler.Stop(); + + // Stop recoil engine gracefully + RecoilEngine.Stop(); + } + catch + { + // Ignore cleanup errors + } } // AA0118 - private void StartThread(ThreadStart action) + private Thread StartThread(ThreadStart action) { var t = new Thread(action) { IsBackground = true }; t.Start(); + return t; } // AA0119 @@ -68,6 +97,22 @@ namespace Lithium // ============================================================ // SETTINGS MANAGEMENT // ============================================================ + private Brush GetBrushFromConsoleColor(ConsoleColor color) + { + switch (color) + { + case ConsoleColor.White: return Brushes.White; + case ConsoleColor.Magenta: return Brushes.Magenta; + case ConsoleColor.Cyan: return Brushes.Cyan; + case ConsoleColor.Green: return Brushes.Lime; + case ConsoleColor.Yellow: return Brushes.Yellow; + case ConsoleColor.Red: return Brushes.Red; + case ConsoleColor.Blue: return Brushes.DeepSkyBlue; + case ConsoleColor.Gray: return Brushes.Gray; + default: return Brushes.White; + } + } + // AA0121 private void LoadSettingsToUI() { @@ -94,16 +139,27 @@ namespace Lithium FavoriteAttackersTextBox.Text = ConfigLoader.favoriteAttackers ?? ""; FavoriteDefendersTextBox.Text = ConfigLoader.favoriteDefenders ?? ""; - // AA0126 + var qsLines = new System.Collections.Generic.List(); + foreach (var qs in ConfigLoader.quickSwaps) + { + string keysStr = string.Join("+", qs.Keys.Select(k => $"0x{k:X2}")); + qsLines.Add($"{keysStr} => {qs.TargetOperator}"); + } + QuickSwapsTextBox.Text = string.Join(Environment.NewLine, qsLines); + + // AA0126 - Fixed: Properly match ConsoleColor enum values + string colorName = ConfigLoader.textColor.ToString(); foreach (ComboBoxItem item in TextColorComboBox.Items) { - if (item.Content.ToString() == ConfigLoader.textColor.ToString()) + if (item.Content.ToString().Equals(colorName, StringComparison.OrdinalIgnoreCase)) { TextColorComboBox.SelectedItem = item; break; } } + ConsoleOutput.Foreground = GetBrushFromConsoleColor(ConfigLoader.textColor); + SettingsStatusText.Text = "Settings loaded successfully"; SettingsStatusText.Foreground = new SolidColorBrush(Colors.Lime); } @@ -121,7 +177,15 @@ namespace Lithium try { string configContent = GenerateConfigContent(); - File.WriteAllText("init.config", configContent); + if (configContent == null) + { + SettingsStatusText.Text = "Error: Invalid configuration values"; + SettingsStatusText.Foreground = new SolidColorBrush(Colors.Red); + return; + } + + string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "init.config"); + File.WriteAllText(configPath, configContent); // AA0129 ConfigLoader.LoadInitConfig(); @@ -131,6 +195,16 @@ namespace Lithium SettingsStatusText.Foreground = new SolidColorBrush(Colors.Lime); AppendConsole("Settings updated from UI"); } + catch (UnauthorizedAccessException ex) + { + SettingsStatusText.Text = $"Access denied: {ex.Message}"; + SettingsStatusText.Foreground = new SolidColorBrush(Colors.Red); + } + catch (IOException ex) + { + SettingsStatusText.Text = $"IO Error: {ex.Message}"; + SettingsStatusText.Foreground = new SolidColorBrush(Colors.Red); + } catch (Exception ex) { // AA0130 @@ -168,6 +242,8 @@ namespace Lithium ConfigLoader.favoriteDefenders = null; ConfigLoader.textColor = System.ConsoleColor.Magenta; + QuickSwapsTextBox.Text = ""; + LoadSettingsToUI(); SettingsStatusText.Text = "Settings reset to defaults (save to apply)"; SettingsStatusText.Foreground = new SolidColorBrush(Colors.Yellow); @@ -176,42 +252,104 @@ namespace Lithium // AA0134 private string GenerateConfigContent() { - return $@"-- Lithium Configuration File --- Generated from UI on {DateTime.Now} + // Validate and parse jitter times + if (!int.TryParse(JitterBurstTimeTextBox.Text, out int jitterBurstTime)) + { + AppendConsole("Error: Invalid jitter burst time value"); + return null; + } + if (!int.TryParse(JitterCooldownTextBox.Text, out int jitterCooldown)) + { + AppendConsole("Error: Invalid jitter cooldown value"); + return null; + } --- Basic Settings -debitFlag1 = {DebitFlagCheckBox.IsChecked.ToString().ToLower()} -betaTest = {BetaTestCheckBox.IsChecked.ToString().ToLower()} -ttsEnabled = {TtsEnabledCheckBox.IsChecked.ToString().ToLower()} -jitterEnabled = {JitterEnabledCheckBox.IsChecked.ToString().ToLower()} + // Parse Quick Swaps from UI text box + ConfigLoader.quickSwaps.Clear(); + var textLines = QuickSwapsTextBox.Text.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var rawLine in textLines) + { + var line = rawLine.Trim(); + if (string.IsNullOrWhiteSpace(line) || line.StartsWith("--") || line.StartsWith("#")) continue; --- Key Bindings (Virtual Key Codes) -nextSetKey = {ParseKeyCode(NextSetKeyTextBox.Text)} -prevSetKey = {ParseKeyCode(PrevSetKeyTextBox.Text)} -refreshKey = {ParseKeyCode(RefreshKeyTextBox.Text)} -switchSideKey = {ParseKeyCode(SwitchSideKeyTextBox.Text)} + string parsedLine = line; + if (!line.StartsWith("quickSwap", StringComparison.OrdinalIgnoreCase)) + { + parsedLine = "quickSwap = " + line; + } --- Jitter Settings -jitterBurstTime = {int.Parse(JitterBurstTimeTextBox.Text)} -jitterCooldown = {int.Parse(JitterCooldownTextBox.Text)} + if (Helper.ParseQuickSwap(parsedLine, out var qsKeys, out var qsOp)) + { + ConfigLoader.quickSwaps.Add(new QuickSwapBinding { Keys = qsKeys, TargetOperator = qsOp }); + } + else + { + AppendConsole($"Warning: Invalid quick swap format: {line}"); + } + } --- User & Favorites -userName = ""{UserNameTextBox.Text}"" -favoriteAttackers = ""{FavoriteAttackersTextBox.Text}"" -favoriteDefenders = ""{FavoriteDefendersTextBox.Text}"" + var sb = new StringBuilder(); + sb.AppendLine("-- Lithium Configuration File"); + sb.AppendLine($"-- Generated from UI on {DateTime.Now}"); + sb.AppendLine(); + sb.AppendLine("-- Basic Settings"); + sb.AppendLine($"debitFlag1 = {DebitFlagCheckBox.IsChecked.ToString().ToLower()}"); + sb.AppendLine($"betaTest = {BetaTestCheckBox.IsChecked.ToString().ToLower()}"); + sb.AppendLine($"ttsEnabled = {TtsEnabledCheckBox.IsChecked.ToString().ToLower()}"); + sb.AppendLine($"jitterEnabled = {JitterEnabledCheckBox.IsChecked.ToString().ToLower()}"); + sb.AppendLine(); + sb.AppendLine("-- Key Bindings (Virtual Key Codes)"); + sb.AppendLine($"nextSetKey = {ParseKeyCode(NextSetKeyTextBox.Text)}"); + sb.AppendLine($"prevSetKey = {ParseKeyCode(PrevSetKeyTextBox.Text)}"); + sb.AppendLine($"refreshKey = {ParseKeyCode(RefreshKeyTextBox.Text)}"); + sb.AppendLine($"switchSideKey = {ParseKeyCode(SwitchSideKeyTextBox.Text)}"); + sb.AppendLine(); + sb.AppendLine("-- Jitter Settings"); + sb.AppendLine($"jitterBurstTime = {jitterBurstTime}"); + sb.AppendLine($"jitterCooldown = {jitterCooldown}"); + sb.AppendLine(); + sb.AppendLine("-- User & Favorites"); + sb.AppendLine($"userName = \"{UserNameTextBox.Text}\""); + sb.AppendLine($"favoriteAttackers = \"{FavoriteAttackersTextBox.Text}\""); + sb.AppendLine($"favoriteDefenders = \"{FavoriteDefendersTextBox.Text}\""); + sb.AppendLine(); + sb.AppendLine("-- Console Settings"); + sb.AppendLine($"textColor = {((ComboBoxItem)TextColorComboBox.SelectedItem)?.Content.ToString() ?? "Magenta"}"); --- Console Settings -textColor = {((ComboBoxItem)TextColorComboBox.SelectedItem)?.Content.ToString() ?? "Magenta"}"; + if (ConfigLoader.quickSwaps.Count > 0) + { + sb.AppendLine(); + sb.AppendLine("-- Quick Swap Bindings"); + foreach (var qs in ConfigLoader.quickSwaps) + { + string keysStr = string.Join("+", qs.Keys.Select(k => $"0x{k:X2}")); + sb.AppendLine($"quickSwap = {keysStr} => {qs.TargetOperator}"); + } + } + + return sb.ToString(); } // AA0135 private int ParseKeyCode(string text) { - if (text.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrWhiteSpace(text)) + return 0x05; // Default key + + try { - return Convert.ToInt32(text, 16); + if (text.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + return Convert.ToInt32(text, 16); + } + if (int.TryParse(text, out int value)) + return value; } - return int.Parse(text); + catch + { + // Return default on error + } + return 0x05; // Default key } // ============================================================ @@ -278,6 +416,14 @@ textColor = {((ComboBoxItem)TextColorComboBox.SelectedItem)?.Content.ToString() Utils.SafeSpeak(OperatorSets.CurrentSet); } + public void HandleQuickSwap(string targetOp) + { + OperatorSets.QuickSwapTo(targetOp); + + AppendConsole($"Quick Swap → {OperatorSets.CurrentSet}"); + Utils.SafeSpeak(OperatorSets.CurrentSet); + } + // ============================================================ // UI CONSOLE // ============================================================ @@ -362,16 +508,25 @@ textColor = {((ComboBoxItem)TextColorComboBox.SelectedItem)?.Content.ToString() { try { + string customSetPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "custom_set.txt"); File.WriteAllText( - "custom_set.txt", + customSetPath, $"{OperatorNameTextBox.Text}|{RecoilXTextBox.Text}|{RecoilYTextBox.Text}|{(AttackerRadioButton.IsChecked == true ? "attacker" : "defender")}" ); UpdateCustomSetConsole("Custom set saved"); } - catch + catch (UnauthorizedAccessException) { - UpdateCustomSetConsole("Failed to save custom set"); + UpdateCustomSetConsole("Error: Access denied - check file permissions"); + } + catch (IOException ex) + { + UpdateCustomSetConsole($"IO Error: {ex.Message}"); + } + catch (Exception ex) + { + UpdateCustomSetConsole($"Failed to save custom set: {ex.Message}"); } } @@ -380,15 +535,17 @@ textColor = {((ComboBoxItem)TextColorComboBox.SelectedItem)?.Content.ToString() { try { - if (!File.Exists("custom_set.txt")) + string customSetPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "custom_set.txt"); + + if (!File.Exists(customSetPath)) { UpdateCustomSetConsole("No saved set found"); return; } - string[] parts = File.ReadAllText("custom_set.txt").Split('|'); + string[] parts = File.ReadAllText(customSetPath).Split('|'); if (parts.Length != 4) - throw new Exception("Invalid file"); + throw new Exception("Invalid file format"); OperatorNameTextBox.Text = parts[0]; RecoilXTextBox.Text = parts[1]; @@ -401,9 +558,17 @@ textColor = {((ComboBoxItem)TextColorComboBox.SelectedItem)?.Content.ToString() UpdateCustomSetConsole("Custom set loaded"); } - catch + catch (UnauthorizedAccessException) { - UpdateCustomSetConsole("Failed to load custom set"); + UpdateCustomSetConsole("Error: Access denied - check file permissions"); + } + catch (IOException ex) + { + UpdateCustomSetConsole($"IO Error: {ex.Message}"); + } + catch (Exception ex) + { + UpdateCustomSetConsole($"Failed to load custom set: {ex.Message}"); } } diff --git a/Lithium/OperatorSets.cs b/Lithium/OperatorSets.cs index 28015f5..d2b6955 100644 --- a/Lithium/OperatorSets.cs +++ b/Lithium/OperatorSets.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Threading.Tasks; namespace Lua { @@ -31,6 +32,15 @@ namespace Lua // AA0058 public static bool onAttackers = true; + public static string originalSetBeforeQuickSwap = null; + public static bool isQuickSwapping = false; + + // Static HttpClient for connection pooling (FIX: previously created new instance each time) + private static readonly HttpClient _httpClient = new HttpClient + { + Timeout = System.TimeSpan.FromSeconds(10) + }; + private static string CacheDirectory => System.IO.Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), @@ -108,46 +118,74 @@ namespace Lua var allLines = new List(); bool loadedOnline = false; - // Ensure coax folder exists - try { System.IO.Directory.CreateDirectory(CacheDirectory); } catch { } + // Ensure coax folder exists (with proper error handling) + try + { + if (!System.IO.Directory.Exists(CacheDirectory)) + System.IO.Directory.CreateDirectory(CacheDirectory); + } + catch + { + // Directory creation failed - will fall back to cache if available + } // Try loading from online source if (allowOnlineFetch && !string.IsNullOrEmpty(OnlineConfigUrl)) { try { - using (var client = new HttpClient()) + string txt = _httpClient.GetStringAsync(OnlineConfigUrl).Result; + + if (!string.IsNullOrWhiteSpace(txt)) { - string txt = client.GetStringAsync(OnlineConfigUrl).Result; + allLines.AddRange(txt.Split('\n')); - if (!string.IsNullOrWhiteSpace(txt)) + // Save cache file - with proper error handling + try { - allLines.AddRange(txt.Split('\n')); - - // Save cache file in per-user coax folder - System.IO.File.WriteAllText(CacheFile, txt); - - loadedOnline = true; + if (System.IO.Directory.Exists(CacheDirectory)) + System.IO.File.WriteAllText(CacheFile, txt); } + catch + { + // Cache write failed, but we have the data in memory + } + + loadedOnline = true; } } + catch (HttpRequestException) + { + // Network error - fall back to cache + } + catch (TaskCanceledException) + { + // Timeout - fall back to cache + } catch { - // Ignore; fallback handled below + // Other errors - fall back to cache } } // If online fetch failed → fallback to cached file if (!loadedOnline && System.IO.File.Exists(CacheFile)) { - var cached = System.IO.File.ReadAllLines(CacheFile); - if (cached.Length > 0) - allLines.AddRange(cached); + try + { + var cached = System.IO.File.ReadAllLines(CacheFile); + if (cached.Length > 0) + allLines.AddRange(cached); + } + catch + { + // Cache file read failed - continue without it + } } // Also load config.txt (local overrides) var local = Helper.ReadLinesSafe("config.txt"); - if (local != null && local.Length > 0) + if (local.Length > 0) allLines.AddRange(local); bool inAttackers = false; @@ -208,6 +246,7 @@ namespace Lua // AA0070 public static void MergeSets() { + if (!isQuickSwapping) originalSetBeforeQuickSwap = null; if (ConfigLoader.betaTest) { mergedSets.Clear(); @@ -236,6 +275,7 @@ namespace Lua // AA0071 public static void RestoreSet(string oldSet) { + if (!isQuickSwapping) originalSetBeforeQuickSwap = null; if (setNames.Count == 0) { currentSetIndex = 0; @@ -250,6 +290,7 @@ namespace Lua // AA0072 public static void SwitchSide() { + if (!isQuickSwapping) originalSetBeforeQuickSwap = null; if (ConfigLoader.betaTest) return; onAttackers = !onAttackers; @@ -260,6 +301,7 @@ namespace Lua // AA0073 public static void NextSet() { + if (!isQuickSwapping) originalSetBeforeQuickSwap = null; if (setNames.Count == 0) return; currentSetIndex = (currentSetIndex + 1) % setNames.Count; @@ -269,6 +311,7 @@ namespace Lua // AA0074 public static void PreviousSet() { + if (!isQuickSwapping) originalSetBeforeQuickSwap = null; if (setNames.Count == 0) return; currentSetIndex = (currentSetIndex - 1 + setNames.Count) % setNames.Count; @@ -278,7 +321,8 @@ namespace Lua // AA0075 public static void SetCurrentSet(string name) { - if (setNames == null || setNames.Count == 0) return; + if (!isQuickSwapping) originalSetBeforeQuickSwap = null; + if (setNames.Count == 0) return; int idx = setNames.FindIndex(s => string.Equals(s, name, StringComparison.OrdinalIgnoreCase)); if (idx >= 0) @@ -341,5 +385,74 @@ namespace Lua currentSetIndex = 0; CurrentOperator = CurrentSet; } + + public static void QuickSwapTo(string targetOp) + { + if (setNames == null || setNames.Count == 0) return; + + // Check if the target operator is valid + bool inAttackers = attackers.ContainsKey(targetOp); + bool inDefenders = defenders.ContainsKey(targetOp); + + if (!inAttackers && !inDefenders) + { + if (!mergedSets.ContainsKey(targetOp)) + { + return; // Invalid operator + } + } + + isQuickSwapping = true; + try + { + if (!ConfigLoader.betaTest) + { + if (inAttackers && !onAttackers) + { + SwitchSide(); + } + else if (inDefenders && onAttackers) + { + SwitchSide(); + } + } + + if (string.Equals(CurrentSet, targetOp, StringComparison.OrdinalIgnoreCase)) + { + if (!string.IsNullOrEmpty(originalSetBeforeQuickSwap)) + { + bool origInAttackers = attackers.ContainsKey(originalSetBeforeQuickSwap); + bool origInDefenders = defenders.ContainsKey(originalSetBeforeQuickSwap); + + if (!ConfigLoader.betaTest) + { + if (origInAttackers && !onAttackers) + { + SwitchSide(); + } + else if (origInDefenders && onAttackers) + { + SwitchSide(); + } + } + + SetCurrentSet(originalSetBeforeQuickSwap); + originalSetBeforeQuickSwap = null; + } + } + else + { + if (string.IsNullOrEmpty(originalSetBeforeQuickSwap)) + { + originalSetBeforeQuickSwap = CurrentSet; + } + SetCurrentSet(targetOp); + } + } + finally + { + isQuickSwapping = false; + } + } } } \ No newline at end of file diff --git a/Lithium/RecoilEngine.cs b/Lithium/RecoilEngine.cs index a32111e..8ae8da8 100644 --- a/Lithium/RecoilEngine.cs +++ b/Lithium/RecoilEngine.cs @@ -1,4 +1,4 @@ -using Lithium; +using Lithium; using System; using System.Runtime.InteropServices; using System.Threading; @@ -25,36 +25,55 @@ namespace Lua return (GetAsyncKeyState(key) & 0x8000) != 0; } - public static void Start(MainWindow window) - { - while (true) - { - if (window.IsRecoilEnabled && IsDown(VK_LBUTTON) && IsDown(VK_RBUTTON)) - { - HandleRecoilTick(); - } - else - { - _remainderX = _remainderY = 0; - _jitterActive = false; - } + private static CancellationTokenSource _cancellationSource; - Thread.Sleep(2); + public static void Start(MainWindow window, CancellationToken cancellationToken = default) + { + _cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + try + { + while (!_cancellationSource.Token.IsCancellationRequested) + { + // FIX: Perform key checks once and reuse the results + bool leftDown = IsDown(VK_LBUTTON); + bool rightDown = IsDown(VK_RBUTTON); + + if (window.IsRecoilEnabled && leftDown && rightDown) + { + HandleRecoilTick(); + } + else + { + _remainderX = _remainderY = 0; + _jitterActive = false; + } + + Thread.Sleep(2); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"RecoilEngine error: {ex.Message}"); } } private static void HandleRecoilTick() { - if (!IsDown(VK_LBUTTON)) return; + try + { + string setName = OperatorSets.CurrentSet; - string setName = OperatorSets.CurrentSet; + if (!OperatorSets.mergedSets.TryGetValue(setName, out var recoil)) return; - if (!OperatorSets.mergedSets.TryGetValue(setName, out var recoil)) return; + int now = Environment.TickCount; - int now = Environment.TickCount; - - HandleJitter(now); - ApplyRecoil(recoil); + HandleJitter(now); + ApplyRecoil(recoil); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"HandleRecoilTick error: {ex.Message}"); + } } private static void HandleJitter(int now) @@ -103,5 +122,15 @@ namespace Lua Utils.MoveMouse(ix, iy); } + + public static void Stop() + { + try + { + _cancellationSource?.Cancel(); + _cancellationSource?.Dispose(); + } + catch { } + } } } diff --git a/Lithium/Utils.cs b/Lithium/Utils.cs index b26176e..7b0d2ac 100644 --- a/Lithium/Utils.cs +++ b/Lithium/Utils.cs @@ -7,9 +7,29 @@ namespace Lua static class Utils { // AA0091 - public static readonly SpeechSynthesizer Tts = new SpeechSynthesizer(); + private static SpeechSynthesizer _tts; + public static SpeechSynthesizer Tts + { + get + { + if (_tts == null) + _tts = new SpeechSynthesizer(); + return _tts; + } + } + // AA0092 private static readonly object TtsLock = new object(); + + // Cleanup on application shutdown + public static void Dispose() + { + lock (TtsLock) + { + _tts?.Dispose(); + _tts = null; + } + } // AA0093 [DllImport("user32.dll")] diff --git a/Lithium/bin/Debug.7z b/Lithium/bin/Debug.7z new file mode 100644 index 0000000..5010cf1 Binary files /dev/null and b/Lithium/bin/Debug.7z differ diff --git a/Lithium/bin/gayLithium.7z b/Lithium/bin/gayLithium.7z deleted file mode 100644 index dead1cc..0000000 Binary files a/Lithium/bin/gayLithium.7z and /dev/null differ diff --git a/Lithium/helper.cs b/Lithium/helper.cs index cd55281..cafd6fa 100644 --- a/Lithium/helper.cs +++ b/Lithium/helper.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.IO; using System.Text.RegularExpressions; @@ -78,9 +78,9 @@ namespace Lua result = line.Substring(idx + 1).Trim(); - // AA0036 - if ((result.StartsWith("\"") && result.EndsWith("\"")) || - (result.StartsWith("'") && result.EndsWith("'"))) + // AA0036 - Fixed: Check length before substring to avoid negative index + if (result.Length >= 2 && ((result.StartsWith("\"") && result.EndsWith("\"")) || + (result.StartsWith("'") && result.EndsWith("'")))) { result = result.Substring(1, result.Length - 2); } @@ -124,5 +124,58 @@ namespace Lua if (idx >= size) return size - 1; return idx; } + + public static bool ParseQuickSwap(string line, out int[] keys, out string targetOperator) + { + keys = null; + targetOperator = null; + + var idx = line.IndexOf('='); + if (idx < 0) return false; + + var lhs = line.Substring(0, idx).Trim(); + if (!string.Equals(lhs, "quickSwap", StringComparison.OrdinalIgnoreCase)) return false; + + var rhs = line.Substring(idx + 1).Trim(); + var parts = rhs.Split(new[] { "=>" }, StringSplitOptions.None); + if (parts.Length != 2) return false; + + var keyPart = parts[0].Trim(); + var opPart = parts[1].Trim(); + + if (opPart.Length >= 2 && ((opPart.StartsWith("\"") && opPart.EndsWith("\"")) || + (opPart.StartsWith("'") && opPart.EndsWith("'")))) + { + opPart = opPart.Substring(1, opPart.Length - 2); + } + + targetOperator = opPart; + + var keyStrings = keyPart.Split('+'); + var keyList = new System.Collections.Generic.List(); + foreach (var kStr in keyStrings) + { + var trimmed = kStr.Trim(); + int vk = 0; + if (trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + try { vk = Convert.ToInt32(trimmed, 16); } catch { return false; } + } + else if (int.TryParse(trimmed, out int val)) + { + vk = val; + } + else + { + return false; + } + keyList.Add(vk); + } + + if (keyList.Count == 0) return false; + + keys = keyList.ToArray(); + return true; + } } } \ No newline at end of file