diff --git a/Lithium/HotkeyHandler.cs b/Lithium/HotkeyHandler.cs
new file mode 100644
index 0000000..b19be17
--- /dev/null
+++ b/Lithium/HotkeyHandler.cs
@@ -0,0 +1,80 @@
+using Lua;
+using System;
+using System.Threading;
+
+namespace Lithium
+{
+ internal static class HotkeyHandler
+ {
+ private const int CAPSLOCK = 0x14;
+
+ private static bool nextSetHeld, prevSetHeld, refreshHeld, switchSideHeld, capsHeld;
+ private static MainWindow mainWindow;
+
+ public static void Start(MainWindow window)
+ {
+ mainWindow = window;
+
+ while (true)
+ {
+ HandleToggleCapsLock();
+ HandleNextSet();
+ HandlePrevSet();
+ HandleRefresh();
+ HandleSwitchSide();
+ Thread.Sleep(8);
+ }
+ }
+
+ private static void HandleToggleCapsLock()
+ {
+ bool capsDown = Utils.IsPressed(CAPSLOCK);
+ if (capsDown && !capsHeld)
+ {
+ capsHeld = true;
+ mainWindow.SetRecoilEnabled(!mainWindow.IsRecoilEnabled);
+ }
+ if (!capsDown) capsHeld = false;
+ }
+
+ private static void HandleNextSet()
+ {
+ if (Utils.IsPressed(ConfigLoader.nextSetKey) && !nextSetHeld)
+ {
+ nextSetHeld = true;
+ mainWindow.HandleNextSet();
+ }
+ else if (!Utils.IsPressed(ConfigLoader.nextSetKey)) nextSetHeld = false;
+ }
+
+ private static void HandlePrevSet()
+ {
+ if (Utils.IsPressed(ConfigLoader.prevSetKey) && !prevSetHeld)
+ {
+ prevSetHeld = true;
+ mainWindow.HandlePrevSet();
+ }
+ else if (!Utils.IsPressed(ConfigLoader.prevSetKey)) prevSetHeld = false;
+ }
+
+ private static void HandleRefresh()
+ {
+ if (Utils.IsPressed(ConfigLoader.refreshKey) && !refreshHeld)
+ {
+ refreshHeld = true;
+ mainWindow.HandleRefresh();
+ }
+ else if (!Utils.IsPressed(ConfigLoader.refreshKey)) refreshHeld = false;
+ }
+
+ private static void HandleSwitchSide()
+ {
+ if (Utils.IsPressed(ConfigLoader.switchSideKey) && !switchSideHeld)
+ {
+ switchSideHeld = true;
+ mainWindow.HandleSwitchSide();
+ }
+ else if (!Utils.IsPressed(ConfigLoader.switchSideKey)) switchSideHeld = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Lithium/Lithium.csproj b/Lithium/Lithium.csproj
index 16400eb..37434ce 100644
--- a/Lithium/Lithium.csproj
+++ b/Lithium/Lithium.csproj
@@ -113,7 +113,9 @@
MSBuild:Compile
Designer
+
+
MSBuild:Compile
@@ -149,7 +151,6 @@
ResXFileCodeGenerator
Resources.Designer.cs
-
SettingsSingleFileGenerator
diff --git a/Lithium/MainWindow.xaml b/Lithium/MainWindow.xaml
index 213e22f..a6a334b 100644
--- a/Lithium/MainWindow.xaml
+++ b/Lithium/MainWindow.xaml
@@ -2,7 +2,7 @@
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"
- Title="Lithium" Height="570" Width="900"
+ Title="Lithium" Height="650" Width="1000"
Background="#1E1E1E"
WindowStyle="None"
AllowsTransparency="True">
@@ -110,6 +110,8 @@
+
+
@@ -130,34 +132,187 @@
-
+
-
+
-
+
-
-
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0x01: Left Mouse, 0x02: Right Mouse, 0x04: Middle Mouse
+0x05: XButton1, 0x06: XButton2
+0x20: Space, 0x25: Left, 0x26: Up, 0x27: Right, 0x28: Down
+0x30-0x39: 0-9, 0x41-0x5A: A-Z
+0x70-0x87: F1-F24
+Full list: https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/Lithium/MainWindow.xaml.cs b/Lithium/MainWindow.xaml.cs
index 229d1dd..6988a66 100644
--- a/Lithium/MainWindow.xaml.cs
+++ b/Lithium/MainWindow.xaml.cs
@@ -6,165 +6,255 @@ using System.Text;
using System.Text.Json;
using System.Threading;
using System.Windows;
+using System.Windows.Controls;
using System.Windows.Input;
+using System.Windows.Media;
namespace Lithium
{
public partial class MainWindow : Window
{
private bool customSetEnabled = false;
- private bool nextSetHeld, prevSetHeld, refreshHeld, switchSideHeld, capsHeld;
private bool recoilEnabled = true;
private DateTime nextSetAllowed = DateTime.MinValue;
private DateTime prevSetAllowed = DateTime.MinValue;
- private double remX = 0, remY = 0;
-
public MainWindow()
{
InitializeComponent();
- AppendConsole($"Initializing Lithium...");
+ AppendConsole("Initializing Lithium...");
ConfigLoader.LoadInitConfig();
OperatorSets.LoadOperatorSets();
- AppendConsole($"Loaded Operators | Current Set: {OperatorSets.CurrentSet} | Side: {(OperatorSets.onAttackers ? "Attackers" : "Defenders")}");
+
+ AppendConsole(
+ $"Loaded Operators | Current Set: {OperatorSets.CurrentSet} | Side: {(OperatorSets.onAttackers ? "Attackers" : "Defenders")}"
+ );
Browser.Source = !string.IsNullOrEmpty(OperatorSets.ConfigExportUrl)
? new Uri(OperatorSets.ConfigExportUrl)
: new Uri("https://jj.giggity.gay/exindex.html");
+ // Load settings into UI
+ LoadSettingsToUI();
+
StartThread(HotkeyLoop);
StartThread(RecoilLoop);
}
private void StartThread(ThreadStart action)
{
- var thread = new Thread(action) { IsBackground = true };
- thread.Start();
+ var t = new Thread(action)
+ {
+ IsBackground = true
+ };
+ t.Start();
}
- // ============================================================
- // RECOIL ENGINE (fractional X/Y tracking for precise Ricoh-style movement)
- // ============================================================
- private void RecoilLoop()
- {
- const int RIGHT_MOUSE = 0x02;
+ private void RecoilLoop() => RecoilEngine.Start();
- while (true)
+ private void HotkeyLoop() => HotkeyHandler.Start(this);
+
+ // ============================================================
+ // SETTINGS MANAGEMENT
+ // ============================================================
+ private void LoadSettingsToUI()
+ {
+ try
{
- if (!recoilEnabled)
+ // Basic settings
+ DebitFlagCheckBox.IsChecked = ConfigLoader.debitFlag1;
+ BetaTestCheckBox.IsChecked = ConfigLoader.betaTest;
+ TtsEnabledCheckBox.IsChecked = ConfigLoader.ttsEnabled;
+ JitterEnabledCheckBox.IsChecked = ConfigLoader.jitterEnabled;
+
+ // Key bindings
+ NextSetKeyTextBox.Text = $"0x{ConfigLoader.nextSetKey:X2}";
+ PrevSetKeyTextBox.Text = $"0x{ConfigLoader.prevSetKey:X2}";
+ RefreshKeyTextBox.Text = $"0x{ConfigLoader.refreshKey:X2}";
+ SwitchSideKeyTextBox.Text = $"0x{ConfigLoader.switchSideKey:X2}";
+
+ // Jitter settings
+ JitterBurstTimeTextBox.Text = ConfigLoader.jitterBurstTime.ToString();
+ JitterCooldownTextBox.Text = ConfigLoader.jitterCooldown.ToString();
+
+ // User & favorites
+ UserNameTextBox.Text = ConfigLoader.userName ?? "";
+ FavoriteAttackersTextBox.Text = ConfigLoader.favoriteAttackers ?? "";
+ FavoriteDefendersTextBox.Text = ConfigLoader.favoriteDefenders ?? "";
+
+ // Console color
+ foreach (ComboBoxItem item in TextColorComboBox.Items)
{
- Thread.Sleep(1);
- continue;
+ if (item.Content.ToString() == ConfigLoader.textColor.ToString())
+ {
+ TextColorComboBox.SelectedItem = item;
+ break;
+ }
}
- if (Utils.IsPressed(ConfigLoader.shootKey) && Utils.IsPressed(RIGHT_MOUSE))
- {
- var recoil = OperatorSets.GetCurrentRecoil();
-
- double moveX = recoil.recoilX + remX;
- double moveY = recoil.recoilY + remY;
-
- int ix = (int)Math.Round(moveX);
- int iy = (int)Math.Round(moveY);
-
- remX = moveX - ix;
- remY = moveY - iy;
-
- Utils.MoveMouse(ix, iy);
- }
-
- Thread.Sleep(1);
+ SettingsStatusText.Text = "Settings loaded successfully";
+ SettingsStatusText.Foreground = new SolidColorBrush(Colors.Lime);
}
+ catch (Exception ex)
+ {
+ SettingsStatusText.Text = $"Error loading settings: {ex.Message}";
+ SettingsStatusText.Foreground = new SolidColorBrush(Colors.Red);
+ }
+ }
+
+ private void SaveSettings_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ string configContent = GenerateConfigContent();
+ File.WriteAllText("init.config", configContent);
+
+ // Reload configuration
+ ConfigLoader.LoadInitConfig();
+ LoadSettingsToUI();
+
+ SettingsStatusText.Text = "Settings saved and reloaded successfully!";
+ SettingsStatusText.Foreground = new SolidColorBrush(Colors.Lime);
+ AppendConsole("Settings updated from UI");
+ }
+ catch (Exception ex)
+ {
+ SettingsStatusText.Text = $"Error saving settings: {ex.Message}";
+ SettingsStatusText.Foreground = new SolidColorBrush(Colors.Red);
+ }
+ }
+
+ private void ReloadSettings_Click(object sender, RoutedEventArgs e)
+ {
+ ConfigLoader.LoadInitConfig();
+ LoadSettingsToUI();
+ SettingsStatusText.Text = "Settings reloaded from file";
+ SettingsStatusText.Foreground = new SolidColorBrush(Colors.Lime);
+ AppendConsole("Settings reloaded from init.config");
+ }
+
+ private void ResetSettings_Click(object sender, RoutedEventArgs e)
+ {
+ // Reset to defaults
+ ConfigLoader.debitFlag1 = false;
+ ConfigLoader.betaTest = false;
+ ConfigLoader.ttsEnabled = true;
+ ConfigLoader.jitterEnabled = false;
+ ConfigLoader.nextSetKey = 0x05;
+ ConfigLoader.prevSetKey = 0x06;
+ ConfigLoader.refreshKey = 0x50;
+ ConfigLoader.switchSideKey = 0x4D;
+ ConfigLoader.jitterBurstTime = 1800;
+ ConfigLoader.jitterCooldown = 3000;
+ ConfigLoader.userName = null;
+ ConfigLoader.favoriteAttackers = null;
+ ConfigLoader.favoriteDefenders = null;
+ ConfigLoader.textColor = System.ConsoleColor.Magenta;
+
+ LoadSettingsToUI();
+ SettingsStatusText.Text = "Settings reset to defaults (save to apply)";
+ SettingsStatusText.Foreground = new SolidColorBrush(Colors.Yellow);
+ }
+
+ private string GenerateConfigContent()
+ {
+ return $@"-- Lithium Configuration File
+-- Generated from UI on {DateTime.Now}
+
+-- Basic Settings
+debitFlag1 = {DebitFlagCheckBox.IsChecked.ToString().ToLower()}
+betaTest = {BetaTestCheckBox.IsChecked.ToString().ToLower()}
+ttsEnabled = {TtsEnabledCheckBox.IsChecked.ToString().ToLower()}
+jitterEnabled = {JitterEnabledCheckBox.IsChecked.ToString().ToLower()}
+
+-- Key Bindings (Virtual Key Codes)
+nextSetKey = {ParseKeyCode(NextSetKeyTextBox.Text)}
+prevSetKey = {ParseKeyCode(PrevSetKeyTextBox.Text)}
+refreshKey = {ParseKeyCode(RefreshKeyTextBox.Text)}
+switchSideKey = {ParseKeyCode(SwitchSideKeyTextBox.Text)}
+
+-- Jitter Settings
+jitterBurstTime = {int.Parse(JitterBurstTimeTextBox.Text)}
+jitterCooldown = {int.Parse(JitterCooldownTextBox.Text)}
+
+-- User & Favorites
+userName = ""{UserNameTextBox.Text}""
+favoriteAttackers = ""{FavoriteAttackersTextBox.Text}""
+favoriteDefenders = ""{FavoriteDefendersTextBox.Text}""
+
+-- Console Settings
+textColor = {((ComboBoxItem)TextColorComboBox.SelectedItem)?.Content.ToString() ?? "Magenta"}";
+ }
+
+ private int ParseKeyCode(string text)
+ {
+ if (text.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
+ {
+ return Convert.ToInt32(text, 16);
+ }
+ return int.Parse(text);
}
// ============================================================
- // HOTKEY LOOP
+ // HANDLERS CALLED BY HOTKEYHANDLER
// ============================================================
- private void HotkeyLoop()
+ public void SetRecoilEnabled(bool enabled)
{
- const int CAPSLOCK = 0x14;
-
- while (true)
- {
- HandleToggleCapsLock(CAPSLOCK);
- HandleNextSet();
- HandlePrevSet();
- HandleRefresh();
- HandleSwitchSide();
- Thread.Sleep(8);
- }
+ recoilEnabled = enabled;
+ AppendConsole(enabled ? "Recoil Enabled" : "Recoil Disabled");
}
- private void HandleToggleCapsLock(int capsKey)
+ public bool IsRecoilEnabled => recoilEnabled;
+
+ public void HandleNextSet()
{
- bool capsDown = Utils.IsPressed(capsKey);
- if (capsDown && !capsHeld)
- {
- capsHeld = true;
- recoilEnabled = !recoilEnabled;
- AppendConsole(recoilEnabled ? "Recoil Enabled" : "Recoil Disabled");
- }
- if (!capsDown) capsHeld = false;
+ if (DateTime.Now < nextSetAllowed) return;
+
+ nextSetAllowed = DateTime.Now.AddMilliseconds(200);
+ OperatorSets.NextSet();
+
+ AppendConsole($"Next Set → {OperatorSets.CurrentSet}");
+ Utils.SafeSpeak(OperatorSets.CurrentSet);
}
- private void HandleNextSet()
+ public void HandlePrevSet()
{
- if (Utils.IsPressed(ConfigLoader.nextSetKey) && !nextSetHeld && DateTime.Now >= nextSetAllowed)
- {
- nextSetHeld = true;
- nextSetAllowed = DateTime.Now.AddMilliseconds(200);
- OperatorSets.NextSet();
- // Only announce operator, no side
- AppendConsole($"Next Set → {OperatorSets.CurrentSet}");
- Utils.SafeSpeak(OperatorSets.CurrentSet);
- }
- else if (!Utils.IsPressed(ConfigLoader.nextSetKey)) nextSetHeld = false;
+ if (DateTime.Now < prevSetAllowed) return;
+
+ prevSetAllowed = DateTime.Now.AddMilliseconds(200);
+ OperatorSets.PreviousSet();
+
+ AppendConsole($"Previous Set → {OperatorSets.CurrentSet}");
+ Utils.SafeSpeak(OperatorSets.CurrentSet);
}
- private void HandlePrevSet()
+ public void HandleRefresh()
{
- if (Utils.IsPressed(ConfigLoader.prevSetKey) && !prevSetHeld && DateTime.Now >= prevSetAllowed)
- {
- prevSetHeld = true;
- prevSetAllowed = DateTime.Now.AddMilliseconds(200);
- OperatorSets.PreviousSet();
- // Only announce operator, no side
- AppendConsole($"Previous Set → {OperatorSets.CurrentSet}");
- Utils.SafeSpeak(OperatorSets.CurrentSet);
- }
- else if (!Utils.IsPressed(ConfigLoader.prevSetKey)) prevSetHeld = false;
+ string oldSet = OperatorSets.CurrentSet;
+ bool oldSide = OperatorSets.onAttackers;
+
+ OperatorSets.LoadOperatorSets();
+
+ OperatorSets.SetCurrentSet(oldSet);
+ OperatorSets.onAttackers = oldSide;
+
+ AppendConsole(
+ $"Reloaded config | Current Set: {OperatorSets.CurrentSet} | Side: {(OperatorSets.onAttackers ? "Attackers" : "Defenders")}"
+ );
+
+ Utils.SafeSpeak(OperatorSets.CurrentSet);
}
- private void HandleRefresh()
+ public void HandleSwitchSide()
{
- if (Utils.IsPressed(ConfigLoader.refreshKey) && !refreshHeld)
- {
- refreshHeld = true;
- string current = OperatorSets.CurrentSet; // store current
- OperatorSets.LoadOperatorSets();
- OperatorSets.SetCurrentSet(current); // restore operator after refresh
- AppendConsole($"Reloaded config | Current Set: {OperatorSets.CurrentSet} | Side: {(OperatorSets.onAttackers ? "Attackers" : "Defenders")}");
- Utils.SafeSpeak(OperatorSets.CurrentSet);
- }
- else if (!Utils.IsPressed(ConfigLoader.refreshKey)) refreshHeld = false;
- }
+ OperatorSets.SwitchSide();
- private void HandleSwitchSide()
- {
- if (Utils.IsPressed(ConfigLoader.switchSideKey) && !switchSideHeld)
- {
- switchSideHeld = true;
- OperatorSets.SwitchSide();
- // Announce operator + side only on side switch
- AppendConsole($"Switched side → {(OperatorSets.onAttackers ? "Attackers" : "Defenders")} | Current Set: {OperatorSets.CurrentSet}");
- Utils.SafeSpeak(OperatorSets.CurrentSet);
- }
- else if (!Utils.IsPressed(ConfigLoader.switchSideKey)) switchSideHeld = false;
+ AppendConsole($"Switched side → {(OperatorSets.onAttackers ? "Attackers" : "Defenders")} | Current Set: {OperatorSets.CurrentSet}");
+ Utils.SafeSpeak(OperatorSets.CurrentSet);
}
-
// ============================================================
// UI CONSOLE
// ============================================================
@@ -174,6 +264,7 @@ namespace Lithium
{
ConsoleOutput.Text += msg + Environment.NewLine;
ConsoleScroll.ScrollToBottom();
+ Utils.AutoClearIfNearBottom();
});
}
@@ -202,25 +293,40 @@ namespace Lithium
double.TryParse(RecoilXTextBox.Text, out double x);
double.TryParse(RecoilYTextBox.Text, out double y);
+
string type = AttackerRadioButton.IsChecked == true ? "attackers" : "defenders";
- var payload = new { type, name, recoilX = x, recoilY = y };
+ var payload = new
+ {
+ type,
+ name,
+ recoilX = x,
+ recoilY = y
+ };
try
{
using HttpClient client = new HttpClient();
string json = JsonSerializer.Serialize(payload);
- var response = await client.PostAsync("https://jj.giggity.gay/api/global/add",
- new StringContent(json, Encoding.UTF8, "application/json"));
+
+ var response = await client.PostAsync(
+ "https://jj.giggity.gay/api/global/add",
+ new StringContent(json, Encoding.UTF8, "application/json")
+ );
if (response.IsSuccessStatusCode)
- UpdateCustomSetConsole($"Successfully published: {name} ({type}) X:{x} Y:{y}");
+ {
+ UpdateCustomSetConsole($"Published: {name} ({type}) X:{x} Y:{y}");
+ }
else
- UpdateCustomSetConsole($"Failed: {response.StatusCode} - {await response.Content.ReadAsStringAsync()}");
+ {
+ string body = await response.Content.ReadAsStringAsync();
+ UpdateCustomSetConsole($"HTTP Error {response.StatusCode}: {body}");
+ }
}
catch (Exception ex)
{
- UpdateCustomSetConsole("Error publishing set: " + ex.Message);
+ UpdateCustomSetConsole("Error: " + ex.Message);
}
}
@@ -228,11 +334,17 @@ namespace Lithium
{
try
{
- File.WriteAllText("custom_set.txt",
- $"{OperatorNameTextBox.Text}|{RecoilXTextBox.Text}|{RecoilYTextBox.Text}|{(AttackerRadioButton.IsChecked == true ? "attacker" : "defender")}");
+ File.WriteAllText(
+ "custom_set.txt",
+ $"{OperatorNameTextBox.Text}|{RecoilXTextBox.Text}|{RecoilYTextBox.Text}|{(AttackerRadioButton.IsChecked == true ? "attacker" : "defender")}"
+ );
+
UpdateCustomSetConsole("Custom set saved");
}
- catch { UpdateCustomSetConsole("Failed to save custom set"); }
+ catch
+ {
+ UpdateCustomSetConsole("Failed to save custom set");
+ }
}
private void LoadSet_Click(object sender, RoutedEventArgs e)
@@ -246,11 +358,13 @@ namespace Lithium
}
string[] parts = File.ReadAllText("custom_set.txt").Split('|');
- if (parts.Length != 4) throw new Exception("Invalid file");
+ if (parts.Length != 4)
+ throw new Exception("Invalid file");
OperatorNameTextBox.Text = parts[0];
RecoilXTextBox.Text = parts[1];
RecoilYTextBox.Text = parts[2];
+
if (parts[3].Equals("attacker", StringComparison.OrdinalIgnoreCase))
AttackerRadioButton.IsChecked = true;
else
@@ -258,21 +372,27 @@ namespace Lithium
UpdateCustomSetConsole("Custom set loaded");
}
- catch { UpdateCustomSetConsole("Failed to load custom set"); }
+ catch
+ {
+ UpdateCustomSetConsole("Failed to load custom set");
+ }
}
+ // ============================================================
+ // WINDOW CONTROLS
+ // ============================================================
private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
{
- if (e.ChangedButton == MouseButton.Left) DragMove();
+ if (e.ChangedButton == MouseButton.Left)
+ DragMove();
}
- private void Minimize_Click(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized;
+ private void Minimize_Click(object sender, RoutedEventArgs e) =>
+ WindowState = WindowState.Minimized;
- private void MaximizeRestore_Click(object sender, RoutedEventArgs e)
- {
+ private void MaximizeRestore_Click(object sender, RoutedEventArgs e) =>
WindowState = WindowState == WindowState.Normal ? WindowState.Maximized : WindowState.Normal;
- }
private void Close_Click(object sender, RoutedEventArgs e) => Close();
}
-}
+}
\ No newline at end of file
diff --git a/Lithium/RecoilEngine.cs b/Lithium/RecoilEngine.cs
new file mode 100644
index 0000000..d8b4d97
--- /dev/null
+++ b/Lithium/RecoilEngine.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Threading;
+
+namespace Lua
+{
+ internal static class RecoilEngine
+ {
+ private const int VK_LBUTTON = 0x01;
+ private const int VK_RBUTTON = 0x02;
+
+ private static Random rng = new Random();
+ private static bool jitterActive = false;
+ private static int jitterStart = 0;
+ private static int lastJitterEnd = 0;
+ private static double remX, remY;
+ private static double jitterMultX, jitterMultY;
+
+ public static void Start()
+ {
+ while (true)
+ {
+ if (Utils.IsCapsLockOn() && Utils.IsPressed(VK_RBUTTON))
+ {
+ HandleRecoilTick();
+ }
+ else
+ {
+ remX = remY = 0;
+ jitterActive = false;
+ }
+ Thread.Sleep(2);
+ }
+ }
+
+ private static void HandleRecoilTick()
+ {
+ if (!Utils.IsPressed(VK_LBUTTON))
+ return;
+
+ string setName = OperatorSets.CurrentSet;
+
+ if (!OperatorSets.mergedSets.TryGetValue(setName, out var recoil))
+ return;
+
+ int now = Environment.TickCount;
+
+ HandleJitter(now);
+ ApplyRecoil(recoil, now);
+ }
+
+ private static void HandleJitter(int now)
+ {
+ if (ConfigLoader.jitterEnabled)
+ {
+ if (!jitterActive && now - lastJitterEnd >= ConfigLoader.jitterCooldown)
+ {
+ jitterActive = true;
+ jitterStart = now;
+
+ double strength = 0.1 + rng.NextDouble() * 0.8;
+ jitterMultX = jitterMultY = 0;
+
+ switch (rng.Next(1, 5))
+ {
+ case 1: jitterMultX = -strength; break;
+ case 2: jitterMultX = strength; break;
+ case 3: jitterMultY = -strength; break;
+ case 4: jitterMultY = strength; break;
+ }
+ }
+
+ if (jitterActive && now - jitterStart >= ConfigLoader.jitterBurstTime)
+ {
+ jitterActive = false;
+ lastJitterEnd = now;
+ jitterMultX = jitterMultY = 0;
+ }
+ }
+ }
+
+ private static void ApplyRecoil((double recoilX, double recoilY) recoil, int now)
+ {
+ double jitterX = recoil.recoilX * jitterMultX;
+ double jitterY = recoil.recoilY * jitterMultY;
+
+ double moveX = recoil.recoilX + remX + jitterX;
+ double moveY = recoil.recoilY + remY + jitterY;
+
+ if (ConfigLoader.debitFlag1)
+ {
+ moveX *= 1.282;
+ moveY *= 1.351;
+ }
+
+ int ix = (int)Math.Round(moveX);
+ int iy = (int)Math.Round(moveY);
+
+ remX = moveX - ix;
+ remY = moveY - iy;
+
+ Utils.MoveMouse(ix, iy);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Lithium/Utils.cs b/Lithium/Utils.cs
index e4a065c..ac1a93d 100644
--- a/Lithium/Utils.cs
+++ b/Lithium/Utils.cs
@@ -1,5 +1,4 @@
-// Utils.cs (patched)
-using System;
+using System;
using System.Runtime.InteropServices;
using System.Speech.Synthesis;
using System.Threading;
@@ -16,8 +15,8 @@ namespace Lua
[DllImport("user32.dll")] public static extern void mouse_event(uint dwFlags, int dx, int dy, uint dwData, UIntPtr dwExtraInfo);
public const uint MOUSEEVENTF_MOVE = 0x0001;
- const int VK_CAPITAL = 0x14;
- static bool previousCapsState;
+ private const int VK_CAPITAL = 0x14;
+ private static bool previousCapsState;
public static void InitializeCapsState()
{
@@ -73,15 +72,12 @@ namespace Lua
: $"🚀 Standard Mode Enabled | Current Set: {OperatorSets.CurrentSet}";
}
- // -----------------------------------------------------
- // NEW: Auto console clear when near bottom
- // -----------------------------------------------------
public static void AutoClearIfNearBottom()
{
try
{
int current = Console.CursorTop;
- int limit = Console.WindowHeight - 2; // keep 2-row buffer
+ int limit = Console.WindowHeight - 2;
if (current >= limit)
{