diff --git a/README.md b/README.md index 5fc4e6c..ca99701 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# SleepStrap 6.9.4 +# SleepStrap 6.9.5 ![SleepStrap icon](SleepStrap/SleepStrap.png) SleepStrap is a purple Windows launcher for Roblox with skyboxes, texture presets, visual modes, fonts, and Rivals display tools. -[Download SleepStrap 6.9.4](https://github.com/SleepyDebug/SleepStrap/releases/tag/sleepstrap-6.9.4) · [Discord](https://discord.gg/W3CMjx8C7s) · [Report an issue](https://github.com/SleepyDebug/SleepStrap/issues) +[Download SleepStrap 6.9.5](https://github.com/SleepyDebug/SleepStrap/releases/tag/sleepstrap-6.9.5) · [Discord](https://discord.gg/W3CMjx8C7s) · [Report an issue](https://github.com/SleepyDebug/SleepStrap/issues) ## Repository layout diff --git a/SleepStrap/Models/Persistable/Settings.cs b/SleepStrap/Models/Persistable/Settings.cs index 440d05a..d9216c7 100644 --- a/SleepStrap/Models/Persistable/Settings.cs +++ b/SleepStrap/Models/Persistable/Settings.cs @@ -89,13 +89,6 @@ public class Settings public string MacroSecondaryWeapon { get; set; } = "Warper"; public string MacroMeleeWeapon { get; set; } = "Maul"; public string MacroUtilityWeapon { get; set; } = "Grappler"; - public string MacroQuickLoadoutPrimaryWeapon { get; set; } = "Distortion"; - public string MacroQuickLoadoutSecondaryWeapon { get; set; } = "Warper"; - public string MacroQuickLoadoutMeleeWeapon { get; set; } = "Maul"; - public string MacroQuickLoadoutUtilityWeapon { get; set; } = "Grappler"; - public int MacroQuickLoadoutHotkeyModifiers { get; set; } = 3; // Ctrl + Alt - public int MacroQuickLoadoutHotkeyVirtualKey { get; set; } = 0x51; // Q - public bool MacroQuickLoadoutEnabled { get; set; } = true; public bool MacroQuickRespawn { get; set; } = false; public bool MacroAutoUtility { get; set; } = false; public bool MacroAutoInspect { get; set; } = false; diff --git a/SleepStrap/Services/MacroAutomationService.cs b/SleepStrap/Services/MacroAutomationService.cs index a5d01b2..14dae69 100644 --- a/SleepStrap/Services/MacroAutomationService.cs +++ b/SleepStrap/Services/MacroAutomationService.cs @@ -30,10 +30,11 @@ public static class MacroAutomationService private const uint MonitorDefaultToNearest = 0x00000002; private const int GwlStyle = -16; private const int GwlExStyle = -20; - - private static readonly object AutomaticActionsLock = new(); - private static Process? _automaticActionsProcess; - private static string? _automaticActionsScriptPath; + private const uint MouseEventMove = 0x0001; + private const uint MouseEventLeftDown = 0x0002; + private const uint MouseEventLeftUp = 0x0004; + private const uint MouseEventVirtualDesk = 0x4000; + private const uint MouseEventAbsolute = 0x8000; private static readonly IReadOnlyDictionary Slots = new Dictionary @@ -166,88 +167,8 @@ public static bool IsRobloxForeground() public static bool IsKeyDown(int virtualKey) => (GetAsyncKeyState(virtualKey) & 0x8000) != 0; - public static bool ConfigureAutomaticActions(bool quickRespawn, bool autoUtility, bool autoInspect, bool enabled) - { - lock (AutomaticActionsLock) - { - StopAutomaticActionsCore(); - - if (!enabled || (!quickRespawn && !autoUtility && !autoInspect)) - return FindAutoHotkeyV2() is not null; - - string? autoHotkey = FindAutoHotkeyV2(); - if (autoHotkey is null) - return false; - - string scriptPath = Path.Combine(Path.GetTempPath(), $"SleepStrap-AutoActions-{Environment.ProcessId}.ahk"); - string script = - "#Requires AutoHotkey v2.0\r\n" + - "#SingleInstance Off\r\n" + - $"parentPid := {Environment.ProcessId}\r\n" + - $"quickRespawn := {(quickRespawn ? "true" : "false")}\r\n" + - $"autoUtility := {(autoUtility ? "true" : "false")}\r\n" + - $"autoInspect := {(autoInspect ? "true" : "false")}\r\n" + - "SetTimer WatchParent, 500\r\n" + - "SetTimer SpamQuickRespawn, 75\r\n" + - "SetTimer SpamAutoUtility, 120\r\n" + - "SetTimer SpamAutoInspect, 180\r\n" + - "WatchParent() {\r\n" + - " global parentPid\r\n" + - " if !ProcessExist(parentPid)\r\n" + - " ExitApp\r\n" + - "}\r\n" + - "RobloxActive() {\r\n" + - " return WinActive(\"ahk_exe RobloxPlayerBeta.exe\")\r\n" + - "}\r\n" + - "SpamQuickRespawn() {\r\n" + - " global quickRespawn\r\n" + - " if quickRespawn && RobloxActive()\r\n" + - " Send \"{Space}\"\r\n" + - "}\r\n" + - "SpamAutoUtility() {\r\n" + - " global autoUtility\r\n" + - " if autoUtility && RobloxActive()\r\n" + - " Send \"g\"\r\n" + - "}\r\n" + - "SpamAutoInspect() {\r\n" + - " global autoInspect\r\n" + - " if autoInspect && RobloxActive()\r\n" + - " Send \"v\"\r\n" + - "}\r\n"; - - try - { - File.WriteAllText(scriptPath, script, new UTF8Encoding(false)); - var startInfo = new ProcessStartInfo(autoHotkey) - { - UseShellExecute = false, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden - }; - startInfo.ArgumentList.Add("/ErrorStdOut"); - startInfo.ArgumentList.Add(scriptPath); - - _automaticActionsProcess = Process.Start(startInfo) - ?? throw new InvalidOperationException("SleepStrap could not start automatic actions."); - _automaticActionsScriptPath = scriptPath; - App.Logger.WriteLine("MacroAutomationService", $"Started AutoHotkey automatic actions (Space={quickRespawn}, G={autoUtility}, V={autoInspect})"); - return true; - } - catch (Exception ex) - { - App.Logger.WriteException("MacroAutomationService::ConfigureAutomaticActions", ex); - try { File.Delete(scriptPath); } catch { } - return false; - } - } - } - public static async Task RunAutoRejoinAsync(CancellationToken cancellationToken) { - string? autoHotkey = FindAutoHotkeyV2(); - if (autoHotkey is null) - throw new InvalidOperationException("Auto Rejoin requires AutoHotkey v2."); - IntPtr robloxWindow = FindRobloxWindow(); if (robloxWindow == IntPtr.Zero) throw new InvalidOperationException("Roblox is not running."); @@ -256,100 +177,23 @@ public static async Task RunAutoRejoinAsync(CancellationToken cancellationToken) ShowWindowAsync(robloxWindow, 9); ActivateWindow(robloxWindow); - string scriptPath = Path.Combine(Path.GetTempPath(), $"SleepStrap-AutoRejoin-{Guid.NewGuid():N}.ahk"); - string script = - "#Requires AutoHotkey v2.0\r\n" + - "#SingleInstance Off\r\n" + - "CoordMode \"Mouse\", \"Screen\"\r\n" + - "WinActivate \"ahk_exe RobloxPlayerBeta.exe\"\r\n" + - "if !WinWaitActive(\"ahk_exe RobloxPlayerBeta.exe\",, 3)\r\n" + - " ExitApp 2\r\n" + - "Send \"{Escape}\"\r\n" + - "Sleep 120\r\n" + - "Send \"l\"\r\n" + - "Sleep 120\r\n" + - "Send \"{Enter}\"\r\n" + - "Sleep 1400\r\n" + - "ClickPoint(-860, 694, 900) ; Disconnect\r\n" + - "ClickPoint(-1892, 159, 900) ; Home\r\n" + - "ClickPoint(-1735, 1031, 900) ; Select Rivals\r\n" + - "ClickPoint(-1011, 372, 1800) ; Join Rivals\r\n" + - "ClickPoint(-956, 954, 1200) ; Play\r\n" + - "MouseMove -240, 389, 0\r\n" + - "Sleep 80\r\n" + - "Click \"Down\"\r\n" + - "Sleep 120\r\n" + - "MouseMove -241, 951, 10\r\n" + - "Sleep 100\r\n" + - "Click \"Up\"\r\n" + - "Sleep 600\r\n" + - "ClickPoint(-977, 827, 700) ; Select FFA\r\n" + - "ClickPoint(-790, 836, 500) ; Join\r\n" + - "ExitApp\r\n" + - "ClickPoint(x, y, waitAfter) {\r\n" + - " MouseMove x, y, 0\r\n" + - " Sleep 60\r\n" + - " Click \"Down\"\r\n" + - " Sleep 35\r\n" + - " Click \"Up\"\r\n" + - " Sleep waitAfter\r\n" + - "}\r\n"; - - try - { - await File.WriteAllTextAsync(scriptPath, script, new UTF8Encoding(false), cancellationToken); - App.Logger.WriteLine("MacroAutomationService", "Starting hourly Auto Rejoin sequence"); - var startInfo = new ProcessStartInfo(autoHotkey) - { - UseShellExecute = false, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden - }; - startInfo.ArgumentList.Add("/ErrorStdOut"); - startInfo.ArgumentList.Add(scriptPath); - - using Process process = Process.Start(startInfo) - ?? throw new InvalidOperationException("SleepStrap could not start Auto Rejoin."); - await process.WaitForExitAsync(cancellationToken); - if (process.ExitCode != 0) - throw new InvalidOperationException($"Auto Rejoin stopped with exit code {process.ExitCode}."); - } - finally - { - try { if (File.Exists(scriptPath)) File.Delete(scriptPath); } catch { } - } - } - - public static void StopAutomaticActions() - { - lock (AutomaticActionsLock) - StopAutomaticActionsCore(); - } - - private static void StopAutomaticActionsCore() - { - try - { - if (_automaticActionsProcess is { HasExited: false }) - _automaticActionsProcess.Kill(); - } - catch (Exception ex) - { - App.Logger.WriteException("MacroAutomationService::StopAutomaticActions", ex); - } - finally - { - _automaticActionsProcess?.Dispose(); - _automaticActionsProcess = null; - } - - try - { - if (_automaticActionsScriptPath is not null && File.Exists(_automaticActionsScriptPath)) - File.Delete(_automaticActionsScriptPath); - } - catch { } - _automaticActionsScriptPath = null; + App.Logger.WriteLine("MacroAutomationService", "Starting built-in hourly Auto Rejoin sequence"); + TapKey(0x1B); // Escape + await Task.Delay(120, cancellationToken); + TapKey(0x4C); // L + await Task.Delay(120, cancellationToken); + TapKey(0x0D); // Enter + await Task.Delay(1400, cancellationToken); + + await ClickRecordedPointAsync(robloxWindow, new MacroPoint(-860, 694), 900, cancellationToken); // Disconnect + await ClickRecordedPointAsync(robloxWindow, new MacroPoint(-1892, 159), 900, cancellationToken); // Home + await ClickRecordedPointAsync(robloxWindow, new MacroPoint(-1735, 1031), 900, cancellationToken); // Select Rivals + await ClickRecordedPointAsync(robloxWindow, new MacroPoint(-1011, 372), 1800, cancellationToken); // Join Rivals + await ClickRecordedPointAsync(robloxWindow, new MacroPoint(-956, 954), 1200, cancellationToken); // Play + await DragRecordedPointsAsync(robloxWindow, new MacroPoint(-240, 389), new MacroPoint(-241, 951), cancellationToken); + await Task.Delay(600, cancellationToken); + await ClickRecordedPointAsync(robloxWindow, new MacroPoint(-977, 827), 700, cancellationToken); // Select FFA + await ClickRecordedPointAsync(robloxWindow, new MacroPoint(-790, 836), 500, cancellationToken); // Join } public static void TapKey(byte virtualKey) @@ -368,41 +212,42 @@ private static void TapChord(params byte[] keys) private static async Task MoveAndClickExactPointAsync(MacroPoint point, CancellationToken cancellationToken) { - string? autoHotkey = FindAutoHotkeyV2(); - if (autoHotkey is not null) + App.Logger.WriteLine("MacroAutomationService", $"Moving cursor to recorded screen point {point.X}, {point.Y}"); + App.Logger.WriteLine("MacroAutomationService", $"Clicking recorded screen point {point.X}, {point.Y}"); + SendMouseAtPoint(point, MouseEventLeftDown); + try { - await ClickWithAutoHotkeyAsync(autoHotkey, point, cancellationToken); - return; + await Task.Delay(25, cancellationToken); } - - App.Logger.WriteLine("MacroAutomationService", $"Moving cursor to recorded screen point {point.X}, {point.Y}"); - if (!SetCursorPos(point.X, point.Y)) - throw new InvalidOperationException("SleepStrap could not move the cursor to the selected weapon."); - - if (!GetCursorPos(out POINT actual) || actual.X != point.X || actual.Y != point.Y) - throw new InvalidOperationException($"Windows moved the cursor to {actual.X}, {actual.Y} instead of the recorded position {point.X}, {point.Y}."); - - await Task.Delay(45, cancellationToken); - - // The recorder only captures a hover position. Playback owns the click, so lock - // the cursor back onto that exact point immediately before pressing the button. - if (!SetCursorPos(point.X, point.Y) || - !GetCursorPos(out actual) || - actual.X != point.X || - actual.Y != point.Y) + finally { - throw new InvalidOperationException($"The cursor left the recorded position before SleepStrap could click {point.X}, {point.Y}."); + SendMouseButton(MouseEventLeftUp); } + } - App.Logger.WriteLine("MacroAutomationService", $"Clicking recorded screen point {point.X}, {point.Y}"); - SendMouseButton(0x0002); + private static async Task ClickRecordedPointAsync(IntPtr window, MacroPoint recordedPoint, int waitAfter, CancellationToken cancellationToken) + { + await MoveAndClickExactPointAsync(MapRecordedPointToWindow(recordedPoint, window), cancellationToken); + await Task.Delay(waitAfter, cancellationToken); + } + + private static async Task DragRecordedPointsAsync(IntPtr window, MacroPoint recordedStart, MacroPoint recordedEnd, CancellationToken cancellationToken) + { + MacroPoint start = MapRecordedPointToWindow(recordedStart, window); + MacroPoint end = MapRecordedPointToWindow(recordedEnd, window); + if (!SetCursorPos(start.X, start.Y)) + throw new InvalidOperationException("SleepStrap could not start the Auto Rejoin drag."); + await Task.Delay(80, cancellationToken); + SendMouseButton(MouseEventLeftDown); try { - await Task.Delay(25, cancellationToken); + await Task.Delay(120, cancellationToken); + SetCursorPos(end.X, end.Y); + await Task.Delay(100, cancellationToken); } finally { - SendMouseButton(0x0004); + SendMouseButton(MouseEventLeftUp); } } @@ -493,69 +338,33 @@ private static async Task NormalizeRobloxWindowAsync(IntPtr window, Cancellation } } - private static string? FindAutoHotkeyV2() + private static void SendMouseButton(uint flags) { - string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); - string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - string[] candidates = + INPUT[] inputs = { - Path.Combine(programFiles, "AutoHotkey", "v2", "AutoHotkey64.exe"), - Path.Combine(localAppData, "Programs", "AutoHotkey", "v2", "AutoHotkey64.exe") + new INPUT + { + Type = 0, + Data = new INPUTUNION + { + Mouse = new MOUSEINPUT { Flags = flags } + } + } }; - return candidates.FirstOrDefault(File.Exists); + if (SendInput(1, inputs, Marshal.SizeOf()) != 1) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Windows rejected the simulated mouse click."); } - private static async Task ClickWithAutoHotkeyAsync(string autoHotkey, MacroPoint point, CancellationToken cancellationToken) + private static void SendMouseAtPoint(MacroPoint point, uint buttonFlags) { - string scriptPath = Path.Combine(Path.GetTempPath(), $"SleepStrap-Macro-{Guid.NewGuid():N}.ahk"); - string script = - "#Requires AutoHotkey v2.0\r\n" + - "#SingleInstance Off\r\n" + - "CoordMode \"Mouse\", \"Screen\"\r\n" + - $"MouseMove {point.X}, {point.Y}, 0\r\n" + - "Sleep 45\r\n" + - "Click \"Down\"\r\n" + - "Sleep 25\r\n" + - "Click \"Up\"\r\n" + - "ExitApp\r\n"; - - try - { - await File.WriteAllTextAsync(scriptPath, script, new UTF8Encoding(false), cancellationToken); - App.Logger.WriteLine("MacroAutomationService", $"AutoHotkey moving and clicking recorded screen point {point.X}, {point.Y}"); + int virtualLeft = GetSystemMetrics(76); + int virtualTop = GetSystemMetrics(77); + int virtualWidth = Math.Max(2, GetSystemMetrics(78)); + int virtualHeight = Math.Max(2, GetSystemMetrics(79)); + int absoluteX = (int)Math.Round((point.X - virtualLeft) * 65535d / (virtualWidth - 1)); + int absoluteY = (int)Math.Round((point.Y - virtualTop) * 65535d / (virtualHeight - 1)); - var startInfo = new ProcessStartInfo(autoHotkey) - { - UseShellExecute = false, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden - }; - startInfo.ArgumentList.Add("/ErrorStdOut"); - startInfo.ArgumentList.Add(scriptPath); - - using Process process = Process.Start(startInfo) - ?? throw new InvalidOperationException("SleepStrap could not start AutoHotkey for the recorded click."); - await process.WaitForExitAsync(cancellationToken); - if (process.ExitCode != 0) - throw new InvalidOperationException($"AutoHotkey could not click the recorded position (exit code {process.ExitCode})."); - } - finally - { - try - { - if (File.Exists(scriptPath)) - File.Delete(scriptPath); - } - catch - { - // The temporary script is harmless and Windows will clear the temp folder later. - } - } - } - - private static void SendMouseButton(uint flags) - { INPUT[] inputs = { new INPUT @@ -563,12 +372,22 @@ private static void SendMouseButton(uint flags) Type = 0, Data = new INPUTUNION { - Mouse = new MOUSEINPUT { Flags = flags } + Mouse = new MOUSEINPUT + { + X = Math.Clamp(absoluteX, 0, 65535), + Y = Math.Clamp(absoluteY, 0, 65535), + Flags = MouseEventMove | MouseEventAbsolute | MouseEventVirtualDesk + } } + }, + new INPUT + { + Type = 0, + Data = new INPUTUNION { Mouse = new MOUSEINPUT { Flags = buttonFlags } } } }; - if (SendInput(1, inputs, Marshal.SizeOf()) != 1) + if (SendInput((uint)inputs.Length, inputs, Marshal.SizeOf()) != (uint)inputs.Length) throw new Win32Exception(Marshal.GetLastWin32Error(), "Windows rejected the simulated mouse click."); } @@ -689,9 +508,6 @@ private struct MOUSEINPUT [DllImport("user32.dll")] private static extern bool SetCursorPos(int x, int y); - [DllImport("user32.dll")] - private static extern bool GetCursorPos(out POINT point); - [DllImport("user32.dll", SetLastError = true)] private static extern bool GetClientRect(IntPtr window, out RECT rectangle); @@ -716,6 +532,9 @@ private struct MOUSEINPUT [DllImport("user32.dll", SetLastError = true)] private static extern uint SendInput(uint inputCount, INPUT[] inputs, int inputSize); + [DllImport("user32.dll")] + private static extern int GetSystemMetrics(int index); + [DllImport("user32.dll")] private static extern void keybd_event(byte virtualKey, byte scanCode, uint flags, UIntPtr extraInfo); } diff --git a/SleepStrap/SleepStrap.csproj b/SleepStrap/SleepStrap.csproj index 08e5119..13132a8 100644 --- a/SleepStrap/SleepStrap.csproj +++ b/SleepStrap/SleepStrap.csproj @@ -7,8 +7,8 @@ true True SleepStrap.ico - 6.9.4.0 - 6.9.4.0 + 6.9.5.0 + 6.9.5.0 app.manifest true x64 diff --git a/SleepStrap/UI/Elements/Settings/Pages/MacroPage.xaml b/SleepStrap/UI/Elements/Settings/Pages/MacroPage.xaml index 9bd3e14..0ef0954 100644 --- a/SleepStrap/UI/Elements/Settings/Pages/MacroPage.xaml +++ b/SleepStrap/UI/Elements/Settings/Pages/MacroPage.xaml @@ -6,8 +6,7 @@ xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" xmlns:dmodels="clr-namespace:SleepStrap.UI.ViewModels.Settings" mc:Ignorable="d" d:DataContext="{d:DesignInstance dmodels:MacroViewModel, IsDesignTimeCreatable=False}" - d:DesignHeight="700" d:DesignWidth="800" Title="Macro" Scrollable="True" - PreviewKeyDown="Page_PreviewKeyDown"> + d:DesignHeight="700" d:DesignWidth="800" Title="Macro" Scrollable="True"> @@ -104,13 +103,7 @@ - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SleepStrap/UI/Elements/Settings/Pages/MacroPage.xaml.cs b/SleepStrap/UI/Elements/Settings/Pages/MacroPage.xaml.cs index ae5504c..dda0b1d 100644 --- a/SleepStrap/UI/Elements/Settings/Pages/MacroPage.xaml.cs +++ b/SleepStrap/UI/Elements/Settings/Pages/MacroPage.xaml.cs @@ -1,6 +1,3 @@ -using System.Windows.Controls; -using System.Windows.Input; - using SleepStrap.UI.ViewModels.Settings; namespace SleepStrap.UI.Elements.Settings.Pages @@ -8,9 +5,6 @@ namespace SleepStrap.UI.Elements.Settings.Pages public partial class MacroPage { private readonly MacroViewModel _viewModel; - private bool _capturingQuickHotkey; - private int _pendingModifiers; - private int _pendingVirtualKey; public MacroPage() { @@ -18,86 +12,5 @@ public MacroPage() DataContext = _viewModel; InitializeComponent(); } - - private void Page_PreviewKeyDown(object sender, KeyEventArgs e) - { - Key key = e.Key == Key.System ? e.SystemKey : e.Key; - bool sevenDown = Keyboard.IsKeyDown(Key.D7) || Keyboard.IsKeyDown(Key.NumPad7); - bool revealChord = ((key == Key.D7 || key == Key.NumPad7) && Keyboard.IsKeyDown(Key.U)) || - (key == Key.U && sevenDown); - if (!revealChord) - return; - - _viewModel.RevealQuickLoadout(); - e.Handled = true; - } - - private void QuickHotkeyBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) - { - _capturingQuickHotkey = true; - _viewModel.SetQuickHotkeyCaptureActive(true); - _pendingModifiers = 0; - _pendingVirtualKey = 0; - QuickHotkeyBox.Text = "Press keys, then Enter"; - QuickHotkeyBox.Focus(); - Keyboard.Focus(QuickHotkeyBox); - e.Handled = true; - } - - private void QuickHotkeyBox_PreviewKeyDown(object sender, KeyEventArgs e) - { - if (!_capturingQuickHotkey) - return; - - e.Handled = true; - Key key = e.Key == Key.System ? e.SystemKey : e.Key; - if (key == Key.Enter) - { - if (_pendingVirtualKey != 0) - _viewModel.SetQuickLoadoutHotkey(_pendingModifiers, _pendingVirtualKey); - FinishQuickHotkeyCapture(); - return; - } - if (key == Key.Escape) - { - FinishQuickHotkeyCapture(); - return; - } - if (key is Key.LeftAlt or Key.RightAlt or Key.LeftCtrl or Key.RightCtrl or Key.LeftShift or Key.RightShift or Key.LWin or Key.RWin) - return; - - ModifierKeys modifiers = Keyboard.Modifiers; - _pendingModifiers = 0; - if (modifiers.HasFlag(ModifierKeys.Alt)) _pendingModifiers |= 1; - if (modifiers.HasFlag(ModifierKeys.Control)) _pendingModifiers |= 2; - if (modifiers.HasFlag(ModifierKeys.Shift)) _pendingModifiers |= 4; - if (modifiers.HasFlag(ModifierKeys.Windows)) _pendingModifiers |= 8; - _pendingVirtualKey = KeyInterop.VirtualKeyFromKey(key); - QuickHotkeyBox.Text = FormatHotkey(key, modifiers) + " • press Enter"; - } - - private void QuickHotkeyBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e) - { - if (_capturingQuickHotkey) - FinishQuickHotkeyCapture(); - } - - private void FinishQuickHotkeyCapture() - { - _capturingQuickHotkey = false; - _viewModel.SetQuickHotkeyCaptureActive(false); - QuickHotkeyBox.GetBindingExpression(TextBox.TextProperty)?.UpdateTarget(); - } - - private static string FormatHotkey(Key key, ModifierKeys modifiers) - { - var parts = new List(); - if (modifiers.HasFlag(ModifierKeys.Control)) parts.Add("Ctrl"); - if (modifiers.HasFlag(ModifierKeys.Alt)) parts.Add("Alt"); - if (modifiers.HasFlag(ModifierKeys.Shift)) parts.Add("Shift"); - if (modifiers.HasFlag(ModifierKeys.Windows)) parts.Add("Win"); - parts.Add(key.ToString()); - return String.Join(" + ", parts); - } } } diff --git a/SleepStrap/UI/ViewModels/Settings/MacroViewModel.cs b/SleepStrap/UI/ViewModels/Settings/MacroViewModel.cs index fc3978d..b95361d 100644 --- a/SleepStrap/UI/ViewModels/Settings/MacroViewModel.cs +++ b/SleepStrap/UI/ViewModels/Settings/MacroViewModel.cs @@ -70,11 +70,6 @@ public WeaponOption(string name, MacroWeaponCategory category, int originalIndex private bool _isRunning; private bool _automationMasterEnabled = true; private bool _masterKeyWasDown; - private bool _usingAutoHotkeyActions; - private bool _quickLoadoutVisible; - private bool _quickHotkeyWasDown; - private bool _quickLoadoutRunning; - private bool _quickHotkeyCaptureActive; private CancellationTokenSource? _macroCancellation; private DateTime? _nextAutoRejoinUtc; private bool _autoRejoinInProgress; @@ -82,10 +77,6 @@ public WeaponOption(string name, MacroWeaponCategory category, int originalIndex private WeaponOption? _selectedSecondary; private WeaponOption? _selectedMelee; private WeaponOption? _selectedUtility; - private WeaponOption? _quickSelectedPrimary; - private WeaponOption? _quickSelectedSecondary; - private WeaponOption? _quickSelectedMelee; - private WeaponOption? _quickSelectedUtility; public ObservableCollection PrimaryWeapons { get; } = new(); public ObservableCollection SecondaryWeapons { get; } = new(); @@ -98,20 +89,6 @@ public WeaponOption(string name, MacroWeaponCategory category, int originalIndex public ObservableCollection AvailableUtility { get; } = new(); public ICommand RunMacroCommand => _runMacroCommand; - public Visibility QuickLoadoutVisibility => _quickLoadoutVisible ? Visibility.Visible : Visibility.Collapsed; - public string QuickLoadoutHotkeyDisplay => FormatHotkey( - App.Settings.Prop.MacroQuickLoadoutHotkeyModifiers, - App.Settings.Prop.MacroQuickLoadoutHotkeyVirtualKey); - - public bool QuickLoadoutEnabled - { - get => App.Settings.Prop.MacroQuickLoadoutEnabled; - set - { - App.Settings.Prop.MacroQuickLoadoutEnabled = value; - SaveAndNotify(nameof(QuickLoadoutEnabled)); - } - } public bool IsRunning { @@ -122,7 +99,6 @@ private set OnPropertyChanged(nameof(IsRunning)); OnPropertyChanged(nameof(RunButtonText)); _runMacroCommand.NotifyCanExecuteChanged(); - ReconfigureAutomaticActions(); } } @@ -138,7 +114,6 @@ private set _automationMasterEnabled = value; OnPropertyChanged(nameof(AutomationMasterEnabled)); OnPropertyChanged(nameof(AutomationMasterText)); - ReconfigureAutomaticActions(); } } @@ -170,46 +145,22 @@ public WeaponOption? SelectedUtility set => SetSelection(ref _selectedUtility, value, nameof(SelectedUtility), selected => App.Settings.Prop.MacroUtilityWeapon = selected.Name); } - public WeaponOption? QuickSelectedPrimary - { - get => _quickSelectedPrimary; - set => SetSelection(ref _quickSelectedPrimary, value, nameof(QuickSelectedPrimary), selected => App.Settings.Prop.MacroQuickLoadoutPrimaryWeapon = selected.Name); - } - - public WeaponOption? QuickSelectedSecondary - { - get => _quickSelectedSecondary; - set => SetSelection(ref _quickSelectedSecondary, value, nameof(QuickSelectedSecondary), selected => App.Settings.Prop.MacroQuickLoadoutSecondaryWeapon = selected.Name); - } - - public WeaponOption? QuickSelectedMelee - { - get => _quickSelectedMelee; - set => SetSelection(ref _quickSelectedMelee, value, nameof(QuickSelectedMelee), selected => App.Settings.Prop.MacroQuickLoadoutMeleeWeapon = selected.Name); - } - - public WeaponOption? QuickSelectedUtility - { - get => _quickSelectedUtility; - set => SetSelection(ref _quickSelectedUtility, value, nameof(QuickSelectedUtility), selected => App.Settings.Prop.MacroQuickLoadoutUtilityWeapon = selected.Name); - } - public bool QuickRespawn { get => App.Settings.Prop.MacroQuickRespawn; - set { App.Settings.Prop.MacroQuickRespawn = value; SaveAndNotify(nameof(QuickRespawn)); ReconfigureAutomaticActions(); } + set { App.Settings.Prop.MacroQuickRespawn = value; SaveAndNotify(nameof(QuickRespawn)); } } public bool AutoUtility { get => App.Settings.Prop.MacroAutoUtility; - set { App.Settings.Prop.MacroAutoUtility = value; SaveAndNotify(nameof(AutoUtility)); ReconfigureAutomaticActions(); } + set { App.Settings.Prop.MacroAutoUtility = value; SaveAndNotify(nameof(AutoUtility)); } } public bool AutoInspect { get => App.Settings.Prop.MacroAutoInspect; - set { App.Settings.Prop.MacroAutoInspect = value; SaveAndNotify(nameof(AutoInspect)); ReconfigureAutomaticActions(); } + set { App.Settings.Prop.MacroAutoInspect = value; SaveAndNotify(nameof(AutoInspect)); } } public bool AutoRejoinEnabled @@ -270,18 +221,12 @@ public MacroViewModel() _selectedSecondary = FindSaved(AvailableSecondary, App.Settings.Prop.MacroSecondaryWeapon); _selectedMelee = FindSaved(AvailableMelee, App.Settings.Prop.MacroMeleeWeapon); _selectedUtility = FindSaved(AvailableUtility, App.Settings.Prop.MacroUtilityWeapon); - _quickSelectedPrimary = FindSaved(AvailablePrimary, App.Settings.Prop.MacroQuickLoadoutPrimaryWeapon); - _quickSelectedSecondary = FindSaved(AvailableSecondary, App.Settings.Prop.MacroQuickLoadoutSecondaryWeapon); - _quickSelectedMelee = FindSaved(AvailableMelee, App.Settings.Prop.MacroQuickLoadoutMeleeWeapon); - _quickSelectedUtility = FindSaved(AvailableUtility, App.Settings.Prop.MacroQuickLoadoutUtilityWeapon); - _runMacroCommand = new AsyncRelayCommand(RunMacroAsync, () => !IsRunning && AllSelectionsPresent()); _automationTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50) }; _automationTimer.Tick += AutomationTimer_Tick; _automationTimer.Start(); if (AutoRejoinEnabled) _nextAutoRejoinUtc = DateTime.UtcNow.AddHours(1); - ReconfigureAutomaticActions(); } private void AddCategory( @@ -295,31 +240,6 @@ private void AddCategory( destination.Add(new WeaponOption(name, category, index++, image, missing.Contains(name), MissingChanged)); } - public void RevealQuickLoadout() - { - if (_quickLoadoutVisible) - return; - - _quickLoadoutVisible = true; - OnPropertyChanged(nameof(QuickLoadoutVisibility)); - } - - public void SetQuickLoadoutHotkey(int modifiers, int virtualKey) - { - App.Settings.Prop.MacroQuickLoadoutHotkeyModifiers = modifiers; - App.Settings.Prop.MacroQuickLoadoutHotkeyVirtualKey = virtualKey; - App.Settings.Save(); - _quickHotkeyWasDown = false; - OnPropertyChanged(nameof(QuickLoadoutHotkeyDisplay)); - } - - public void SetQuickHotkeyCaptureActive(bool active) - { - _quickHotkeyCaptureActive = active; - if (active) - _quickHotkeyWasDown = false; - } - private void MissingChanged(WeaponOption changed) { App.Settings.Prop.MacroMissingWeapons = AllWeapons() @@ -387,21 +307,6 @@ private void RepairSelectionIfRemoved(WeaponOption removed) break; } - switch (removed.Category) - { - case MacroWeaponCategory.Primary when ReferenceEquals(_quickSelectedPrimary, removed): - SetSelectionOrClear(ref _quickSelectedPrimary, replacement, nameof(QuickSelectedPrimary), value => App.Settings.Prop.MacroQuickLoadoutPrimaryWeapon = value); - break; - case MacroWeaponCategory.Secondary when ReferenceEquals(_quickSelectedSecondary, removed): - SetSelectionOrClear(ref _quickSelectedSecondary, replacement, nameof(QuickSelectedSecondary), value => App.Settings.Prop.MacroQuickLoadoutSecondaryWeapon = value); - break; - case MacroWeaponCategory.Melee when ReferenceEquals(_quickSelectedMelee, removed): - SetSelectionOrClear(ref _quickSelectedMelee, replacement, nameof(QuickSelectedMelee), value => App.Settings.Prop.MacroQuickLoadoutMeleeWeapon = value); - break; - case MacroWeaponCategory.Utility when ReferenceEquals(_quickSelectedUtility, removed): - SetSelectionOrClear(ref _quickSelectedUtility, replacement, nameof(QuickSelectedUtility), value => App.Settings.Prop.MacroQuickLoadoutUtilityWeapon = value); - break; - } } private void RefreshEffectiveSlots() @@ -475,11 +380,6 @@ private async Task RunMacroAsync() private void AutomationTimer_Tick(object? sender, EventArgs e) { - bool quickHotkeyDown = IsQuickLoadoutHotkeyDown(); - if (quickHotkeyDown && !_quickHotkeyWasDown && !_quickLoadoutRunning && !IsRunning && AllQuickSelectionsPresent()) - _ = RunQuickLoadoutAsync(); - _quickHotkeyWasDown = quickHotkeyDown; - bool masterKeyDown = MacroAutomationService.IsKeyDown(0xDD); // ] / OEM close bracket if (masterKeyDown && !_masterKeyWasDown) { @@ -502,7 +402,7 @@ private void AutomationTimer_Tick(object? sender, EventArgs e) _ = RunHourlyAutoRejoinAsync(); } - if (_usingAutoHotkeyActions || !AutomationMasterEnabled || !MacroAutomationService.IsRobloxForeground()) + if (!AutomationMasterEnabled || _autoRejoinInProgress || !MacroAutomationService.IsRobloxForeground()) return; long now = Environment.TickCount64; @@ -523,46 +423,6 @@ private void AutomationTimer_Tick(object? sender, EventArgs e) } } - private bool IsQuickLoadoutHotkeyDown() - { - if (!QuickLoadoutEnabled || _quickHotkeyCaptureActive) - return false; - - int key = App.Settings.Prop.MacroQuickLoadoutHotkeyVirtualKey; - if (key == 0 || !MacroAutomationService.IsKeyDown(key)) - return false; - - int modifiers = App.Settings.Prop.MacroQuickLoadoutHotkeyModifiers; - if ((modifiers & 1) != 0 && !MacroAutomationService.IsKeyDown(0x12)) return false; - if ((modifiers & 2) != 0 && !MacroAutomationService.IsKeyDown(0x11)) return false; - if ((modifiers & 4) != 0 && !MacroAutomationService.IsKeyDown(0x10)) return false; - if ((modifiers & 8) != 0 && !MacroAutomationService.IsKeyDown(0x5B) && !MacroAutomationService.IsKeyDown(0x5C)) return false; - return true; - } - - private async Task RunQuickLoadoutAsync() - { - _quickLoadoutRunning = true; - try - { - var selections = new[] - { - CreateSelection(QuickSelectedPrimary!), CreateSelection(QuickSelectedSecondary!), - CreateSelection(QuickSelectedMelee!), CreateSelection(QuickSelectedUtility!) - }; - await MacroAutomationService.RunLoadoutAsync(selections, CancellationToken.None); - } - catch (Exception ex) - { - App.Logger.WriteException("MacroViewModel::RunQuickLoadout", ex); - Frontend.ShowMessageBox($"SleepStrap could not apply the quick loadout.\n\n{ex.Message}", MessageBoxImage.Error); - } - finally - { - _quickLoadoutRunning = false; - } - } - private void SetSelection(ref WeaponOption? field, WeaponOption? value, string propertyName, Action save) { if (value is null || ReferenceEquals(field, value)) @@ -607,40 +467,16 @@ private void SetSelectionOrClear(ref WeaponOption? field, WeaponOption? value, s private bool AllSelectionsPresent() => SelectedPrimary is not null && SelectedSecondary is not null && SelectedMelee is not null && SelectedUtility is not null; - private bool AllQuickSelectionsPresent() => QuickSelectedPrimary is not null && QuickSelectedSecondary is not null && QuickSelectedMelee is not null && QuickSelectedUtility is not null; - - private static string FormatHotkey(int modifiers, int virtualKey) - { - var parts = new List(); - if ((modifiers & 2) != 0) parts.Add("Ctrl"); - if ((modifiers & 1) != 0) parts.Add("Alt"); - if ((modifiers & 4) != 0) parts.Add("Shift"); - if ((modifiers & 8) != 0) parts.Add("Win"); - Key key = KeyInterop.KeyFromVirtualKey(virtualKey); - parts.Add(key == Key.None ? $"0x{virtualKey:X2}" : key.ToString()); - return String.Join(" + ", parts); - } - private void SaveAndNotify(string propertyName) { App.Settings.Save(); OnPropertyChanged(propertyName); } - private void ReconfigureAutomaticActions() - { - _usingAutoHotkeyActions = MacroAutomationService.ConfigureAutomaticActions( - QuickRespawn, - AutoUtility, - AutoInspect, - AutomationMasterEnabled && !_autoRejoinInProgress); - } - private async Task RunHourlyAutoRejoinAsync() { bool resumeRepeatingLoadout = IsRunning; _autoRejoinInProgress = true; - ReconfigureAutomaticActions(); try { @@ -661,7 +497,6 @@ private async Task RunHourlyAutoRejoinAsync() finally { _autoRejoinInProgress = false; - ReconfigureAutomaticActions(); if (resumeRepeatingLoadout && AutomationMasterEnabled && AllSelectionsPresent()) _ = RunMacroAsync(); } @@ -671,7 +506,6 @@ public void Dispose() { _automationTimer.Stop(); _automationTimer.Tick -= AutomationTimer_Tick; - MacroAutomationService.StopAutomaticActions(); } } }