quests and queststate work
This commit is contained in:
parent
94b460bbc8
commit
445ae1387c
27 changed files with 3815 additions and 179 deletions
300
src/Roboto.Input/SendInputController.cs
Normal file
300
src/Roboto.Input/SendInputController.cs
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Roboto.Core;
|
||||
using Serilog;
|
||||
|
||||
namespace Roboto.Input;
|
||||
|
||||
/// <summary>
|
||||
/// Fallback input controller using Win32 SendInput with KEYEVENTF_SCANCODE.
|
||||
/// Games read scan codes, so this works for POE2 without the Interception driver.
|
||||
/// </summary>
|
||||
public sealed partial class SendInputController : IInputController
|
||||
{
|
||||
private static readonly Random Rng = new();
|
||||
private readonly Humanizer? _humanizer;
|
||||
|
||||
public bool IsInitialized { get; private set; }
|
||||
|
||||
public SendInputController(Humanizer? humanizer = null)
|
||||
{
|
||||
_humanizer = humanizer;
|
||||
}
|
||||
|
||||
public bool Initialize()
|
||||
{
|
||||
IsInitialized = true;
|
||||
Log.Information("SendInput controller initialized (fallback)");
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Keyboard ──
|
||||
|
||||
public void KeyDown(ushort scanCode)
|
||||
{
|
||||
var input = MakeKeyScanInput(scanCode, keyUp: false);
|
||||
SendInput(1, [input], INPUT_SIZE);
|
||||
}
|
||||
|
||||
public void KeyUp(ushort scanCode)
|
||||
{
|
||||
var input = MakeKeyScanInput(scanCode, keyUp: true);
|
||||
SendInput(1, [input], INPUT_SIZE);
|
||||
}
|
||||
|
||||
public void KeyPress(ushort scanCode, int holdMs = 50)
|
||||
{
|
||||
if (_humanizer is not null)
|
||||
{
|
||||
if (_humanizer.ShouldThrottle()) return;
|
||||
holdMs = _humanizer.GaussianDelay(holdMs);
|
||||
_humanizer.RecordAction();
|
||||
}
|
||||
|
||||
KeyDown(scanCode);
|
||||
Thread.Sleep(holdMs);
|
||||
KeyUp(scanCode);
|
||||
}
|
||||
|
||||
// ── Mouse movement ──
|
||||
|
||||
public void MouseMoveTo(int x, int y)
|
||||
{
|
||||
SetCursorPos(x, y);
|
||||
}
|
||||
|
||||
public void MouseMoveBy(int dx, int dy)
|
||||
{
|
||||
if (!GetCursorPos(out var pt)) return;
|
||||
SetCursorPos(pt.X + dx, pt.Y + dy);
|
||||
}
|
||||
|
||||
public void SmoothMoveTo(int x, int y)
|
||||
{
|
||||
if (!GetCursorPos(out var pt)) { MouseMoveTo(x, y); return; }
|
||||
|
||||
var dx = (double)(x - pt.X);
|
||||
var dy = (double)(y - pt.Y);
|
||||
var distance = Math.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 15) { MouseMoveTo(x, y); return; }
|
||||
|
||||
var perpX = -dy / distance;
|
||||
var perpY = dx / distance;
|
||||
var spread = distance * 0.15;
|
||||
|
||||
var cp1X = pt.X + dx * 0.3 + perpX * (Rng.NextDouble() - 0.5) * spread;
|
||||
var cp1Y = pt.Y + dy * 0.3 + perpY * (Rng.NextDouble() - 0.5) * spread;
|
||||
var cp2X = pt.X + dx * 0.7 + perpX * (Rng.NextDouble() - 0.5) * spread;
|
||||
var cp2Y = pt.Y + dy * 0.7 + perpY * (Rng.NextDouble() - 0.5) * spread;
|
||||
|
||||
var steps = Math.Clamp((int)Math.Round(distance / 15), 10, 40);
|
||||
|
||||
for (var i = 1; i <= steps; i++)
|
||||
{
|
||||
var t = EaseInOutQuad((double)i / steps);
|
||||
var (bx, by) = CubicBezier(t, pt.X, pt.Y, cp1X, cp1Y, cp2X, cp2Y, x, y);
|
||||
MouseMoveTo((int)Math.Round(bx), (int)Math.Round(by));
|
||||
Thread.Sleep(2 + Rng.Next(3));
|
||||
}
|
||||
|
||||
MouseMoveTo(x, y);
|
||||
}
|
||||
|
||||
// ── Mouse clicks ──
|
||||
|
||||
public void LeftClick(int x, int y)
|
||||
{
|
||||
if (_humanizer is not null)
|
||||
{
|
||||
if (_humanizer.ShouldThrottle()) return;
|
||||
(x, y) = _humanizer.JitterPosition(x, y);
|
||||
Thread.Sleep(_humanizer.GaussianDelay(10));
|
||||
_humanizer.RecordAction();
|
||||
}
|
||||
SmoothMoveTo(x, y);
|
||||
Thread.Sleep(_humanizer is not null ? _humanizer.GaussianDelay(10) : 10);
|
||||
MouseClick(MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, _humanizer?.GaussianDelay(50) ?? 50);
|
||||
}
|
||||
|
||||
public void RightClick(int x, int y)
|
||||
{
|
||||
if (_humanizer is not null)
|
||||
{
|
||||
if (_humanizer.ShouldThrottle()) return;
|
||||
(x, y) = _humanizer.JitterPosition(x, y);
|
||||
Thread.Sleep(_humanizer.GaussianDelay(10));
|
||||
_humanizer.RecordAction();
|
||||
}
|
||||
SmoothMoveTo(x, y);
|
||||
Thread.Sleep(_humanizer is not null ? _humanizer.GaussianDelay(10) : 10);
|
||||
MouseClick(MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, _humanizer?.GaussianDelay(50) ?? 50);
|
||||
}
|
||||
|
||||
public void MiddleClick(int x, int y)
|
||||
{
|
||||
if (_humanizer is not null)
|
||||
{
|
||||
if (_humanizer.ShouldThrottle()) return;
|
||||
(x, y) = _humanizer.JitterPosition(x, y);
|
||||
Thread.Sleep(_humanizer.GaussianDelay(10));
|
||||
_humanizer.RecordAction();
|
||||
}
|
||||
SmoothMoveTo(x, y);
|
||||
Thread.Sleep(_humanizer is not null ? _humanizer.GaussianDelay(10) : 10);
|
||||
MouseClick(MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP, _humanizer?.GaussianDelay(50) ?? 50);
|
||||
}
|
||||
|
||||
public void LeftDown()
|
||||
{
|
||||
var input = MakeMouseInput(MOUSEEVENTF_LEFTDOWN);
|
||||
SendInput(1, [input], INPUT_SIZE);
|
||||
}
|
||||
|
||||
public void LeftUp()
|
||||
{
|
||||
var input = MakeMouseInput(MOUSEEVENTF_LEFTUP);
|
||||
SendInput(1, [input], INPUT_SIZE);
|
||||
}
|
||||
|
||||
public void RightDown()
|
||||
{
|
||||
var input = MakeMouseInput(MOUSEEVENTF_RIGHTDOWN);
|
||||
SendInput(1, [input], INPUT_SIZE);
|
||||
}
|
||||
|
||||
public void RightUp()
|
||||
{
|
||||
var input = MakeMouseInput(MOUSEEVENTF_RIGHTUP);
|
||||
SendInput(1, [input], INPUT_SIZE);
|
||||
}
|
||||
|
||||
// ── Private helpers ──
|
||||
|
||||
private void MouseClick(uint downFlag, uint upFlag, int holdMs)
|
||||
{
|
||||
var down = MakeMouseInput(downFlag);
|
||||
var up = MakeMouseInput(upFlag);
|
||||
SendInput(1, [down], INPUT_SIZE);
|
||||
Thread.Sleep(holdMs);
|
||||
SendInput(1, [up], INPUT_SIZE);
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
// ── Win32 SendInput P/Invoke ──
|
||||
|
||||
private const int INPUT_KEYBOARD = 1;
|
||||
private const int INPUT_MOUSE = 0;
|
||||
private const uint KEYEVENTF_SCANCODE = 0x0008;
|
||||
private const uint KEYEVENTF_KEYUP = 0x0002;
|
||||
private const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
|
||||
private const uint MOUSEEVENTF_LEFTUP = 0x0004;
|
||||
private const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;
|
||||
private const uint MOUSEEVENTF_RIGHTUP = 0x0010;
|
||||
private const uint MOUSEEVENTF_MIDDLEDOWN = 0x0020;
|
||||
private const uint MOUSEEVENTF_MIDDLEUP = 0x0040;
|
||||
|
||||
private static readonly int INPUT_SIZE = Marshal.SizeOf<INPUT>();
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct POINT { public int X; public int Y; }
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct INPUT
|
||||
{
|
||||
public int type;
|
||||
public INPUT_UNION union;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private struct INPUT_UNION
|
||||
{
|
||||
[FieldOffset(0)] public KEYBDINPUT ki;
|
||||
[FieldOffset(0)] public MOUSEINPUT mi;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct KEYBDINPUT
|
||||
{
|
||||
public ushort wVk;
|
||||
public ushort wScan;
|
||||
public uint dwFlags;
|
||||
public uint time;
|
||||
public nint dwExtraInfo;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MOUSEINPUT
|
||||
{
|
||||
public int dx;
|
||||
public int dy;
|
||||
public uint mouseData;
|
||||
public uint dwFlags;
|
||||
public uint time;
|
||||
public nint dwExtraInfo;
|
||||
}
|
||||
|
||||
private static INPUT MakeKeyScanInput(ushort scanCode, bool keyUp)
|
||||
{
|
||||
var flags = KEYEVENTF_SCANCODE;
|
||||
if (keyUp) flags |= KEYEVENTF_KEYUP;
|
||||
|
||||
return new INPUT
|
||||
{
|
||||
type = INPUT_KEYBOARD,
|
||||
union = new INPUT_UNION
|
||||
{
|
||||
ki = new KEYBDINPUT
|
||||
{
|
||||
wVk = 0,
|
||||
wScan = scanCode,
|
||||
dwFlags = flags,
|
||||
time = 0,
|
||||
dwExtraInfo = 0,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static INPUT MakeMouseInput(uint flags)
|
||||
{
|
||||
return new INPUT
|
||||
{
|
||||
type = INPUT_MOUSE,
|
||||
union = new INPUT_UNION
|
||||
{
|
||||
mi = new MOUSEINPUT
|
||||
{
|
||||
dwFlags = flags,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[LibraryImport("user32.dll", SetLastError = true)]
|
||||
private static partial uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool SetCursorPos(int x, int y);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool GetCursorPos(out POINT lpPoint);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue