3 Commits

Author SHA1 Message Date
Trenten Pettry 60b519335f 🛠️ Fixed (Bugs resolved)
- Quick Swap Side Restoration: Fixed a bug where quickswap would fail to switch sides when toggling back to the original operator. The issue occurred because the baseline set was captured after the side had already switched, so the revert logic couldn't determine it needed to switch sides again. Now the baseline is captured before any side change, ensuring correct toggle behavior.
📂 File-Level Changes Summary
- OperatorSets.cs: Captured preSwitchSet before the SwitchSide() call in QuickSwapTo() and used it when storing originalSetBeforeQuickSwap.
2026-06-22 11:33:26 -04:00
Trenten Pettry 7d6bdc592f ## 🚀 Added (New Features)
• Quick Swap Feature:
      • Users can now bind keyboard keys (both single keys and multi-key combinations, e.g.,  Alt+1 ) to switch to
      specific operator sets.
      • Auto-toggle capability: Pressing the hotkey switches to the target operator set (automatically changing side
      if the operator is on standard Attackers/Defenders side). Pressing it again toggles back to the baseline
      operator set.
      • Added specificity filtering: Resolves overlapping hotkeys (e.g., pressing  Alt+1  will only trigger the  Alt+1
      binding, successfully ignoring the individual  1  key binding).
      • Added a dedicated Quick Swap Bindings editor inside the Settings panel to configure, save, and reload these
      bindings visually.

  ## ⚙️ Updated (Refactoring & Architecture)

  • Graceful Thread Shutdown: Updated RecoilEngine.cs to run with a  CancellationToken  linked to the main window
  lifespan, matching HotkeyHandler.cs and preventing background thread leaks when exiting.
  • State Management: Updated  OperatorSets  methods to protect the quick swap transition state, ensuring manual
  adjustments (clicks or next/prev hotkeys) reset the quick-swap toggle baseline.
  • Config Serialization: Updated  MainWindow  configuration generator to save new quick-swap parameters cleanly back
  to  init.config .

  ## 🛠️ Fixed (Bugs resolved)

  • TabControl/Menu Text Colors: Fixed the styling on MainWindow.xaml tab headers. Unselected and hover states now
  render in clear light greys, and active tabs in solid white, resolving dark-on-dark invisible text bugs.
  • Dropdown Selection Visibility: Changed the text foreground color of  TextColorComboBox  in MainWindow.xaml from
  black to white to make choices readable in dark mode.
  • UI Console Text Color: Bound the console setting ( textColor ) to the actual WPF GUI log ( ConsoleOutput.
  Foreground ), making the chosen color reflect in the application window.
  ──────
  ### 📂 File-Level Changes Summary

  • helper.cs: Added helper.cs to safely parse combinations.
  • ConfigLoader.cs: Added ConfigLoader.cs class and loading logic.
  • OperatorSets.cs: Added OperatorSets.cs and state-tracking flags.
  • HotkeyHandler.cs: Registered hotkey monitoring loops for multi-key patterns.
  • RecoilEngine.cs: Integrated thread lifecycle cancellation.
  • MainWindow.xaml: Styled menus, fixed dropdown contrast, and added Quick Swap bindings textbox.
  • MainWindow.xaml.cs: Connected UI controls to backend quick-swap actions and applied dynamic log coloring.
