Add project files.

This commit is contained in:
JJ
2025-11-22 14:21:26 -05:00
parent bbc518324b
commit 9e9dd5f57c
18 changed files with 1555 additions and 0 deletions
+25
View File
@@ -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
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
</configuration>
+7
View File
@@ -0,0 +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">
<Application.Resources>
</Application.Resources>
</Application>
+17
View File
@@ -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
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
}
}
+112
View File
@@ -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;
/// <summary>
/// Load defaults and then attempt to read init.config from working directory.
/// </summary>
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
}
}
}
}
+190
View File
@@ -0,0 +1,190 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{71516142-7D3E-46A1-A6EC-E242CD128DB1}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>Lithium</RootNamespace>
<AssemblyName>Lithium</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.10.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Web.WebView2.Core, Version=1.0.3595.46, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3595.46\lib\net462\Microsoft.Web.WebView2.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Web.WebView2.WinForms, Version=1.0.3595.46, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3595.46\lib\net462\Microsoft.Web.WebView2.WinForms.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Web.WebView2.Wpf, Version=1.0.3595.46, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3595.46\lib\net462\Microsoft.Web.WebView2.Wpf.dll</HintPath>
</Reference>
<Reference Include="System" />
<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.Data" />
<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.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.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.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>
</Reference>
<Reference Include="System.Text.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Json.10.0.0\lib\net462\System.Text.Json.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<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>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="RecoilControl.cs" />
<Compile Include="Utils.cs" />
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="ConfigLoader.cs" />
<Compile Include="helper.cs" />
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="OperatorSets.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="AppDomain.AssemblyResolve" />
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.8">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.8 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Microsoft.Web.WebView2.1.0.3595.46\build\Microsoft.Web.WebView2.targets" Condition="Exists('..\packages\Microsoft.Web.WebView2.1.0.3595.46\build\Microsoft.Web.WebView2.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>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}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Microsoft.Web.WebView2.1.0.3595.46\build\Microsoft.Web.WebView2.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.Web.WebView2.1.0.3595.46\build\Microsoft.Web.WebView2.targets'))" />
<Error Condition="!Exists('..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets'))" />
</Target>
<Import Project="..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets" Condition="Exists('..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" />
<ItemGroup>
<!-- Include all referenced DLLs -->
<ReferenceCopyLocalPaths Include="@(ReferencePath)" />
</ItemGroup>
<Target Name="CopyDllsToLib" AfterTargets="Build">
<MakeDir Directories="$(OutputPath)lib" />
<Copy SourceFiles="@(ReferenceCopyLocalPaths)" DestinationFolder="$(OutputPath)lib" SkipUnchangedFiles="true" />
</Target>
</Project>
+73
View File
@@ -0,0 +1,73 @@
<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"
Title="Lithium" Height="570" Width="900"
Background="#1E1E1E">
<Grid Margin="10">
<TabControl Background="#1E1E1E" Foreground="White">
<!-- Console -->
<TabItem Header="Console">
<Grid Background="#252525" Margin="10">
<ScrollViewer x:Name="ConsoleScroll" VerticalScrollBarVisibility="Auto">
<TextBlock x:Name="ConsoleOutput"
TextWrapping="Wrap"
Foreground="White"
FontFamily="Consolas"
FontSize="14" />
</ScrollViewer>
</Grid>
</TabItem>
<!-- Website -->
<TabItem Header="Website">
<Grid Background="#252525">
<wv:WebView2 x:Name="Browser"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Grid>
</TabItem>
<!-- Custom Set Builder -->
<TabItem Header="Custom Set Builder">
<Grid Background="#252525" Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Operator Details -->
<StackPanel Grid.Row="0" Margin="20" VerticalAlignment="Top">
<TextBlock Text="Operator Name:" Foreground="White" FontSize="14" Margin="0,5"/>
<TextBox x:Name="OperatorNameTextBox" Margin="0,5"/>
<TextBlock Text="Recoil X:" Foreground="White" FontSize="14" Margin="0,5"/>
<TextBox x:Name="RecoilXTextBox" Margin="0,5"/>
<TextBlock Text="Recoil Y:" Foreground="White" FontSize="14" Margin="0,5"/>
<TextBox x:Name="RecoilYTextBox" Margin="0,5"/>
<TextBlock Text="Type:" Foreground="White" FontSize="14" Margin="0,5"/>
<StackPanel Orientation="Horizontal" Margin="0,5">
<RadioButton x:Name="AttackerRadioButton" Content="Attacker" IsChecked="True" Margin="0,0,10,0"/>
<RadioButton x:Name="DefenderRadioButton" Content="Defender"/>
</StackPanel>
<Button x:Name="ToggleCustomSetButton" Content="Toggle ON/OFF" Width="120" Margin="0,15" Click="ToggleCustomSet_Click"/>
</StackPanel>
<!-- Console & Publish -->
<StackPanel Grid.Row="1" Orientation="Vertical" Margin="20">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,0,0,5">
<Button Content="Publish Set" Width="120" Margin="0,0,10,0" Click="PublishSet_Click"/>
</StackPanel>
<TextBlock x:Name="CustomSetConsole" Foreground="Lime" FontFamily="Consolas" FontSize="12" Text="Set is OFF" Margin="0,5"/>
</StackPanel>
</Grid>
</TabItem>
</TabControl>
</Grid>
</Window>
+202
View File
@@ -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
}
}
+317
View File
@@ -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<string, (double recoilX, double recoilY)> attackers =
new Dictionary<string, (double, double)>(StringComparer.OrdinalIgnoreCase);
public static Dictionary<string, (double recoilX, double recoilY)> defenders =
new Dictionary<string, (double, double)>(StringComparer.OrdinalIgnoreCase);
public static Dictionary<string, (double recoilX, double recoilY)> mergedSets =
new Dictionary<string, (double, double)>(StringComparer.OrdinalIgnoreCase);
public static List<string> attackerNames = new List<string>();
public static List<string> defenderNames = new List<string>();
public static List<string> setNames = new List<string>();
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<string>();
// ---------- 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<string, (double, double)>(attackers, StringComparer.OrdinalIgnoreCase)
: new Dictionary<string, (double, double)>(defenders, StringComparer.OrdinalIgnoreCase);
setNames = onAttackers
? new List<string>(attackerNames)
: new List<string>(defenderNames);
}
if (setNames == null) setNames = new List<string>();
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;
}
}
}
+52
View File
@@ -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
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> 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")]
+71
View File
@@ -0,0 +1,71 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Lithium.Properties
{
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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()
{
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
return resourceCulture;
}
set
{
resourceCulture = value;
}
}
}
}
+117
View File
@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+30
View File
@@ -0,0 +1,30 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
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;
}
}
}
}
+7
View File
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>
+99
View File
@@ -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);
}
}
}
+99
View File
@@ -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
}
}
}
}
+116
View File
@@ -0,0 +1,116 @@
// Helper.cs
using System;
using System.IO;
using System.Globalization;
using System.Text.RegularExpressions;
namespace Lua
{
static class Helper
{
/// <summary>
/// Safely read lines from a file; returns empty array if missing or unreadable.
/// </summary>
public static string[] ReadLinesSafe(string path)
{
try
{
if (!File.Exists(path)) return Array.Empty<string>();
return File.ReadAllLines(path);
}
catch
{
return Array.Empty<string>();
}
}
/// <summary>
/// Parse lines of the form: key = value (case-insensitive key). Returns true when parsed.
/// For boolean values (true/false).
/// </summary>
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);
}
/// <summary>
/// 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 }"
/// </summary>
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;
}
/// <summary>
/// Ensure index is valid for a list of given size (0..size-1). If size==0 returns 0.
/// </summary>
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;
}
}
}
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Bcl.AsyncInterfaces" version="10.0.0" targetFramework="net48" />
<package id="Microsoft.Web.WebView2" version="1.0.3595.46" targetFramework="net48" />
<package id="System.Buffers" version="4.6.1" targetFramework="net48" />
<package id="System.IO.Pipelines" version="10.0.0" targetFramework="net48" />
<package id="System.Memory" version="4.6.3" targetFramework="net48" />
<package id="System.Numerics.Vectors" version="4.6.1" targetFramework="net48" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.1.2" targetFramework="net48" />
<package id="System.Speech" version="10.0.0" targetFramework="net48" />
<package id="System.Text.Encodings.Web" version="10.0.0" targetFramework="net48" />
<package id="System.Text.Json" version="10.0.0" targetFramework="net48" />
<package id="System.Threading.Tasks.Extensions" version="4.6.3" targetFramework="net48" />
<package id="System.ValueTuple" version="4.6.1" targetFramework="net48" />
</packages>