[Python] 纯文本查看 复制代码
# -*- coding: utf-8 -*-
"""
游戏专用鼠标控制模块
实现强大的鼠标控制功能,专门针对游戏环境优化,可绕过大多数游戏限制
支持多种底层API:SendInput、mouse_event、ctypes等
"""
import ctypes
from ctypes import wintypes, windll
import win32api
import win32con
import pyautogui
import time
import math
import numpy as np
from typing import Tuple, Optional, List
from collections import deque
from config_manager import config
# Windows API 常量
INPUT_MOUSE = 0
MOUSEEVENTF_MOVE = 0x0001
MOUSEEVENTF_ABSOLUTE = 0x8000
MOUSEEVENTF_VIRTUALDESK = 0x4000
# Windows API 结构体
class POINT(ctypes.Structure):
_fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)]
class MOUSEINPUT(ctypes.Structure):
_fields_ = [("dx", wintypes.LONG),
("dy", wintypes.LONG),
("mouseData", wintypes.DWORD),
("dwFlags", wintypes.DWORD),
("time", wintypes.DWORD),
("dwExtraInfo", ctypes.POINTER(wintypes.ULONG))]
class INPUT(ctypes.Structure):
class _INPUT(ctypes.Union):
_fields_ = [("mi", MOUSEINPUT)]
_anonymous_ = ("_input",)
_fields_ = [("type", wintypes.DWORD),
("_input", _INPUT)]
class MouseController:
def __init__(self):
"""初始化游戏专用鼠标控制器"""
# Windows API 函数
self.user32 = windll.user32
self.kernel32 = windll.kernel32
# 设置DPI感知,解决高DPI缩放问题
try:
self.user32.SetProcessDPIAware()
except:
# 如果失败,尝试使用更新的API
try:
ctypes.windll.shcore.SetProcessDpiAwareness(1)
except:
print("⚠️ 无法设置DPI感知,可能影响高分辨率下的精度")
# 获取实际屏幕尺寸和DPI缩放比例
self.screen_width = self.user32.GetSystemMetrics(0)
self.screen_height = self.user32.GetSystemMetrics(1)
# 获取DPI缩放比例
try:
hdc = self.user32.GetDC(0)
dpi_x = windll.gdi32.GetDeviceCaps(hdc, 88) # LOGPIXELSX
dpi_y = windll.gdi32.GetDeviceCaps(hdc, 90) # LOGPIXELSY
self.user32.ReleaseDC(0, hdc)
self.dpi_scale_x = dpi_x / 96.0
self.dpi_scale_y = dpi_y / 96.0
print(f"DPI缩放比例: X={self.dpi_scale_x:.2f}, Y={self.dpi_scale_y:.2f}")
except:
self.dpi_scale_x = 1.0
self.dpi_scale_y = 1.0
print("⚠️ 无法获取DPI缩放比例,使用默认值")
print(f"实际屏幕尺寸: {self.screen_width}x{self.screen_height}")
# 历史位置记录,用于预测
self.position_history = deque(maxlen=10)
# 控制参数(从配置加载)
self.sensitivity = config.get('mouse', 'sensitivity', 1.0)
self.acceleration = config.get('mouse', 'acceleration', 1.2)
self.smoothing = config.get('aiming', 'smoothing', 0.3)
self.prediction_enabled = config.get('aiming', 'enable_prediction', True)
self.prediction_factor = config.get('aiming', 'prediction_factor', 0.2)
self.min_threshold = config.get('aiming', 'min_threshold', 2)
self.dpi_correction_enabled = config.get('mouse', 'dpi_scale_correction', True)
# 运动状态
self.last_move_time = 0
self.current_velocity = np.array([0.0, 0.0])
# 优先使用SendInput方法(游戏兼容性最好)
self.primary_method = 'sendinput_relative'
self.backup_methods = [
'sendinput_absolute', # SendInput绝对移动 - 精度高
'mouse_event_relative', # mouse_event相对移动 - 传统方法
'ctypes_direct', # 直接ctypes调用 - 底层控制
]
print("游戏专用鼠标控制器初始化完成")
print(f"当前配置: 灵敏度={self.sensitivity}, 加速度={self.acceleration}, 平滑={self.smoothing}")
def _sendinput_relative_move(self, dx: int, dy: int) -> bool:
"""使用SendInput API进行相对移动 - 游戏兼容性最好"""
try:
# 确保输入值有效
if abs(dx) > 32767 or abs(dy) > 32767:
# 分段处理大幅移动
return self._sendinput_large_move(dx, dy)
# 创建输入结构
inputs = INPUT()
inputs.type = INPUT_MOUSE
inputs.mi.dx = int(dx)
inputs.mi.dy = int(dy)
inputs.mi.mouseData = 0
inputs.mi.dwFlags = MOUSEEVENTF_MOVE # 相对移动
inputs.mi.time = 0
inputs.mi.dwExtraInfo = None
# 发送输入
result = self.user32.SendInput(1, ctypes.byref(inputs), ctypes.sizeof(INPUT))
# 验证结果
if result == 1:
# 添加微小延迟确保输入被处理
time.sleep(0.001)
return True
else:
print(f"SendInput返回值异常: {result}")
return False
except Exception as e:
print(f"SendInput相对移动失败: {e}")
return False
def _sendinput_large_move(self, dx: int, dy: int) -> bool:
"""处理大幅度移动(分段发送)"""
try:
# 分段大小
max_step = 16000
while abs(dx) > max_step or abs(dy) > max_step:
step_dx = max(-max_step, min(max_step, dx))
step_dy = max(-max_step, min(max_step, dy))
if not self._sendinput_relative_move(step_dx, step_dy):
return False
dx -= step_dx
dy -= step_dy
time.sleep(0.001) # 短暂延迟
# 发送剩余移动
if dx != 0 or dy != 0:
return self._sendinput_relative_move(dx, dy)
return True
except Exception as e:
print(f"大幅移动失败: {e}")
return False
def _sendinput_absolute_move(self, x: int, y: int) -> bool:
"""使用SendInput API进行绝对移动"""
try:
# 获取屏幕尺寸
screen_width = self.user32.GetSystemMetrics(0)
screen_height = self.user32.GetSystemMetrics(1)
# 边界检查
x = max(0, min(x, screen_width - 1))
y = max(0, min(y, screen_height - 1))
# 转换为绝对坐标 (0-65535)
abs_x = int((x * 65535) / screen_width)
abs_y = int((y * 65535) / screen_height)
# 创建输入结构
inputs = INPUT()
inputs.type = INPUT_MOUSE
inputs.mi.dx = abs_x
inputs.mi.dy = abs_y
inputs.mi.mouseData = 0
inputs.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK
inputs.mi.time = 0
inputs.mi.dwExtraInfo = None
# 发送输入
result = self.user32.SendInput(1, ctypes.byref(inputs), ctypes.sizeof(INPUT))
if result == 1:
time.sleep(0.001) # 确保输入被处理
return True
else:
print(f"SendInput绝对移动返回值异常: {result}")
return False
except Exception as e:
print(f"SendInput绝对移动失败: {e}")
return False
def _mouse_event_relative_move(self, dx: int, dy: int) -> bool:
"""使用mouse_event API进行相对移动"""
try:
win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, int(dx), int(dy), 0, 0)
return True
except Exception as e:
print(f"mouse_event相对移动失败: {e}")
return False
def _ctypes_direct_move(self, dx: int, dy: int) -> bool:
"""使用底层ctypes直接调用"""
try:
# 直接调用user32.dll的mouse_event
windll.user32.mouse_event(1, int(dx), int(dy), 0, 0)
return True
except Exception as e:
print(f"ctypes直接调用失败: {e}")
return False
def _setcursorpos_move(self, x: int, y: int) -> bool:
"""使用SetCursorPos进行绝对移动"""
try:
win32api.SetCursorPos((int(x), int(y)))
return True
except Exception as e:
print(f"SetCursorPos移动失败: {e}")
return False
def _pyautogui_move(self, x: int, y: int, relative: bool = False) -> bool:
"""使用PyAutoGUI移动"""
try:
if relative:
current_x, current_y = self.get_mouse_position()
pyautogui.moveTo(current_x + x, current_y + y, duration=0)
else:
pyautogui.moveTo(x, y, duration=0)
return True
except Exception as e:
print(f"PyAutoGUI移动失败: {e}")
return False
def get_mouse_position(self) -> Tuple[int, int]:
"""获取当前鼠标位置"""
try:
return win32api.GetCursorPos()
except:
try:
return pyautogui.position()
except:
return (0, 0)
def move_mouse_relative(self, dx: int, dy: int) -> bool:
"""相对移动鼠标 - 优先使用SendInput"""
if abs(dx) < 0.1 and abs(dy) < 0.1:
return True
# 应用灵敏度和平滑
dx_smooth, dy_smooth = self.apply_smoothing(dx * self.sensitivity, dy * self.sensitivity)
dx, dy = int(dx_smooth), int(dy_smooth)
# 应用预测(如果启用)
if self.prediction_enabled:
dx_pred, dy_pred = self.predict_movement(dx, dy)
dx, dy = int(dx_pred), int(dy_pred)
# 首先尝试主要方法(SendInput相对移动)
if self._sendinput_relative_move(dx, dy):
self._record_successful_move()
return True
print("SendInput相对移动失败,尝试备用方法...")
# 尝试备用方法
for method in self.backup_methods:
success = False
if method == 'sendinput_absolute':
current_x, current_y = self.get_mouse_position()
success = self._sendinput_absolute_move(current_x + dx, current_y + dy)
elif method == 'mouse_event_relative':
success = self._mouse_event_relative_move(dx, dy)
elif method == 'ctypes_direct':
success = self._ctypes_direct_move(dx, dy)
if success:
self._record_successful_move()
return True
print("所有鼠标控制方法都失败了!")
return False
def _record_successful_move(self):
"""记录成功的移动"""
current_pos = self.get_mouse_position()
self.position_history.append((current_pos, time.time()))
self.last_move_time = time.time()
def set_mouse_position(self, x: int, y: int) -> bool:
"""设置鼠标绝对位置 - 优先使用相对移动"""
current_x, current_y = self.get_mouse_position()
dx, dy = x - current_x, y - current_y
# 优先使用相对移动(在游戏中更有效)
if self.move_mouse_relative(dx, dy):
return True
# 尝试绝对移动方法
methods = [
lambda: self._sendinput_absolute_move(x, y),
lambda: self._setcursorpos_move(x, y),
lambda: self._pyautogui_move(x, y, relative=False)
]
for method in methods:
try:
if method():
return True
except:
continue
return False
def smooth_move_to(self, target_x: int, target_y: int, duration: float = 0.1):
"""平滑移动到目标位置"""
start_x, start_y = self.get_mouse_position()
distance = math.sqrt((target_x - start_x)**2 + (target_y - start_y)**2)
if distance < self.min_threshold:
return
# 计算步数和每步的移动量
steps = max(1, int(distance / 10)) # 每10像素一步
step_time = duration / steps
for i in range(steps):
# 计算当前步的目标位置
progress = (i + 1) / steps
# 使用缓动函数使移动更自然
eased_progress = self._ease_out_cubic(progress)
current_x = start_x + (target_x - start_x) * eased_progress
current_y = start_y + (target_y - start_y) * eased_progress
# 移动到当前步的位置
step_dx = current_x - self.get_mouse_position()[0]
step_dy = current_y - self.get_mouse_position()[1]
if abs(step_dx) > 0.5 or abs(step_dy) > 0.5:
self.move_mouse_relative(int(step_dx), int(step_dy))
if i < steps - 1: # 最后一步不延迟
time.sleep(step_time)
def _ease_out_cubic(self, t: float) -> float:
"""三次缓动函数"""
return 1 - pow(1 - t, 3)
def apply_smoothing(self, dx: float, dy: float) -> Tuple[float, float]:
"""应用平滑算法"""
if self.smoothing <= 0:
return dx, dy
# 更新速度(指数平滑)
self.current_velocity = (
self.current_velocity * self.smoothing +
np.array([dx, dy]) * (1 - self.smoothing)
)
return float(self.current_velocity[0]), float(self.current_velocity[1])
def predict_movement(self, current_dx: float, current_dy: float) -> Tuple[float, float]:
"""预测性移动补偿"""
if not self.prediction_enabled or len(self.position_history) < 2:
return current_dx, current_dy
# 计算历史速度趋势
recent_positions = list(self.position_history)[-3:]
if len(recent_positions) < 2:
return current_dx, current_dy
velocities = []
for i in range(1, len(recent_positions)):
prev_pos, prev_time = recent_positions[i-1]
curr_pos, curr_time = recent_positions
dt = curr_time - prev_time
if dt > 0:
vx = (curr_pos[0] - prev_pos[0]) / dt
vy = (curr_pos[1] - prev_pos[1]) / dt
velocities.append((vx, vy))
if not velocities:
return current_dx, current_dy
# 计算平均速度
avg_vx = sum(v[0] for v in velocities) / len(velocities)
avg_vy = sum(v[1] for v in velocities) / len(velocities)
# 添加预测补偿
predicted_dx = current_dx + avg_vx * self.prediction_factor
predicted_dy = current_dy + avg_vy * self.prediction_factor
return predicted_dx, predicted_dy
def get_movement_stats(self) -> dict:
"""获取移动统计信息"""
return {
'sensitivity': self.sensitivity,
'acceleration': self.acceleration,
'smoothing': self.smoothing,
'prediction_enabled': self.prediction_enabled,
'prediction_factor': self.prediction_factor,
'min_threshold': self.min_threshold,
'history_size': len(self.position_history),
'primary_method': self.primary_method
}
def test_sendinput_capability(self) -> dict:
"""测试SendInput功能可用性"""
results = {
'sendinput_available': False,
'relative_move_test': False,
'absolute_move_test': False,
'large_move_test': False,
'error_messages': []
}
try:
# 测试基本SendInput可用性
if hasattr(self.user32, 'SendInput'):
results['sendinput_available'] = True
# 保存当前位置
original_pos = self.get_mouse_position()
# 测试小幅相对移动
if self._sendinput_relative_move(1, 1):
results['relative_move_test'] = True
# 恢复位置
self._sendinput_relative_move(-1, -1)
# 测试绝对移动
test_x, test_y = original_pos[0] + 5, original_pos[1] + 5
if self._sendinput_absolute_move(test_x, test_y):
results['absolute_move_test'] = True
# 恢复位置
self._sendinput_absolute_move(original_pos[0], original_pos[1])
# 测试大幅移动
if self._sendinput_large_move(100, 100):
results['large_move_test'] = True
# 恢复位置
self._sendinput_large_move(-100, -100)
except Exception as e:
results['error_messages'].append(str(e))
return results
def optimize_for_game(self):
"""针对游戏环境进行优化设置"""
print("正在优化游戏鼠标控制设置...")
# 测试SendInput功能
test_results = self.test_sendinput_capability()
print(f"SendInput测试结果: {test_results}")
if test_results['sendinput_available'] and test_results['relative_move_test']:
print("✓ SendInput相对移动可用 - 游戏兼容性最佳")
self.primary_method = 'sendinput_relative'
else:
print("✗ SendInput不可用,使用备用方法")
self.primary_method = 'mouse_event_relative'
# 游戏优化设置
self.sensitivity = config.get('mouse', 'sensitivity', 1.5) # 进一步提高灵敏度
self.smoothing = config.get('aiming', 'smoothing', 0.05) # 大幅降低平滑以提高响应速度
self.prediction_enabled = True # 启用预测
self.prediction_factor = 0.4 # 增强预测
print(f"优化完成: 主要方法={self.primary_method}, 灵敏度={self.sensitivity}")
return test_results
def aim_at_target(self, target_x: int, target_y: int, target_history: list = None) -> bool:
"""
瞄准目标位置(游戏专用,处理高DPI缩放)
Args:
target_x: 目标X坐标(屏幕坐标)
target_y: 目标Y坐标(屏幕坐标)
target_history: 目标历史位置(用于预测)
Returns:
是否成功瞄准
"""
try:
current_x, current_y = self.get_mouse_position()
# 调试信息:显示坐标和DPI缩放信息
print(f"DEBUG - 目标坐标: ({target_x}, {target_y})")
print(f"DEBUG - 当前鼠标: ({current_x}, {current_y})")
print(f"DEBUG - DPI缩放: X={self.dpi_scale_x:.2f}, Y={self.dpi_scale_y:.2f}")
print(f"DEBUG - 屏幕尺寸: {self.screen_width}x{self.screen_height}")
# 应用DPI缩放校正(如果启用)
if self.dpi_correction_enabled and (self.dpi_scale_x != 1.0 or self.dpi_scale_y != 1.0):
original_x, original_y = target_x, target_y
target_x = int(target_x / self.dpi_scale_x)
target_y = int(target_y / self.dpi_scale_y)
print(f"DEBUG - DPI校正: ({original_x}, {original_y}) -> ({target_x}, {target_y})")
# 计算移动距离
dx = target_x - current_x
dy = target_y - current_y
print(f"DEBUG - 移动向量: dx={dx}, dy={dy}, 距离={math.sqrt(dx*dx + dy*dy):.1f}")
# 检查是否在最小阈值内
distance = math.sqrt(dx*dx + dy*dy)
if distance < self.min_threshold:
return True
# 应用预测(如果有历史数据)
if self.prediction_enabled and target_history and len(target_history) > 1:
# 计算目标移动趋势
recent_positions = target_history[-3:] # 取最近3个位置
if len(recent_positions) >= 2:
# 计算平均速度
total_dx = total_dy = 0
for i in range(1, len(recent_positions)):
pos_dx = recent_positions[0] - recent_positions[i-1][0]
pos_dy = recent_positions[1] - recent_positions[i-1][1]
total_dx += pos_dx
total_dy += pos_dy
avg_dx = total_dx / (len(recent_positions) - 1)
avg_dy = total_dy / (len(recent_positions) - 1)
# 应用预测
predict_dx = avg_dx * self.prediction_factor
predict_dy = avg_dy * self.prediction_factor
dx += predict_dx
dy += predict_dy
# 应用灵敏度调整
dx *= self.sensitivity
dy *= self.sensitivity
# 应用平滑处理
dx_smooth, dy_smooth = self.apply_smoothing(dx, dy)
# 检查移动范围限制 - 适中的限制平衡速度和精度
max_single_move = 80 # 限制单次移动的最大像素(提高到80以增加移动速度)
if abs(dx_smooth) > max_single_move:
dx_smooth = max_single_move if dx_smooth > 0 else -max_single_move
if abs(dy_smooth) > max_single_move:
dy_smooth = max_single_move if dy_smooth > 0 else -max_single_move
# 添加最小移动阈值,避免微小抖动
min_move_threshold = self.min_threshold
total_distance = math.sqrt(dx_smooth*dx_smooth + dy_smooth*dy_smooth)
if total_distance < min_move_threshold:
return True # 距离太小,不需要移动
# 执行移动
return self.move_mouse_relative(int(dx_smooth), int(dy_smooth))
except Exception as e:
print(f"瞄准目标失败: {e}")
return False
def is_in_game_area(self, x: int, y: int) -> bool:
"""
检查坐标是否在游戏有效区域内
Args:
x, y: 屏幕坐标
Returns:
是否在游戏区域内
"""
# 排除屏幕边缘区域(可能是任务栏、边框等)
margin = 50
return (margin <= x <= self.screen_width - margin and
margin <= y <= self.screen_height - margin)
# 测试代码
if __name__ == "__main__":
print("初始化游戏专用鼠标控制器...")
controller = MouseController()
print(f"当前鼠标位置: {controller.get_mouse_position()}")
print(f"移动统计: {controller.get_movement_stats()}")
# 游戏优化
print("\n执行游戏优化...")
test_results = controller.optimize_for_game()
print(f"\n优化后统计: {controller.get_movement_stats()}")
# 测试SendInput功能
print("\n=== SendInput功能测试 ===")
for key, value in test_results.items():
status = "✓" if value else "✗"
if key != 'error_messages':
print(f"{status} {key}: {value}")
if test_results.get('error_messages'):
print(f"错误信息: {test_results['error_messages']}")
# 实际移动测试
print("\n=== 实际移动测试 ===")
print("测试相对移动(游戏友好)...")
success = controller.move_mouse_relative(50, 50)
print(f"相对移动结果: {'成功' if success else '失败'}")
time.sleep(1)
# 测试回到原位
print("测试返回...")
success = controller.move_mouse_relative(-50, -50)
print(f"返回移动结果: {'成功' if success else '失败'}")
# 测试平滑移动
print("\n测试平滑移动到屏幕中心...")
screen_width = controller.user32.GetSystemMetrics(0)
screen_height = controller.user32.GetSystemMetrics(1)
center_x, center_y = screen_width // 2, screen_height // 2
controller.smooth_move_to(center_x, center_y, duration=0.5)
print("平滑移动完成")
print("\n测试完成! 鼠标控制器已针对游戏环境优化。")