2026-06-15 03:55:58 -04:00
Trenten Pettry d6d8a60b20 gng 2026-04-28 15:29:42 -04:00
14 changed files with 706 additions and 123 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"dotnet.preferCSharpExtension": true
}
+1 -1
View File
@@ -1,7 +1,7 @@
<Application x:Class="Lithium.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
StartupUri="../MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
+13 -1
View File
@@ -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<QuickSwapBinding> quickSwaps = new System.Collections.Generic.List<QuickSwapBinding>();
// 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
+105 -10
View File
@@ -1,4 +1,5 @@
using Lua;
using Lua;
using System;
using System.Threading;
namespace Lithium
@@ -13,24 +14,49 @@ namespace Lithium
// AA0043
private static MainWindow _mainWindow;
// AA0044
public static void Start(MainWindow window)
// AA0044 - Added: Cancellation token for graceful shutdown
private static CancellationTokenSource _cancellationSource;
// AA0045 - Updated: Accept cancellation token
public static void Start(MainWindow window, CancellationToken cancellationToken = default)
{
_mainWindow = window;
_cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
// AA0045
while (true)
try
{
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
// AA0046 - Static method to stop the handler gracefully
public static void Stop()
{
_cancellationSource?.Cancel();
}
// 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<Lua.QuickSwapBinding>();
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<Lua.QuickSwapBinding>();
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;
}
}
}
}
}
+37 -1
View File
@@ -91,20 +91,56 @@
<Reference Include="System.Buffers, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Data" />
<Reference Include="System.IO, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.4.3.0\lib\net462\System.IO.dll</HintPath>
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.IO.Pipelines, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Pipelines.10.0.0\lib\net462\System.IO.Pipelines.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http, Version=4.1.1.3, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Net.Http.4.3.4\lib\net46\System.Net.Http.dll</HintPath>
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors, Version=4.1.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll</HintPath>
</Reference>
<Reference Include="System.Runtime, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll</HintPath>
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Algorithms, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll</HintPath>
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.Security.Cryptography.Encoding, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll</HintPath>
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.Security.Cryptography.Primitives, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll</HintPath>
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.Security.Cryptography.X509Certificates, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll</HintPath>
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.Speech" />
<Reference Include="System.Text.Encodings.Web, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Encodings.Web.10.0.0\lib\net462\System.Text.Encodings.Web.dll</HintPath>
@@ -120,7 +156,6 @@
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
@@ -171,6 +206,7 @@
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="A1\packages.config" />
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
+24
View File
@@ -0,0 +1,24 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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
+30 -4
View File
@@ -1,4 +1,4 @@
<Window x:Class="Lithium.MainWindow"
<Window x:Class="Lithium.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wv="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
@@ -7,6 +7,26 @@
WindowStyle="None"
AllowsTransparency="True">
<Window.Resources>
<Style TargetType="TabItem">
<Setter Property="Foreground" Value="LightGray"/>
<Setter Property="Background" Value="#252525"/>
<Setter Property="BorderBrush" Value="#444"/>
<Setter Property="Padding" Value="12,6"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#333333"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#2D2D2D"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<!-- Custom Title Bar -->
<Grid Height="35" VerticalAlignment="Top" MouseDown="TitleBar_MouseDown">
@@ -173,6 +193,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
@@ -267,7 +288,7 @@
<GroupBox Grid.Row="4" Grid.Column="0" Header="Console Settings" Foreground="White" Margin="5" BorderBrush="#444">
<StackPanel Margin="10">
<TextBlock Text="Text Color:" Foreground="White" Margin="0,5"/>
<ComboBox x:Name="TextColorComboBox" Width="120" HorizontalAlignment="Left" Background="#2D2D2D" Foreground="Black" BorderBrush="#444">
<ComboBox x:Name="TextColorComboBox" Width="120" HorizontalAlignment="Left" Background="#2D2D2D" Foreground="White" BorderBrush="#444">
<ComboBoxItem Content="White"/>
<ComboBoxItem Content="Magenta"/>
<ComboBoxItem Content="Cyan"/>
@@ -289,8 +310,13 @@
</StackPanel>
</GroupBox>
<!-- Quick Swap Bindings -->
<GroupBox Grid.Row="5" Grid.ColumnSpan="2" Header="Quick Swap Bindings (Format: Key1+Key2 => Operator Name)" Foreground="White" Margin="5" BorderBrush="#444">
<TextBox x:Name="QuickSwapsTextBox" AcceptsReturn="True" Height="80" VerticalScrollBarVisibility="Auto" Background="#2D2D2D" Foreground="White" BorderBrush="#444" FontFamily="Consolas" FontSize="11" Margin="10"/>
</GroupBox>
<!-- Key Code Reference -->
<GroupBox Grid.Row="5" Grid.ColumnSpan="2" Header="Common Virtual Key Codes" Foreground="White" Margin="5" BorderBrush="#444">
<GroupBox Grid.Row="6" Grid.ColumnSpan="2" Header="Common Virtual Key Codes" Foreground="White" Margin="5" BorderBrush="#444">
<ScrollViewer MaxHeight="120" VerticalScrollBarVisibility="Auto">
<TextBlock x:Name="KeyCodeReferenceText" Foreground="LightGray" FontFamily="Consolas" FontSize="10" Margin="10" TextWrapping="Wrap">
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
</GroupBox>
<!-- Status -->
<Border Grid.Row="6" Grid.ColumnSpan="2" Background="#2D2D2D" Margin="5" BorderBrush="#444" BorderThickness="1">
<Border Grid.Row="7" Grid.ColumnSpan="2" Background="#2D2D2D" Margin="5" BorderBrush="#444" BorderThickness="1">
<TextBlock x:Name="SettingsStatusText" Foreground="Lime" FontFamily="Consolas" FontSize="11" Margin="10,5" Text="Settings loaded successfully" TextWrapping="Wrap"/>
</Border>
+202 -37
View File
@@ -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;
@@ -24,6 +25,10 @@ namespace Lithium
// 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<string>();
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 (string.IsNullOrWhiteSpace(text))
return 0x05; // Default key
try
{
if (text.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
{
return Convert.ToInt32(text, 16);
}
return int.Parse(text);
if (int.TryParse(text, out int value))
return value;
}
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}");
}
}
+127 -12
View File
@@ -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),
@@ -45,13 +55,13 @@ namespace Lua
private static string OnlineConfigUrl =>
string.IsNullOrEmpty(ConfigLoader.userName)
? null
: $"https://jj.giggity.gay/api/config/export?name={Uri.EscapeDataString(ConfigLoader.userName)}";
: $"https://jj.scpsmp.site/api/config/export?name={Uri.EscapeDataString(ConfigLoader.userName)}";
// AA0060
public static string ConfigExportUrl =>
string.IsNullOrEmpty(ConfigLoader.userName)
? null
: $"https://jj.giggity.gay/exindex.html?name={Uri.EscapeDataString(ConfigLoader.userName)}";
: $"https://jj.scpsmp.site/exindex.html?name={Uri.EscapeDataString(ConfigLoader.userName)}";
// AA0061
public static string CurrentSet
@@ -108,46 +118,74 @@ namespace Lua
var allLines = new List<string>();
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 = client.GetStringAsync(OnlineConfigUrl).Result;
string txt = _httpClient.GetStringAsync(OnlineConfigUrl).Result;
if (!string.IsNullOrWhiteSpace(txt))
{
allLines.AddRange(txt.Split('\n'));
// Save cache file in per-user coax folder
// Save cache file - with proper error handling
try
{
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))
{
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,76 @@ 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
{
string preSwitchSet = CurrentSet;
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 = preSwitchSet;
}
SetCurrentSet(targetOp);
}
}
finally
{
isQuickSwapping = false;
}
}
}
}
+44 -20
View File
@@ -1,35 +1,44 @@
using Lithium;
using Lithium;
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace Lua
{
internal static class RecoilEngine
{
// AA0077
private const int VK_LBUTTON = 0x01;
// AA0078
private const int VK_RBUTTON = 0x02;
// AA0079
[DllImport("user32.dll")]
private static extern short GetAsyncKeyState(int vKey);
private static readonly Random _rng = new Random();
// AA0080
private static bool _jitterActive = false;
// AA0081
private static int _jitterStart = 0;
// AA0082
private static int _lastJitterEnd = 0;
// AA0083
private static double _remainderX, _remainderY;
// AA0084
private static double _jitterMultiplierX, _jitterMultiplierY;
// AA0085
public static void Start(MainWindow window)
private static bool IsDown(int key)
{
while (true)
return (GetAsyncKeyState(key) & 0x8000) != 0;
}
private static CancellationTokenSource _cancellationSource;
public static void Start(MainWindow window, CancellationToken cancellationToken = default)
{
if (window.IsRecoilEnabled && Utils.IsPressed(VK_LBUTTON) && Utils.IsPressed(VK_RBUTTON))
_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();
}
@@ -42,12 +51,16 @@ namespace Lua
Thread.Sleep(2);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"RecoilEngine error: {ex.Message}");
}
}
// AA0086
private static void HandleRecoilTick()
{
if (!Utils.IsPressed(VK_LBUTTON)) return;
try
{
string setName = OperatorSets.CurrentSet;
if (!OperatorSets.mergedSets.TryGetValue(setName, out var recoil)) return;
@@ -57,8 +70,12 @@ namespace Lua
HandleJitter(now);
ApplyRecoil(recoil);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"HandleRecoilTick error: {ex.Message}");
}
}
// AA0087
private static void HandleJitter(int now)
{
if (!ConfigLoader.jitterEnabled) return;
@@ -68,7 +85,6 @@ namespace Lua
_jitterActive = true;
_jitterStart = now;
// AA0088
_jitterMultiplierX = (_rng.NextDouble() - 0.5) * 0.2;
_jitterMultiplierY = (_rng.NextDouble() - 0.5) * 0.2;
}
@@ -81,7 +97,6 @@ namespace Lua
}
}
// AA0089
private static void ApplyRecoil((double recoilX, double recoilY) recoil)
{
double moveX = recoil.recoilX;
@@ -90,7 +105,6 @@ namespace Lua
moveX += recoil.recoilX * _jitterMultiplierX;
moveY += recoil.recoilY * _jitterMultiplierY;
// AA0090
if (ConfigLoader.debitFlag1)
{
moveX *= 1.282;
@@ -108,5 +122,15 @@ namespace Lua
Utils.MoveMouse(ix, iy);
}
public static void Stop()
{
try
{
_cancellationSource?.Cancel();
_cancellationSource?.Dispose();
}
catch { }
}
}
}
+21 -1
View File
@@ -7,10 +7,30 @@ 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")]
public static extern short GetAsyncKeyState(int vKey);
Binary file not shown.
+57 -4
View File
@@ -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<int>();
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;
}
}
}
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="System.IO" version="4.3.0" targetFramework="net48" />
<package id="System.Net.Http" version="4.3.4" targetFramework="net48" />
<package id="System.Runtime" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net48" />
</packages>