diff --git a/Lithium.sln b/Lithium.sln
new file mode 100644
index 0000000..bea27fd
--- /dev/null
+++ b/Lithium.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.36408.4 d17.14
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lithium", "Lithium\Lithium.csproj", "{71516142-7D3E-46A1-A6EC-E242CD128DB1}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {71516142-7D3E-46A1-A6EC-E242CD128DB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {71516142-7D3E-46A1-A6EC-E242CD128DB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {71516142-7D3E-46A1-A6EC-E242CD128DB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {71516142-7D3E-46A1-A6EC-E242CD128DB1}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {CADAD289-56CB-41B4-8D5F-F334BCF9CABB}
+ EndGlobalSection
+EndGlobal
diff --git a/Lithium/App.config b/Lithium/App.config
new file mode 100644
index 0000000..193aecc
--- /dev/null
+++ b/Lithium/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Lithium/App.xaml b/Lithium/App.xaml
new file mode 100644
index 0000000..d00be54
--- /dev/null
+++ b/Lithium/App.xaml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/Lithium/App.xaml.cs b/Lithium/App.xaml.cs
new file mode 100644
index 0000000..554d883
--- /dev/null
+++ b/Lithium/App.xaml.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace Lithium
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ }
+}
diff --git a/Lithium/ConfigLoader.cs b/Lithium/ConfigLoader.cs
new file mode 100644
index 0000000..ec349da
--- /dev/null
+++ b/Lithium/ConfigLoader.cs
@@ -0,0 +1,112 @@
+// ConfigLoader.cs
+using System;
+using System.Globalization;
+using System.IO;
+using System.Runtime.Remoting.Messaging;
+
+namespace Lua
+{
+ internal static class ConfigLoader
+ {
+ // Basic flags / settings
+ public static bool debitFlag1 = false;
+ public static bool betaTest = false;
+ public static bool ttsEnabled = true;
+
+ // Key codes (default values)
+ public static int nextSetKey = 0x05; // XBUTTON1
+ public static int prevSetKey = 0x06; // XBUTTON2
+ public static int refreshKey = 0x50; // P
+ public static int switchSideKey = 0x4D; // M
+
+ // Favorites and user
+ public static string favoriteAttackers = null;
+ public static string favoriteDefenders = null;
+ public static string userName = null;
+
+ // Jitter config
+ public static bool jitterEnabled = false;
+ public static int jitterBurstTime = 1800; // milliseconds
+ public static int jitterCooldown = 3000; // milliseconds
+
+ // Console text color
+ public static ConsoleColor textColor = ConsoleColor.Magenta;
+
+ ///
+ /// Load defaults and then attempt to read init.config from working directory.
+ ///
+ public static void LoadInitConfig()
+ {
+ // reset to defaults
+ debitFlag1 = false;
+ betaTest = false;
+ ttsEnabled = true;
+ favoriteAttackers = null;
+ favoriteDefenders = null;
+ userName = null;
+ jitterEnabled = false;
+ jitterBurstTime = 1800;
+ jitterCooldown = 3000;
+ textColor = ConsoleColor.Magenta;
+
+ SetConsoleColor();
+
+ if (!File.Exists("init.config")) return;
+
+ var lines = Helper.ReadLinesSafe("init.config");
+
+ foreach (var raw in lines)
+ {
+ if (string.IsNullOrWhiteSpace(raw)) continue;
+ var line = raw.Trim();
+
+ // ignore comments
+ if (line.StartsWith("--") || line.StartsWith("#")) continue;
+
+ if (Helper.ParseBoolFromAssignment(line, "debitFlag1", out var b1))
+ debitFlag1 = b1;
+ else if (Helper.ParseBoolFromAssignment(line, "betaTest", out var b2))
+ betaTest = b2;
+ else if (Helper.ParseBoolFromAssignment(line, "ttsEnabled", out var b3))
+ ttsEnabled = b3;
+ else if (Helper.ParseIntFromAssignment(line, "nextSetKey", out var i1))
+ nextSetKey = i1;
+ else if (Helper.ParseIntFromAssignment(line, "prevSetKey", out var i2))
+ prevSetKey = i2;
+ else if (Helper.ParseIntFromAssignment(line, "refreshKey", out var i3))
+ refreshKey = i3;
+ else if (Helper.ParseIntFromAssignment(line, "switchSideKey", out var i4))
+ switchSideKey = i4;
+ else if (Helper.ParseStringFromAssignment(line, "favoriteAttackers", out var s1))
+ favoriteAttackers = s1;
+ else if (Helper.ParseStringFromAssignment(line, "favoriteDefenders", out var s2))
+ favoriteDefenders = s2;
+ else if (Helper.ParseStringFromAssignment(line, "userName", out var s3))
+ userName = s3;
+ else if (Helper.ParseBoolFromAssignment(line, "jitterEnabled", out var jb))
+ jitterEnabled = jb;
+ else if (Helper.ParseIntFromAssignment(line, "jitterBurstTime", out var jbT))
+ jitterBurstTime = jbT;
+ else if (Helper.ParseIntFromAssignment(line, "jitterCooldown", out var jbC))
+ jitterCooldown = jbC;
+ else if (Helper.ParseConsoleColorFromAssignment(line, "textColor", out var col))
+ textColor = col;
+ }
+
+ // Re-apply color in case it was changed by config
+ SetConsoleColor();
+ }
+
+ private static void SetConsoleColor()
+ {
+ try
+ {
+ Console.ForegroundColor = textColor;
+ }
+ catch
+ {
+ // ignore if console doesn't support color
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Lithium/Lithium.csproj b/Lithium/Lithium.csproj
new file mode 100644
index 0000000..2cf98ac
--- /dev/null
+++ b/Lithium/Lithium.csproj
@@ -0,0 +1,190 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {71516142-7D3E-46A1-A6EC-E242CD128DB1}
+ WinExe
+ Lithium
+ Lithium
+ v4.8
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+ true
+ true
+
+
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 0
+ 1.0.0.%2a
+ false
+ false
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\packages\Microsoft.Bcl.AsyncInterfaces.10.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll
+
+
+ ..\packages\Microsoft.Web.WebView2.1.0.3595.46\lib\net462\Microsoft.Web.WebView2.Core.dll
+
+
+ ..\packages\Microsoft.Web.WebView2.1.0.3595.46\lib\net462\Microsoft.Web.WebView2.WinForms.dll
+
+
+ ..\packages\Microsoft.Web.WebView2.1.0.3595.46\lib\net462\Microsoft.Web.WebView2.Wpf.dll
+
+
+
+ ..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll
+
+
+
+ ..\packages\System.IO.Pipelines.10.0.0\lib\net462\System.IO.Pipelines.dll
+
+
+ ..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll
+
+
+
+ ..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll
+
+
+ ..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll
+
+
+
+ ..\packages\System.Text.Encodings.Web.10.0.0\lib\net462\System.Text.Encodings.Web.dll
+
+
+ ..\packages\System.Text.Json.10.0.0\lib\net462\System.Text.Json.dll
+
+
+ ..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll
+
+
+
+
+
+
+
+
+ 4.0
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ App.xaml
+ Code
+
+
+
+
+ MainWindow.xaml
+ Code
+
+
+
+
+
+ Code
+
+
+ True
+ True
+ Resources.resx
+
+
+ True
+ Settings.settings
+ True
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+
+
+ False
+ Microsoft .NET Framework 4.8 %28x86 and x64%29
+ true
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Lithium/MainWindow.xaml b/Lithium/MainWindow.xaml
new file mode 100644
index 0000000..850f44b
--- /dev/null
+++ b/Lithium/MainWindow.xaml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Lithium/MainWindow.xaml.cs b/Lithium/MainWindow.xaml.cs
new file mode 100644
index 0000000..df46c8e
--- /dev/null
+++ b/Lithium/MainWindow.xaml.cs
@@ -0,0 +1,202 @@
+using System;
+using System.IO;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+using System.Threading;
+using System.Windows;
+using System.Windows.Controls;
+using Lua;
+
+namespace Lithium
+{
+ public partial class MainWindow : Window
+ {
+ private bool customSetEnabled = false;
+
+ public MainWindow()
+ {
+ InitializeComponent();
+
+ // Load configs
+ AppendConsole("Initializing Lithium...");
+ ConfigLoader.LoadInitConfig();
+ OperatorSets.LoadOperatorSets();
+
+ AppendConsole("Loaded Operators | Current Set: " + OperatorSets.CurrentSet);
+
+ // Load website inside browser
+ string url = OperatorSets.ConfigExportUrl;
+ Browser.Source = !string.IsNullOrEmpty(url) ? new Uri(url) : new Uri("https://jj.giggity.gay/exindex.html");
+
+ // Start hotkey monitoring thread
+ Thread hotkeyThread = new Thread(HotkeyLoop)
+ {
+ IsBackground = true
+ };
+ hotkeyThread.Start();
+ }
+
+ #region Keybinds
+ private void HotkeyLoop()
+ {
+ while (true)
+ {
+ // Next Set
+ if (Utils.IsPressed(ConfigLoader.nextSetKey))
+ {
+ OperatorSets.NextSet();
+ AppendConsole("Next Set → " + OperatorSets.CurrentSet);
+ Utils.SafeSpeak(OperatorSets.CurrentSet);
+ Thread.Sleep(200);
+ }
+
+ // Previous Set
+ if (Utils.IsPressed(ConfigLoader.prevSetKey))
+ {
+ OperatorSets.PreviousSet();
+ AppendConsole("Previous Set → " + OperatorSets.CurrentSet);
+ Utils.SafeSpeak(OperatorSets.CurrentSet);
+ Thread.Sleep(200);
+ }
+
+ // Reload Config
+ if (Utils.IsPressed(ConfigLoader.refreshKey))
+ {
+ OperatorSets.LoadOperatorSets();
+ AppendConsole("Reloaded config.");
+ Thread.Sleep(300);
+ }
+
+ // Switch Side
+ if (Utils.IsPressed(ConfigLoader.switchSideKey))
+ {
+ OperatorSets.SwitchSide();
+ AppendConsole("Switched side → " + (OperatorSets.onAttackers ? "Attackers" : "Defenders"));
+ Thread.Sleep(250);
+ }
+
+ Thread.Sleep(10);
+ }
+ }
+ #endregion
+
+ #region Console Append
+ public void AppendConsole(string msg)
+ {
+ Dispatcher.Invoke(() =>
+ {
+ ConsoleOutput.Text += msg + "\n";
+ });
+ }
+
+ public void UpdateCustomSetConsole(string msg)
+ {
+ Dispatcher.Invoke(() =>
+ {
+ CustomSetConsole.Text = msg;
+ });
+ }
+ #endregion
+
+ #region Custom Set Builder Handlers
+
+ private void ToggleCustomSet_Click(object sender, RoutedEventArgs e)
+ {
+ customSetEnabled = !customSetEnabled;
+ UpdateCustomSetConsole(customSetEnabled ? "Set is ON" : "Set is OFF");
+ }
+
+ private async void PublishSet_Click(object sender, RoutedEventArgs e)
+ {
+ string name = OperatorNameTextBox.Text.Trim();
+ if (string.IsNullOrEmpty(name))
+ {
+ UpdateCustomSetConsole("Error: Operator name required");
+ return;
+ }
+
+ if (!double.TryParse(RecoilXTextBox.Text, out var x)) x = 0;
+ if (!double.TryParse(RecoilYTextBox.Text, out var y)) y = 0;
+
+ string type = AttackerRadioButton.IsChecked == true ? "attackers" : "defenders";
+
+ var payload = new
+ {
+ type,
+ name,
+ recoilX = x,
+ recoilY = y
+ };
+
+ try
+ {
+ string json = JsonSerializer.Serialize(payload);
+
+ using (HttpClient client = new HttpClient())
+ {
+ var content = new StringContent(json, Encoding.UTF8, "application/json");
+ HttpResponseMessage response = await client.PostAsync("https://jj.giggity.gay/api/global/add", content);
+
+ if (response.IsSuccessStatusCode)
+ {
+ UpdateCustomSetConsole($"Successfully published: {name} ({type}) X:{x} Y:{y}");
+ }
+ else
+ {
+ string errorText = await response.Content.ReadAsStringAsync();
+ UpdateCustomSetConsole($"Failed to publish: {response.StatusCode} - {errorText}");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ UpdateCustomSetConsole("Error publishing set: " + ex.Message);
+ }
+ }
+
+ private void SaveSet_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ string path = "custom_set.txt";
+ File.WriteAllText(path, $"{OperatorNameTextBox.Text}|{RecoilXTextBox.Text}|{RecoilYTextBox.Text}|{(AttackerRadioButton.IsChecked == true ? "attacker" : "defender")}");
+ UpdateCustomSetConsole("Custom set saved");
+ }
+ catch
+ {
+ UpdateCustomSetConsole("Failed to save custom set");
+ }
+ }
+
+ private void LoadSet_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ string path = "custom_set.txt";
+ if (!File.Exists(path))
+ {
+ UpdateCustomSetConsole("No saved set found");
+ return;
+ }
+
+ string[] parts = File.ReadAllText(path).Split('|');
+ if (parts.Length != 4) throw new Exception("Invalid file format");
+
+ OperatorNameTextBox.Text = parts[0];
+ RecoilXTextBox.Text = parts[1];
+ RecoilYTextBox.Text = parts[2];
+ if (parts[3].ToLower() == "attacker") AttackerRadioButton.IsChecked = true;
+ else DefenderRadioButton.IsChecked = true;
+
+ UpdateCustomSetConsole("Custom set loaded");
+ }
+ catch
+ {
+ UpdateCustomSetConsole("Failed to load custom set");
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Lithium/OperatorSets.cs b/Lithium/OperatorSets.cs
new file mode 100644
index 0000000..b213b65
--- /dev/null
+++ b/Lithium/OperatorSets.cs
@@ -0,0 +1,317 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+
+namespace Lua
+{
+ static class OperatorSets
+ {
+ // ======================================================
+ // DATA TABLES
+ // ======================================================
+
+ public static Dictionary attackers =
+ new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ public static Dictionary defenders =
+ new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ public static Dictionary mergedSets =
+ new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ public static List attackerNames = new List();
+ public static List defenderNames = new List();
+
+ public static List setNames = new List();
+
+ public static int currentSetIndex = 0;
+ public static bool onAttackers = true;
+
+ public static string ConfigExportUrl =>
+ string.IsNullOrEmpty(ConfigLoader.userName)
+ ? null
+ : $"https://jj.giggity.gay/exindex.html?name={Uri.EscapeDataString(ConfigLoader.userName)}";
+
+
+
+ // ======================================================
+ // CURRENT SET + CURRENT OPERATOR
+ // ======================================================
+
+ public static string CurrentSet
+ {
+ get
+ {
+ if (setNames == null || setNames.Count == 0) return "N/A";
+ currentSetIndex = Helper.SafeClampIndex(currentSetIndex, setNames.Count);
+ return setNames[currentSetIndex];
+ }
+ }
+
+ private static string _currentOperator = "";
+ public static string CurrentOperator
+ {
+ get
+ {
+ if (!string.IsNullOrWhiteSpace(_currentOperator) &&
+ mergedSets.ContainsKey(_currentOperator))
+ return _currentOperator;
+
+ return CurrentSet; // fallback
+ }
+ set
+ {
+ if (mergedSets.ContainsKey(value))
+ _currentOperator = value;
+ }
+ }
+
+
+ // Build online URL
+ private static string OnlineConfigUrl =>
+ string.IsNullOrEmpty(ConfigLoader.userName)
+ ? null
+ : $"https://jj.giggity.gay/api/config/export?name={Uri.EscapeDataString(ConfigLoader.userName)}";
+
+
+ // ======================================================
+ // LOAD CONFIG
+ // ======================================================
+ public static void LoadOperatorSets(bool allowOnlineFetch = true)
+ {
+ attackers.Clear();
+ defenders.Clear();
+ attackerNames.Clear();
+ defenderNames.Clear();
+
+ var allLines = new List();
+
+ // ---------- ONLINE ----------
+ if (allowOnlineFetch && !string.IsNullOrEmpty(OnlineConfigUrl))
+ {
+ try
+ {
+ using (var client = new HttpClient())
+ {
+ string txt = client.GetStringAsync(OnlineConfigUrl).Result;
+ if (!string.IsNullOrWhiteSpace(txt))
+ allLines.AddRange(txt.Split('\n'));
+ }
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ // ---------- LOCAL ----------
+ var local = Helper.ReadLinesSafe("config.txt");
+ if (local != null && local.Length > 0)
+ allLines.AddRange(local);
+
+ bool inAtk = false;
+ bool inDef = false;
+
+ // ---------- PARSE ----------
+ foreach (var raw in allLines)
+ {
+ if (string.IsNullOrWhiteSpace(raw)) continue;
+ var line = raw.Trim();
+
+ if (line.StartsWith("--") || line.StartsWith("#")) continue;
+
+ if (line.StartsWith("attackers", StringComparison.OrdinalIgnoreCase))
+ {
+ inAtk = true; inDef = false; continue;
+ }
+ if (line.StartsWith("defenders", StringComparison.OrdinalIgnoreCase))
+ {
+ inAtk = false; inDef = true; continue;
+ }
+ if (line == "}" || line == "]," || line == "],")
+ {
+ inAtk = false; inDef = false; continue;
+ }
+
+ // parse recoil line
+ if (Helper.TryParseRecoilLine(line, out var name, out var x, out var y))
+ {
+ if (inAtk)
+ {
+ attackers[name] = (x, y);
+ attackerNames.Add(name);
+ }
+ else if (inDef)
+ {
+ defenders[name] = (x, y);
+ defenderNames.Add(name);
+ }
+ }
+ }
+
+ MergeSets();
+ RestoreFavorites();
+
+ // correct operator selection
+ if (setNames.Count > 0)
+ CurrentOperator = CurrentSet;
+ }
+
+
+ // ======================================================
+ // MERGE
+ // ======================================================
+ public static void MergeSets()
+ {
+ if (ConfigLoader.betaTest)
+ {
+ mergedSets.Clear();
+
+ foreach (var kv in attackers)
+ mergedSets[kv.Key] = kv.Value;
+ foreach (var kv in defenders)
+ mergedSets[kv.Key] = kv.Value;
+
+ setNames = attackerNames.Concat(defenderNames)
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
+ }
+ else
+ {
+ mergedSets = onAttackers
+ ? new Dictionary(attackers, StringComparer.OrdinalIgnoreCase)
+ : new Dictionary(defenders, StringComparer.OrdinalIgnoreCase);
+
+ setNames = onAttackers
+ ? new List(attackerNames)
+ : new List(defenderNames);
+ }
+
+ if (setNames == null) setNames = new List();
+ currentSetIndex = Helper.SafeClampIndex(currentSetIndex, setNames.Count);
+ }
+
+
+ // ======================================================
+ // RESTORE
+ // ======================================================
+ public static void RestoreSet(string oldSet)
+ {
+ if (setNames.Count == 0)
+ {
+ currentSetIndex = 0;
+ return;
+ }
+
+ int idx = setNames.FindIndex(s =>
+ string.Equals(s, oldSet, StringComparison.OrdinalIgnoreCase));
+
+ currentSetIndex = idx >= 0 ? idx : 0;
+
+ CurrentOperator = CurrentSet;
+ }
+
+
+ // ======================================================
+ // SWITCH SIDE
+ // ======================================================
+ public static void SwitchSide()
+ {
+ if (ConfigLoader.betaTest) return;
+
+ onAttackers = !onAttackers;
+ MergeSets();
+ RestoreFavorites();
+ }
+
+
+ // ======================================================
+ // CYCLING
+ // ======================================================
+ public static void NextSet()
+ {
+ if (setNames.Count == 0) return;
+
+ currentSetIndex = (currentSetIndex + 1) % setNames.Count;
+ CurrentOperator = CurrentSet;
+ }
+
+ public static void PreviousSet()
+ {
+ if (setNames.Count == 0) return;
+
+ currentSetIndex = (currentSetIndex - 1 + setNames.Count) % setNames.Count;
+ CurrentOperator = CurrentSet;
+ }
+
+
+ // ======================================================
+ // FAVORITES
+ // ======================================================
+ private static void RestoreFavorites()
+ {
+ if (setNames.Count == 0)
+ {
+ currentSetIndex = 0;
+ return;
+ }
+
+ if (ConfigLoader.betaTest)
+ {
+ if (!string.IsNullOrEmpty(ConfigLoader.favoriteAttackers) &&
+ setNames.Contains(ConfigLoader.favoriteAttackers, StringComparer.OrdinalIgnoreCase))
+ {
+ currentSetIndex = setNames.FindIndex(s =>
+ string.Equals(s, ConfigLoader.favoriteAttackers, StringComparison.OrdinalIgnoreCase));
+
+ CurrentOperator = CurrentSet;
+ return;
+ }
+
+ if (!string.IsNullOrEmpty(ConfigLoader.favoriteDefenders) &&
+ setNames.Contains(ConfigLoader.favoriteDefenders, StringComparer.OrdinalIgnoreCase))
+ {
+ currentSetIndex = setNames.FindIndex(s =>
+ string.Equals(s, ConfigLoader.favoriteDefenders, StringComparison.OrdinalIgnoreCase));
+
+ CurrentOperator = CurrentSet;
+ return;
+ }
+
+ currentSetIndex = 0;
+ CurrentOperator = CurrentSet;
+ return;
+ }
+
+ // non-beta side-based
+ if (onAttackers)
+ {
+ if (!string.IsNullOrEmpty(ConfigLoader.favoriteAttackers) &&
+ setNames.Contains(ConfigLoader.favoriteAttackers, StringComparer.OrdinalIgnoreCase))
+ {
+ currentSetIndex = setNames.FindIndex(s =>
+ string.Equals(s, ConfigLoader.favoriteAttackers, StringComparison.OrdinalIgnoreCase));
+
+ CurrentOperator = CurrentSet;
+ return;
+ }
+ }
+ else
+ {
+ if (!string.IsNullOrEmpty(ConfigLoader.favoriteDefenders) &&
+ setNames.Contains(ConfigLoader.favoriteDefenders, StringComparer.OrdinalIgnoreCase))
+ {
+ currentSetIndex = setNames.FindIndex(s =>
+ string.Equals(s, ConfigLoader.favoriteDefenders, StringComparison.OrdinalIgnoreCase));
+
+ CurrentOperator = CurrentSet;
+ return;
+ }
+ }
+
+ currentSetIndex = 0;
+ CurrentOperator = CurrentSet;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Lithium/Properties/AssemblyInfo.cs b/Lithium/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..dbed813
--- /dev/null
+++ b/Lithium/Properties/AssemblyInfo.cs
@@ -0,0 +1,52 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Lithium")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Lithium")]
+[assembly: AssemblyCopyright("Copyright © 2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+//In order to begin building localizable applications, set
+//CultureYouAreCodingWith in your .csproj file
+//inside a . For example, if you are using US english
+//in your source files, set the to en-US. Then uncomment
+//the NeutralResourceLanguage attribute below. Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
+
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Lithium/Properties/Resources.Designer.cs b/Lithium/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..e586858
--- /dev/null
+++ b/Lithium/Properties/Resources.Designer.cs
@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Lithium.Properties
+{
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources
+ {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources()
+ {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager
+ {
+ get
+ {
+ if ((resourceMan == null))
+ {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Lithium.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture
+ {
+ get
+ {
+ return resourceCulture;
+ }
+ set
+ {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/Lithium/Properties/Resources.resx b/Lithium/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/Lithium/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Lithium/Properties/Settings.Designer.cs b/Lithium/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..d333c5a
--- /dev/null
+++ b/Lithium/Properties/Settings.Designer.cs
@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Lithium.Properties
+{
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+ {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default
+ {
+ get
+ {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/Lithium/Properties/Settings.settings b/Lithium/Properties/Settings.settings
new file mode 100644
index 0000000..033d7a5
--- /dev/null
+++ b/Lithium/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Lithium/RecoilControl.cs b/Lithium/RecoilControl.cs
new file mode 100644
index 0000000..0ab5da5
--- /dev/null
+++ b/Lithium/RecoilControl.cs
@@ -0,0 +1,99 @@
+// RecoilControl.cs (patched)
+using System;
+using System.Threading;
+
+namespace Lua
+{
+ static class RecoilControl
+ {
+ const int VK_LBUTTON = 0x01;
+ const int VK_RBUTTON = 0x02;
+
+ static Random rng = new Random();
+
+ static bool jitterActive = false;
+ static int jitterStart = 0;
+ static int lastJitterEnd = 0;
+
+ static double remX, remY;
+ static double jitterMultX, jitterMultY;
+
+ public static void RecoilLoop()
+ {
+ 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;
+
+ var setName = OperatorSets.CurrentSet;
+
+ if (!OperatorSets.mergedSets.TryGetValue(setName, out var recoil))
+ return;
+
+ int now = Environment.TickCount;
+
+ 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;
+ }
+ }
+
+ 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
new file mode 100644
index 0000000..e4a065c
--- /dev/null
+++ b/Lithium/Utils.cs
@@ -0,0 +1,99 @@
+// Utils.cs (patched)
+using System;
+using System.Runtime.InteropServices;
+using System.Speech.Synthesis;
+using System.Threading;
+
+namespace Lua
+{
+ static class Utils
+ {
+ public static readonly SpeechSynthesizer Tts = new SpeechSynthesizer();
+ private static readonly object TtsLock = new object();
+
+ [DllImport("user32.dll")] public static extern short GetAsyncKeyState(int vKey);
+ [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] public static extern short GetKeyState(int keyCode);
+ [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;
+
+ public static void InitializeCapsState()
+ {
+ previousCapsState = IsCapsLockOn();
+ }
+
+ public static bool IsPressed(int vKey) => (GetAsyncKeyState(vKey) & 0x8000) != 0;
+ public static bool IsCapsLockOn() => (GetKeyState(VK_CAPITAL) & 0x0001) != 0;
+ public static void MoveMouse(int dx, int dy) => mouse_event(MOUSEEVENTF_MOVE, dx, dy, 0, UIntPtr.Zero);
+
+ public static void SafeSpeak(string text)
+ {
+ if (!ConfigLoader.ttsEnabled) return;
+
+ lock (TtsLock)
+ {
+ try
+ {
+ Tts.SpeakAsyncCancelAll();
+ Tts.SpeakAsync(text);
+ }
+ catch { }
+ }
+ }
+
+ public static void ShowCapsLockStatus()
+ {
+ Console.WriteLine(IsCapsLockOn()
+ ? "🚫 Recoil Control: OFF (CapsLock ON)"
+ : $"✅ Recoil Control: ON (CapsLock OFF) | Current Set: {OperatorSets.CurrentSet}");
+ }
+
+ public static void CheckCapsLockChange()
+ {
+ bool caps = IsCapsLockOn();
+ if (caps != previousCapsState)
+ {
+ previousCapsState = caps;
+ ShowCapsLockStatus();
+ }
+ }
+
+ public static void DisplayCurrentOperator(string action)
+ {
+ Console.WriteLine($"🎮 {action} | Current Operator: {OperatorSets.CurrentSet}");
+ SafeSpeak(OperatorSets.CurrentSet);
+ }
+
+ public static string ModeHeader()
+ {
+ return ConfigLoader.betaTest
+ ? $"🎯 Dev Mode Enabled | Current Set: {OperatorSets.CurrentSet}"
+ : $"🚀 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
+
+ if (current >= limit)
+ {
+ Console.Clear();
+ Console.WriteLine(ModeHeader());
+ ShowCapsLockStatus();
+ }
+ }
+ catch
+ {
+ // Ignore if console unavailable
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Lithium/helper.cs b/Lithium/helper.cs
new file mode 100644
index 0000000..b1b1f71
--- /dev/null
+++ b/Lithium/helper.cs
@@ -0,0 +1,116 @@
+// Helper.cs
+using System;
+using System.IO;
+using System.Globalization;
+using System.Text.RegularExpressions;
+
+namespace Lua
+{
+ static class Helper
+ {
+ ///
+ /// Safely read lines from a file; returns empty array if missing or unreadable.
+ ///
+ public static string[] ReadLinesSafe(string path)
+ {
+ try
+ {
+ if (!File.Exists(path)) return Array.Empty();
+ return File.ReadAllLines(path);
+ }
+ catch
+ {
+ return Array.Empty();
+ }
+ }
+
+ ///
+ /// Parse lines of the form: key = value (case-insensitive key). Returns true when parsed.
+ /// For boolean values (true/false).
+ ///
+ public static bool ParseBoolFromAssignment(string line, string key, out bool result)
+ {
+ result = false;
+ var prefix = key + "=";
+ var idx = line.IndexOf('=');
+ if (idx < 0) return false;
+
+ var lhs = line.Substring(0, idx).Trim();
+ if (!string.Equals(lhs, key, StringComparison.OrdinalIgnoreCase)) return false;
+
+ var rhs = line.Substring(idx + 1).Trim();
+ if (rhs.Equals("true", StringComparison.OrdinalIgnoreCase)) { result = true; return true; }
+ if (rhs.Equals("false", StringComparison.OrdinalIgnoreCase)) { result = false; return true; }
+
+ // attempt to parse other truthy values
+ if (bool.TryParse(rhs, out var b)) { result = b; return true; }
+
+ return false;
+ }
+
+ public static bool ParseIntFromAssignment(string line, string key, out int result)
+ {
+ result = 0;
+ var idx = line.IndexOf('=');
+ if (idx < 0) return false;
+ var lhs = line.Substring(0, idx).Trim();
+ if (!string.Equals(lhs, key, StringComparison.OrdinalIgnoreCase)) return false;
+ var rhs = line.Substring(idx + 1).Trim();
+ return int.TryParse(rhs, NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
+ }
+
+ public static bool ParseStringFromAssignment(string line, string key, out string result)
+ {
+ result = null;
+ var idx = line.IndexOf('=');
+ if (idx < 0) return false;
+ var lhs = line.Substring(0, idx).Trim();
+ if (!string.Equals(lhs, key, StringComparison.OrdinalIgnoreCase)) return false;
+ result = line.Substring(idx + 1).Trim();
+ // strip surrounding quotes if present
+ if ((result.StartsWith("\"") && result.EndsWith("\"")) || (result.StartsWith("'") && result.EndsWith("'")))
+ result = result.Substring(1, result.Length - 2);
+ return true;
+ }
+
+ public static bool ParseConsoleColorFromAssignment(string line, string key, out ConsoleColor color)
+ {
+ color = ConsoleColor.White;
+ if (!ParseStringFromAssignment(line, key, out var s)) return false;
+ return Enum.TryParse(s, true, out color);
+ }
+
+ ///
+ /// Parse a recoil definition line. Returns name and double values using InvariantCulture.
+ /// Matches patterns like:
+ /// ["Name"] = { recoilX = 1.23, recoilY = -4.56 }
+ /// or "['Name'] = { recoilX = 1.23, recoilY = -4.56 }"
+ ///
+ public static bool TryParseRecoilLine(string line, out string name, out double recoilX, out double recoilY)
+ {
+ name = null; recoilX = 0; recoilY = 0;
+
+ // regex to capture "Name" and two numbers
+ var pattern = @"\[\s*[""'](.+?)[""']\s*\]\s*=\s*\{\s*recoilX\s*=\s*([-\d\.eE]+)\s*,\s*recoilY\s*=\s*([-\d\.eE]+)\s*\}";
+ var m = Regex.Match(line, pattern, RegexOptions.IgnoreCase);
+ if (!m.Success) return false;
+
+ name = m.Groups[1].Value.Trim();
+ if (!double.TryParse(m.Groups[2].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out recoilX)) return false;
+ if (!double.TryParse(m.Groups[3].Value, NumberStyles.Float, CultureInfo.InvariantCulture, out recoilY)) return false;
+
+ return true;
+ }
+
+ ///
+ /// Ensure index is valid for a list of given size (0..size-1). If size==0 returns 0.
+ ///
+ public static int SafeClampIndex(int idx, int size)
+ {
+ if (size <= 0) return 0;
+ if (idx < 0) return 0;
+ if (idx >= size) return size - 1;
+ return idx;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Lithium/packages.config b/Lithium/packages.config
new file mode 100644
index 0000000..fdda007
--- /dev/null
+++ b/Lithium/packages.config
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file