poe2-bot/src/Automata.Game/InputSender.cs
2026-02-28 15:13:22 -05:00

406 lines
12 KiB
C#

using System.Runtime.InteropServices;
using Poe2Trade.Core;
namespace Poe2Trade.Game;
public class InputSender
{
private readonly int _screenWidth;
private readonly int _screenHeight;
private static readonly Random Rng = new();
public InputSender()
{
_screenWidth = InputNative.GetSystemMetrics(InputNative.SM_CXSCREEN);
_screenHeight = InputNative.GetSystemMetrics(InputNative.SM_CYSCREEN);
}
// Virtual key codes
public static class VK
{
public const int RETURN = 0x0D;
public const int CONTROL = 0x11;
public const int MENU = 0x12;
public const int SHIFT = 0x10;
public const int ESCAPE = 0x1B;
public const int TAB = 0x09;
public const int SPACE = 0x20;
public const int DELETE = 0x2E;
public const int BACK = 0x08;
public const int V = 0x56;
public const int A = 0x41;
public const int C = 0x43;
public const int I = 0x49;
public const int W = 0x57;
public const int S = 0x53;
public const int D = 0x44;
public const int E = 0x45;
public const int Q = 0x51;
public const int Z = 0x5A;
}
public async Task PressKey(int vkCode)
{
var scanCode = InputNative.MapVirtualKeyW((uint)vkCode, 0);
SendScanKeyDown(scanCode);
await Helpers.RandomDelay(30, 50);
SendScanKeyUp(scanCode);
await Helpers.RandomDelay(20, 40);
}
public async Task KeyDown(int vkCode)
{
var scanCode = InputNative.MapVirtualKeyW((uint)vkCode, 0);
SendScanKeyDown(scanCode);
await Helpers.RandomDelay(15, 30);
}
public async Task KeyUp(int vkCode)
{
var scanCode = InputNative.MapVirtualKeyW((uint)vkCode, 0);
SendScanKeyUp(scanCode);
await Helpers.RandomDelay(15, 30);
}
public async Task TypeText(string text)
{
foreach (var ch in text)
{
SendUnicodeChar(ch);
await Helpers.RandomDelay(20, 50);
}
}
public async Task Paste()
{
await KeyDown(VK.CONTROL);
await Helpers.Sleep(30);
await PressKey(VK.V);
await KeyUp(VK.CONTROL);
await Helpers.Sleep(50);
}
public async Task SelectAll()
{
await KeyDown(VK.CONTROL);
await Helpers.Sleep(30);
await PressKey(VK.A);
await KeyUp(VK.CONTROL);
await Helpers.Sleep(50);
}
public (int X, int Y) GetCursorPos()
{
InputNative.GetCursorPos(out var pt);
return (pt.X, pt.Y);
}
private void MoveMouseRaw(int x, int y)
{
var normalizedX = (int)Math.Round((double)x * 65535 / _screenWidth);
var normalizedY = (int)Math.Round((double)y * 65535 / _screenHeight);
SendMouseInput(normalizedX, normalizedY, 0,
InputNative.MOUSEEVENTF_MOVE | InputNative.MOUSEEVENTF_ABSOLUTE);
}
public async Task MoveMouse(int x, int y)
{
var (sx, sy) = GetCursorPos();
var dx = x - sx;
var dy = y - sy;
var distance = Math.Sqrt(dx * dx + dy * dy);
if (distance < 10)
{
MoveMouseRaw(x, y);
await Helpers.RandomDelay(10, 20);
return;
}
var perpX = -dy / distance;
var perpY = dx / distance;
var spread = distance * 0.15;
var cp1X = sx + dx * 0.3 + perpX * (Rng.NextDouble() - 0.5) * spread;
var cp1Y = sy + dy * 0.3 + perpY * (Rng.NextDouble() - 0.5) * spread;
var cp2X = sx + dx * 0.7 + perpX * (Rng.NextDouble() - 0.5) * spread;
var cp2Y = sy + dy * 0.7 + perpY * (Rng.NextDouble() - 0.5) * spread;
var steps = Math.Clamp((int)Math.Round(distance / 15), 12, 40);
for (var i = 1; i <= steps; i++)
{
var rawT = (double)i / steps;
var t = EaseInOutQuad(rawT);
var (px, py) = CubicBezier(t, sx, sy, cp1X, cp1Y, cp2X, cp2Y, x, y);
MoveMouseRaw((int)Math.Round(px), (int)Math.Round(py));
await Task.Delay(2 + Rng.Next(3));
}
MoveMouseRaw(x, y);
await Helpers.RandomDelay(5, 15);
}
public void LeftMouseDown() => SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_LEFTDOWN);
public void LeftMouseUp() => SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_LEFTUP);
public void RightMouseDown() => SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_RIGHTDOWN);
public void RightMouseUp() => SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_RIGHTUP);
public void MoveMouseInstant(int x, int y) => MoveMouseRaw(x, y);
public async Task MoveMouseFast(int x, int y)
{
var (sx, sy) = GetCursorPos();
var dx = x - sx;
var dy = y - sy;
var distance = Math.Sqrt(dx * dx + dy * dy);
if (distance < 10)
{
MoveMouseRaw(x, y);
return;
}
var perpX = -dy / distance;
var perpY = dx / distance;
var spread = distance * 0.15;
var cp1X = sx + dx * 0.3 + perpX * (Rng.NextDouble() - 0.5) * spread;
var cp1Y = sy + dy * 0.3 + perpY * (Rng.NextDouble() - 0.5) * spread;
var cp2X = sx + dx * 0.7 + perpX * (Rng.NextDouble() - 0.5) * spread;
var cp2Y = sy + dy * 0.7 + perpY * (Rng.NextDouble() - 0.5) * spread;
for (var i = 1; i <= 5; i++)
{
var t = EaseInOutQuad((double)i / 5);
var (px, py) = CubicBezier(t, sx, sy, cp1X, cp1Y, cp2X, cp2Y, x, y);
MoveMouseRaw((int)Math.Round(px), (int)Math.Round(py));
await Task.Delay(2);
}
MoveMouseRaw(x, y);
}
public async Task LeftClick(int x, int y)
{
await MoveMouse(x, y);
await Helpers.RandomDelay(20, 50);
SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_LEFTDOWN);
await Helpers.RandomDelay(15, 40);
SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_LEFTUP);
await Helpers.RandomDelay(15, 30);
}
public async Task RightClick(int x, int y)
{
await MoveMouse(x, y);
await Helpers.RandomDelay(20, 50);
SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_RIGHTDOWN);
await Helpers.RandomDelay(15, 40);
SendMouseInput(0, 0, 0, InputNative.MOUSEEVENTF_RIGHTUP);
await Helpers.RandomDelay(15, 30);
}
public async Task CtrlRightClick(int x, int y)
{
await KeyDown(VK.CONTROL);
await Helpers.RandomDelay(30, 60);
await RightClick(x, y);
await KeyUp(VK.CONTROL);
await Helpers.RandomDelay(30, 60);
}
public async Task CtrlLeftClick(int x, int y)
{
await KeyDown(VK.CONTROL);
await Helpers.RandomDelay(30, 60);
await LeftClick(x, y);
await KeyUp(VK.CONTROL);
await Helpers.RandomDelay(30, 60);
}
// -- Private helpers --
private void SendMouseInput(int dx, int dy, int mouseData, uint flags)
{
var input = new InputNative.INPUT
{
type = InputNative.INPUT_MOUSE,
u = new InputNative.InputUnion
{
mi = new InputNative.MOUSEINPUT
{
dx = dx, dy = dy,
mouseData = mouseData,
dwFlags = flags,
time = 0,
dwExtraInfo = UIntPtr.Zero
}
}
};
InputNative.SendInput(1, [input], Marshal.SizeOf<InputNative.INPUT>());
}
private void SendScanKeyDown(uint scanCode)
{
var input = new InputNative.INPUT
{
type = InputNative.INPUT_KEYBOARD,
u = new InputNative.InputUnion
{
ki = new InputNative.KEYBDINPUT
{
wVk = 0,
wScan = (ushort)scanCode,
dwFlags = InputNative.KEYEVENTF_SCANCODE,
time = 0,
dwExtraInfo = UIntPtr.Zero
}
}
};
InputNative.SendInput(1, [input], Marshal.SizeOf<InputNative.INPUT>());
}
private void SendScanKeyUp(uint scanCode)
{
var input = new InputNative.INPUT
{
type = InputNative.INPUT_KEYBOARD,
u = new InputNative.InputUnion
{
ki = new InputNative.KEYBDINPUT
{
wVk = 0,
wScan = (ushort)scanCode,
dwFlags = InputNative.KEYEVENTF_SCANCODE | InputNative.KEYEVENTF_KEYUP,
time = 0,
dwExtraInfo = UIntPtr.Zero
}
}
};
InputNative.SendInput(1, [input], Marshal.SizeOf<InputNative.INPUT>());
}
private void SendUnicodeChar(char ch)
{
var code = (ushort)ch;
var down = new InputNative.INPUT
{
type = InputNative.INPUT_KEYBOARD,
u = new InputNative.InputUnion
{
ki = new InputNative.KEYBDINPUT
{
wVk = 0, wScan = code,
dwFlags = InputNative.KEYEVENTF_UNICODE,
time = 0, dwExtraInfo = UIntPtr.Zero
}
}
};
var up = new InputNative.INPUT
{
type = InputNative.INPUT_KEYBOARD,
u = new InputNative.InputUnion
{
ki = new InputNative.KEYBDINPUT
{
wVk = 0, wScan = code,
dwFlags = InputNative.KEYEVENTF_UNICODE | InputNative.KEYEVENTF_KEYUP,
time = 0, dwExtraInfo = UIntPtr.Zero
}
}
};
InputNative.SendInput(1, [down], Marshal.SizeOf<InputNative.INPUT>());
InputNative.SendInput(1, [up], Marshal.SizeOf<InputNative.INPUT>());
}
private static double EaseInOutQuad(double t) =>
t < 0.5 ? 2 * t * t : 1 - Math.Pow(-2 * t + 2, 2) / 2;
private static (double X, double Y) CubicBezier(double t,
double p0x, double p0y, double p1x, double p1y,
double p2x, double p2y, double p3x, double p3y)
{
var u = 1 - t;
var u2 = u * u;
var u3 = u2 * u;
var t2 = t * t;
var t3 = t2 * t;
return (
u3 * p0x + 3 * u2 * t * p1x + 3 * u * t2 * p2x + t3 * p3x,
u3 * p0y + 3 * u2 * t * p1y + 3 * u * t2 * p2y + t3 * p3y
);
}
}
internal static partial class InputNative
{
public const uint INPUT_MOUSE = 0;
public const uint INPUT_KEYBOARD = 1;
public const uint KEYEVENTF_SCANCODE = 0x0008;
public const uint KEYEVENTF_KEYUP = 0x0002;
public const uint KEYEVENTF_UNICODE = 0x0004;
public const uint MOUSEEVENTF_MOVE = 0x0001;
public const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
public const uint MOUSEEVENTF_LEFTUP = 0x0004;
public const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;
public const uint MOUSEEVENTF_RIGHTUP = 0x0010;
public const uint MOUSEEVENTF_ABSOLUTE = 0x8000;
public const int SM_CXSCREEN = 0;
public const int SM_CYSCREEN = 1;
[StructLayout(LayoutKind.Sequential)]
public struct MOUSEINPUT
{
public int dx;
public int dy;
public int mouseData;
public uint dwFlags;
public uint time;
public UIntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public struct KEYBDINPUT
{
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public UIntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Explicit)]
public struct InputUnion
{
[FieldOffset(0)] public MOUSEINPUT mi;
[FieldOffset(0)] public KEYBDINPUT ki;
}
[StructLayout(LayoutKind.Sequential)]
public struct INPUT
{
public uint type;
public InputUnion u;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
[LibraryImport("user32.dll")]
public static partial uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
[LibraryImport("user32.dll")]
public static partial uint MapVirtualKeyW(uint uCode, uint uMapType);
[LibraryImport("user32.dll")]
public static partial int GetSystemMetrics(int nIndex);
[LibraryImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool GetCursorPos(out POINT lpPoint);
}