Rewrite to Go: engine, plugin system, D2R plugin, API, loot filter
This commit is contained in:
parent
e0282a7111
commit
3b363192f2
60 changed files with 1576 additions and 3407 deletions
103
pkg/engine/input/humanize.go
Normal file
103
pkg/engine/input/humanize.go
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// Package input — humanize.go provides human-like behavior randomization.
|
||||
package input
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math/rand"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HumanProfile defines behavior parameters for human-like input.
|
||||
type HumanProfile struct {
|
||||
ReactionMin time.Duration // min reaction delay
|
||||
ReactionMax time.Duration // max reaction delay
|
||||
MouseSpeedMin float64 // min pixels per second
|
||||
MouseSpeedMax float64 // max pixels per second
|
||||
ClickJitter int // pixel jitter on clicks
|
||||
HesitationChance float64 // chance of extra pause (0-1)
|
||||
HesitationMin time.Duration
|
||||
HesitationMax time.Duration
|
||||
}
|
||||
|
||||
// DefaultProfile returns a realistic human behavior profile.
|
||||
func DefaultProfile() HumanProfile {
|
||||
return HumanProfile{
|
||||
ReactionMin: 150 * time.Millisecond,
|
||||
ReactionMax: 450 * time.Millisecond,
|
||||
MouseSpeedMin: 400,
|
||||
MouseSpeedMax: 1200,
|
||||
ClickJitter: 3,
|
||||
HesitationChance: 0.1,
|
||||
HesitationMin: 300 * time.Millisecond,
|
||||
HesitationMax: 1200 * time.Millisecond,
|
||||
}
|
||||
}
|
||||
|
||||
// Humanizer applies human-like randomization to bot actions.
|
||||
type Humanizer struct {
|
||||
Profile HumanProfile
|
||||
actionCount atomic.Int64
|
||||
}
|
||||
|
||||
// NewHumanizer creates a humanizer with the given profile.
|
||||
func NewHumanizer(profile HumanProfile) *Humanizer {
|
||||
return &Humanizer{Profile: profile}
|
||||
}
|
||||
|
||||
// ReactionDelay returns a randomized human-like reaction delay.
|
||||
func (h *Humanizer) ReactionDelay() time.Duration {
|
||||
minMs := h.Profile.ReactionMin.Milliseconds()
|
||||
maxMs := h.Profile.ReactionMax.Milliseconds()
|
||||
base := time.Duration(minMs+rand.Int63n(maxMs-minMs)) * time.Millisecond
|
||||
|
||||
// Occasional hesitation
|
||||
if rand.Float64() < h.Profile.HesitationChance {
|
||||
hesMinMs := h.Profile.HesitationMin.Milliseconds()
|
||||
hesMaxMs := h.Profile.HesitationMax.Milliseconds()
|
||||
base += time.Duration(hesMinMs+rand.Int63n(hesMaxMs-hesMinMs)) * time.Millisecond
|
||||
}
|
||||
|
||||
// Slight fatigue factor
|
||||
actions := float64(h.actionCount.Load())
|
||||
fatigue := min(actions/1000.0, 0.3)
|
||||
base = time.Duration(float64(base) * (1 + fatigue*rand.Float64()))
|
||||
|
||||
return base
|
||||
}
|
||||
|
||||
// Wait pauses for a human-like reaction delay.
|
||||
func (h *Humanizer) Wait() {
|
||||
time.Sleep(h.ReactionDelay())
|
||||
h.actionCount.Add(1)
|
||||
}
|
||||
|
||||
// WaitMs pauses for baseMs ± varianceMs with human randomization.
|
||||
func (h *Humanizer) WaitMs(baseMs, varianceMs int) {
|
||||
actual := baseMs + rand.Intn(2*varianceMs+1) - varianceMs
|
||||
if actual < 0 {
|
||||
actual = 0
|
||||
}
|
||||
time.Sleep(time.Duration(actual) * time.Millisecond)
|
||||
}
|
||||
|
||||
// JitterPosition adds random offset to a click position.
|
||||
func (h *Humanizer) JitterPosition(pos image.Point) image.Point {
|
||||
j := h.Profile.ClickJitter
|
||||
return image.Point{
|
||||
X: pos.X + rand.Intn(2*j+1) - j,
|
||||
Y: pos.Y + rand.Intn(2*j+1) - j,
|
||||
}
|
||||
}
|
||||
|
||||
// MouseSpeed returns a randomized mouse movement speed.
|
||||
func (h *Humanizer) MouseSpeed() float64 {
|
||||
return h.Profile.MouseSpeedMin + rand.Float64()*(h.Profile.MouseSpeedMax-h.Profile.MouseSpeedMin)
|
||||
}
|
||||
|
||||
func min(a, b float64) float64 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
162
pkg/engine/input/input.go
Normal file
162
pkg/engine/input/input.go
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
// Package input provides human-like mouse and keyboard simulation.
|
||||
//
|
||||
// Mouse movement uses Bézier curves with natural acceleration.
|
||||
// All inputs include randomized timing to mimic human behavior.
|
||||
// Platform-specific backends: SendInput (Windows), X11/uinput (Linux).
|
||||
package input
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MouseController handles human-like mouse movement and clicks.
|
||||
type MouseController struct {
|
||||
humanizer *Humanizer
|
||||
}
|
||||
|
||||
// NewMouseController creates a mouse controller with the given humanizer.
|
||||
func NewMouseController(h *Humanizer) *MouseController {
|
||||
return &MouseController{humanizer: h}
|
||||
}
|
||||
|
||||
// MoveTo moves the mouse to target using a Bézier curve.
|
||||
func (m *MouseController) MoveTo(target image.Point) {
|
||||
// Get current position
|
||||
current := m.GetPosition()
|
||||
|
||||
// Generate Bézier control points
|
||||
points := m.bezierPath(current, target)
|
||||
|
||||
// Animate along the path
|
||||
speed := m.humanizer.MouseSpeed()
|
||||
totalDist := m.pathLength(points)
|
||||
steps := int(totalDist / speed * 1000) // ms-based steps
|
||||
if steps < 5 {
|
||||
steps = 5
|
||||
}
|
||||
|
||||
for i := 1; i <= steps; i++ {
|
||||
t := float64(i) / float64(steps)
|
||||
// Ease in-out for natural acceleration
|
||||
t = m.easeInOut(t)
|
||||
p := m.evalBezier(points, t)
|
||||
m.setPosition(p)
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// Click performs a mouse click at the current position.
|
||||
func (m *MouseController) Click() {
|
||||
m.humanizer.Wait()
|
||||
// TODO: Platform-specific click (SendInput / X11)
|
||||
// Randomize hold duration
|
||||
holdMs := 50 + rand.Intn(80)
|
||||
time.Sleep(time.Duration(holdMs) * time.Millisecond)
|
||||
}
|
||||
|
||||
// ClickAt moves to position and clicks.
|
||||
func (m *MouseController) ClickAt(pos image.Point) {
|
||||
jittered := m.humanizer.JitterPosition(pos)
|
||||
m.MoveTo(jittered)
|
||||
m.Click()
|
||||
}
|
||||
|
||||
// RightClick performs a right mouse click.
|
||||
func (m *MouseController) RightClick() {
|
||||
m.humanizer.Wait()
|
||||
holdMs := 50 + rand.Intn(80)
|
||||
time.Sleep(time.Duration(holdMs) * time.Millisecond)
|
||||
}
|
||||
|
||||
// GetPosition returns current mouse position.
|
||||
func (m *MouseController) GetPosition() image.Point {
|
||||
// TODO: Platform-specific implementation
|
||||
return image.Point{}
|
||||
}
|
||||
|
||||
func (m *MouseController) setPosition(p image.Point) {
|
||||
// TODO: Platform-specific implementation
|
||||
}
|
||||
|
||||
// bezierPath generates a cubic Bézier curve with randomized control points.
|
||||
func (m *MouseController) bezierPath(start, end image.Point) [4]image.Point {
|
||||
dx := float64(end.X - start.X)
|
||||
dy := float64(end.Y - start.Y)
|
||||
|
||||
// Randomize control points for natural curvature
|
||||
cp1 := image.Point{
|
||||
X: start.X + int(dx*0.25+rand.Float64()*50-25),
|
||||
Y: start.Y + int(dy*0.25+rand.Float64()*50-25),
|
||||
}
|
||||
cp2 := image.Point{
|
||||
X: start.X + int(dx*0.75+rand.Float64()*50-25),
|
||||
Y: start.Y + int(dy*0.75+rand.Float64()*50-25),
|
||||
}
|
||||
|
||||
return [4]image.Point{start, cp1, cp2, end}
|
||||
}
|
||||
|
||||
// evalBezier evaluates a cubic Bézier curve at parameter t.
|
||||
func (m *MouseController) evalBezier(pts [4]image.Point, t float64) image.Point {
|
||||
u := 1 - t
|
||||
return image.Point{
|
||||
X: int(u*u*u*float64(pts[0].X) + 3*u*u*t*float64(pts[1].X) + 3*u*t*t*float64(pts[2].X) + t*t*t*float64(pts[3].X)),
|
||||
Y: int(u*u*u*float64(pts[0].Y) + 3*u*u*t*float64(pts[1].Y) + 3*u*t*t*float64(pts[2].Y) + t*t*t*float64(pts[3].Y)),
|
||||
}
|
||||
}
|
||||
|
||||
// easeInOut applies ease-in-out for natural mouse acceleration.
|
||||
func (m *MouseController) easeInOut(t float64) float64 {
|
||||
return t * t * (3 - 2*t)
|
||||
}
|
||||
|
||||
func (m *MouseController) pathLength(pts [4]image.Point) float64 {
|
||||
length := 0.0
|
||||
prev := pts[0]
|
||||
for i := 1; i <= 20; i++ {
|
||||
t := float64(i) / 20.0
|
||||
p := m.evalBezier(pts, t)
|
||||
dx := float64(p.X - prev.X)
|
||||
dy := float64(p.Y - prev.Y)
|
||||
length += math.Sqrt(dx*dx + dy*dy)
|
||||
prev = p
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
||||
// KeyboardController handles human-like keyboard input.
|
||||
type KeyboardController struct {
|
||||
humanizer *Humanizer
|
||||
}
|
||||
|
||||
// NewKeyboardController creates a keyboard controller.
|
||||
func NewKeyboardController(h *Humanizer) *KeyboardController {
|
||||
return &KeyboardController{humanizer: h}
|
||||
}
|
||||
|
||||
// PressKey presses and releases a key with human-like timing.
|
||||
func (k *KeyboardController) PressKey(key string) {
|
||||
k.humanizer.Wait()
|
||||
// TODO: Platform-specific key press
|
||||
holdMs := 30 + rand.Intn(70)
|
||||
time.Sleep(time.Duration(holdMs) * time.Millisecond)
|
||||
}
|
||||
|
||||
// TypeText types text with randomized inter-key delays.
|
||||
func (k *KeyboardController) TypeText(text string) {
|
||||
for _, ch := range text {
|
||||
k.PressKey(string(ch))
|
||||
delay := 30 + rand.Intn(120) // 30-150ms between keys
|
||||
time.Sleep(time.Duration(delay) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// HoldKey holds a key down for a duration.
|
||||
func (k *KeyboardController) HoldKey(key string, durationMs int) {
|
||||
// TODO: Platform-specific key down/up
|
||||
variance := rand.Intn(durationMs / 5)
|
||||
time.Sleep(time.Duration(durationMs+variance) * time.Millisecond)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue