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