From 2b14ff1ae6578374c010b0181022d4aba6c688a5 Mon Sep 17 00:00:00 2001 From: yin1895 <3023001549@tju.edu.cn> Date: Sat, 27 Sep 2025 00:59:37 +0800 Subject: [PATCH] feat: Add driver-level mouse control and enhanced aiming system New Features: - Driver-level mouse control using Logitech GHUB/LGS drivers (ghub_device.dll) - Advanced aiming algorithms with smooth movement and predictive targeting - Enhanced configuration system with driver control options - Automatic fallback to system mouse when driver unavailable - Comprehensive setup assistant for driver installation Bug Fixes: - Fixed CapsLock detection using proper VK_CAPITAL toggle state - Resolved PyTorch tensor compatibility issues (view() -> reshape()) - Improved error handling and user feedback Technical Improvements: - Modular architecture with separate mouse driver interface - Real-time configuration hot-reload capability - Enhanced YOLO utility functions - Comprehensive game selection system Files Added/Modified: - mouse_driver.py: Core driver interface - ghub_move/ghub_device.dll: Logitech driver binary - setup_driver.py: Installation and configuration assistant - advanced_aiming.py: Enhanced targeting algorithms - smooth_aiming.py: Smooth movement system - config.py: Extended with driver control options - main_onnx.py: Integrated driver support and bug fixes - yolo_utils.py: Enhanced utility functions --- advanced_aiming.py | 344 ++++++++++++++++++++++++++++++++++ config.py | 17 +- ghub_move/ghub_device.dll | Bin 0 -> 36864 bytes hotkey_manager.py | 379 ++++++++++++++++++++++++++++++++++++++ main_onnx.py | 42 ++++- main_onnx.py.backup | 203 ++++++++++++++++++++ mouse_driver.py | 198 ++++++++++++++++++++ setup_driver.py | 162 ++++++++++++++++ smooth_aiming.py | 312 +++++++++++++++++++++++++++++++ yolo_utils.py | 110 +++++++++++ 10 files changed, 1759 insertions(+), 8 deletions(-) create mode 100644 advanced_aiming.py create mode 100644 ghub_move/ghub_device.dll create mode 100644 hotkey_manager.py create mode 100644 main_onnx.py.backup create mode 100644 mouse_driver.py create mode 100644 setup_driver.py create mode 100644 smooth_aiming.py create mode 100644 yolo_utils.py diff --git a/advanced_aiming.py b/advanced_aiming.py new file mode 100644 index 0000000..5088675 --- /dev/null +++ b/advanced_aiming.py @@ -0,0 +1,344 @@ +""" +精简高级瞄准系统 +保留核心功能:状态机锁定、人类行为模拟、智能目标选择 +""" +import time +import math +import random +from collections import deque +from typing import Optional, Tuple, List, Dict +from enum import Enum +from dataclasses import dataclass + +class LockState(Enum): + SEARCHING = "searching" + ACQUIRING = "acquiring" + LOCKED = "locked" + TRACKING = "tracking" + LOST = "lost" + +@dataclass +class TargetInfo: + x: float + y: float + width: float + height: float + confidence: float + velocity_x: float = 0.0 + velocity_y: float = 0.0 + last_seen: float = 0.0 + +class AdvancedAimingSystem: + def __init__(self): + self.lock_state = LockState.SEARCHING + self.current_target: Optional[TargetInfo] = None + self.state_start_time = time.time() + self.lock_start_time = 0.0 + + # 历史记录 + self.target_history = deque(maxlen=10) + self.move_history = deque(maxlen=5) + + # 平滑状态 + self.smoothed_x = 0.0 + self.smoothed_y = 0.0 + + # 性能统计 + self.stats = { + 'targets_locked': 0, + 'target_switches': 0, + 'reaction_times': [] + } + + def update(self, targets: List[Dict], screen_center: Tuple[int, int], + config) -> Optional[Tuple[float, float]]: + """主更新函数""" + current_time = time.time() + + # 状态机处理 + old_state = self.lock_state + movement = None + if self.lock_state == LockState.SEARCHING: + movement = self._handle_searching(targets, screen_center, config, current_time) + elif self.lock_state == LockState.ACQUIRING: + movement = self._handle_acquiring(targets, screen_center, config, current_time) + elif self.lock_state == LockState.LOCKED: + movement = self._handle_locked(targets, screen_center, config, current_time) + elif self.lock_state == LockState.TRACKING: + movement = self._handle_tracking(targets, screen_center, config, current_time) + elif self.lock_state == LockState.LOST: + movement = self._handle_lost(current_time) + + # 调试信息 + if getattr(config, 'cpsDisplay', False): + if old_state != self.lock_state: + print(f"状态转换: {old_state.value} -> {self.lock_state.value}") + if movement: + print(f"状态 {self.lock_state.value}: 移动 {movement}") + + return movement + + def _handle_searching(self, targets: List[Dict], screen_center: Tuple[int, int], + config, current_time: float) -> Optional[Tuple[float, float]]: + """搜索状态""" + if not targets: + return None + + # 选择最佳目标(距离优先) + best_target = min(targets, key=lambda t: math.sqrt( + (t['current_mid_x'] - screen_center[0])**2 + + (t['current_mid_y'] - screen_center[1])**2 + )) + + # 开始获取目标 + self.current_target = TargetInfo( + x=best_target['current_mid_x'], + y=best_target['current_mid_y'], + width=best_target['width'], + height=best_target['height'], + confidence=best_target['confidence'], + velocity_x=0.0, # 显式初始化速度 + velocity_y=0.0, + last_seen=current_time + ) + + self.lock_state = LockState.ACQUIRING + self.state_start_time = current_time + + # 模拟人类反应延迟 + reaction_delay = getattr(config, 'humanReactionTime', 0.15) + self.lock_acquisition_time = current_time + reaction_delay + + return None + + def _handle_acquiring(self, targets: List[Dict], screen_center: Tuple[int, int], + config, current_time: float) -> Optional[Tuple[float, float]]: + """获取锁定状态""" + # 反应延迟检查 + if current_time < self.lock_acquisition_time: + return None + + # 查找当前目标 + current_target = self._find_target(targets, max_distance=50) + if not current_target: + self._transition_to_lost() + return None + + # 更新目标信息 + self._update_target(current_target, current_time) + + # 计算距离 + distance = math.sqrt( + (self.current_target.x - screen_center[0])**2 + + (self.current_target.y - screen_center[1])**2 + ) + + # 距离足够近则锁定 (增大锁定距离阈值) + if distance < 50: # 从20增加到50像素 + self.lock_state = LockState.LOCKED + self.lock_start_time = current_time + self.stats['targets_locked'] += 1 + + # 记录反应时间 + reaction_time = current_time - self.state_start_time + self.stats['reaction_times'].append(reaction_time) + + # 计算瞄准移动 + return self._calculate_movement(screen_center, config, acquiring=True) + + def _handle_locked(self, targets: List[Dict], screen_center: Tuple[int, int], + config, current_time: float) -> Optional[Tuple[float, float]]: + """锁定状态""" + current_target = self._find_target(targets, max_distance=30) + if not current_target: + self._transition_to_lost() + return None + + self._update_target(current_target, current_time) + + # 检查目标是否移动 + speed = math.sqrt(self.current_target.velocity_x**2 + self.current_target.velocity_y**2) + if speed > 25: + self.lock_state = LockState.TRACKING + + # 反检测:最大锁定时间 + max_lock_time = getattr(config, 'maxContinuousFrames', 120) / 60.0 # 转换为秒 + if current_time - self.lock_start_time > max_lock_time: + self._transition_to_lost() + return None + + return self._calculate_movement(screen_center, config) + + def _handle_tracking(self, targets: List[Dict], screen_center: Tuple[int, int], + config, current_time: float) -> Optional[Tuple[float, float]]: + """跟踪状态""" + current_target = self._find_target(targets, max_distance=40) + if not current_target: + self._transition_to_lost() + return None + + self._update_target(current_target, current_time) + + # 检查目标是否停止 + speed = math.sqrt(self.current_target.velocity_x**2 + self.current_target.velocity_y**2) + if speed < 15: + self.lock_state = LockState.LOCKED + self.lock_start_time = current_time + + return self._calculate_movement(screen_center, config, tracking=True) + + def _handle_lost(self, current_time: float) -> Optional[Tuple[float, float]]: + """丢失状态""" + self.current_target = None + + # 短暂暂停后重新搜索 + if current_time - self.state_start_time > 0.3: + self.lock_state = LockState.SEARCHING + self.state_start_time = current_time + + return None + + def _calculate_movement(self, screen_center: Tuple[int, int], config, + acquiring: bool = False, tracking: bool = False) -> Tuple[float, float]: + """计算瞄准移动""" + if not self.current_target: + return 0.0, 0.0 + + # 目标位置 + target_x = self.current_target.x + target_y = self.current_target.y + + # 应用瞄准偏移 + aim_offset = getattr(config, 'aimOffsetRatio', 0.4) + y_offset = (aim_offset - 0.5) * self.current_target.height + target_y += y_offset + + # 运动预测 + if tracking and getattr(config, 'enablePredictiveAim', True): + pred_strength = getattr(config, 'predictionStrength', 0.3) + target_x += self.current_target.velocity_x * pred_strength * 0.1 + target_y += self.current_target.velocity_y * pred_strength * 0.1 + + # 原始移动量 + raw_x = target_x - screen_center[0] + raw_y = target_y - screen_center[1] + + # 人类化微调 + if random.random() < 0.3: # 30%概率添加微调 + raw_x += random.uniform(-0.5, 0.5) + raw_y += random.uniform(-0.5, 0.5) + + # 平滑处理 + smooth_factor = getattr(config, 'smoothingFactor', 0.6) + if acquiring: + smooth_factor *= 0.7 # 获取时减少平滑 + elif tracking: + smooth_factor *= 0.8 # 跟踪时中等平滑 + + self.smoothed_x = self.smoothed_x * smooth_factor + raw_x * (1 - smooth_factor) + self.smoothed_y = self.smoothed_y * smooth_factor + raw_y * (1 - smooth_factor) + + # 移动阈值 (降低阈值,让小距离移动也能生效) + threshold = getattr(config, 'movementThreshold', 0.5) # 从1.0降到0.5 + distance = math.sqrt(self.smoothed_x**2 + self.smoothed_y**2) + + if distance < threshold: + return 0.0, 0.0 + + return self.smoothed_x, self.smoothed_y + + def _find_target(self, targets: List[Dict], max_distance: float) -> Optional[Dict]: + """在列表中查找当前目标""" + if not self.current_target or not targets: + return None + + for target in targets: + distance = math.sqrt( + (target['current_mid_x'] - self.current_target.x)**2 + + (target['current_mid_y'] - self.current_target.y)**2 + ) + if distance <= max_distance: + return target + + return None + + def _update_target(self, target_dict: Dict, current_time: float): + """更新目标信息""" + if not self.current_target: + return + + # 保存旧位置用于速度计算 + old_x, old_y = self.current_target.x, self.current_target.y + old_time = self.current_target.last_seen + + # 先更新位置 + self.current_target.x = target_dict['current_mid_x'] + self.current_target.y = target_dict['current_mid_y'] + self.current_target.width = target_dict['width'] + self.current_target.height = target_dict['height'] + self.current_target.confidence = target_dict['confidence'] + self.current_target.last_seen = current_time + + # 计算速度(使用新旧位置差) + dt = current_time - old_time + if dt > 0 and dt < 1.0: # 防止异常大的时间间隔 + self.current_target.velocity_x = (self.current_target.x - old_x) / dt + self.current_target.velocity_y = (self.current_target.y - old_y) / dt + else: + # 时间间隔异常,重置速度 + self.current_target.velocity_x = 0.0 + self.current_target.velocity_y = 0.0 + + def _transition_to_lost(self): + """转换到丢失状态""" + self.lock_state = LockState.LOST + self.state_start_time = time.time() + self.stats['target_switches'] += 1 + + # 重置平滑状态 + self.smoothed_x = 0.0 + self.smoothed_y = 0.0 + + def get_stats(self) -> Dict: + """获取统计信息""" + return { + 'current_state': self.lock_state.value, + 'targets_locked': self.stats['targets_locked'], + 'target_switches': self.stats['target_switches'], + 'avg_reaction_time': sum(self.stats['reaction_times']) / len(self.stats['reaction_times']) if self.stats['reaction_times'] else 0 + } + +# 全局实例 +aiming_system = AdvancedAimingSystem() + +def get_advanced_aiming_movement(targets_df, screen_center_x: int, screen_center_y: int, config) -> Optional[Tuple[float, float]]: + """获取高级瞄准移动量""" + # 转换数据格式 + targets = [] + if not targets_df.empty: + for _, row in targets_df.iterrows(): + targets.append({ + 'current_mid_x': row['current_mid_x'], + 'current_mid_y': row['current_mid_y'], + 'width': row['width'], + 'height': row['height'], + 'confidence': row['confidence'] + }) + + screen_center = (screen_center_x, screen_center_y) + + # 调试信息 + if getattr(config, 'cpsDisplay', False): + print(f"瞄准系统输入: 目标数={len(targets)}, 屏幕中心={screen_center}") + if targets: + closest_target = min(targets, key=lambda t: math.sqrt((t['current_mid_x'] - screen_center[0])**2 + (t['current_mid_y'] - screen_center[1])**2)) + distance = math.sqrt((closest_target['current_mid_x'] - screen_center[0])**2 + (closest_target['current_mid_y'] - screen_center[1])**2) + print(f"最近目标: 位置=({closest_target['current_mid_x']:.1f}, {closest_target['current_mid_y']:.1f}), 距离={distance:.1f}") + + # 更新瞄准系统 + result = aiming_system.update(targets, screen_center, config) + + if getattr(config, 'cpsDisplay', False) and result: + print(f"瞄准系统输出: 移动量=({result[0]:.2f}, {result[1]:.2f})") + + return result \ No newline at end of file diff --git a/config.py b/config.py index 43985c4..7102c20 100644 --- a/config.py +++ b/config.py @@ -33,4 +33,19 @@ # 1 - CPU # 2 - AMD # 3 - NVIDIA -onnxChoice = 1 \ No newline at end of file +onnxChoice = 1 +# ======================================== +# Enhanced Features by yin1895 +# ======================================== + +# ========== Driver Mouse Configuration ========== +useDriverMouse = True # Use driver-level control (requires Logitech GHUB/LGS) +fallbackToSystemMouse = True # Fallback to system-level control when driver fails + +# ========== Advanced Aiming Configuration ========== +enableAdvancedAiming = False # Enable advanced aiming system +enableSmoothMovement = True # Enable smooth movement +smoothingFactor = 0.6 # Smoothing factor (0.1-0.9) +movementThreshold = 2 # Movement threshold (pixels) +enablePredictiveAim = False # Enable predictive aiming +predictionStrength = 0.6 # Prediction strength diff --git a/ghub_move/ghub_device.dll b/ghub_move/ghub_device.dll new file mode 100644 index 0000000000000000000000000000000000000000..82a950b70edaef2babba14e5704a6f6a5bd4c9ef GIT binary patch literal 36864 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 J^Zz9a{J*Io)RzDN literal 0 HcmV?d00001 diff --git a/hotkey_manager.py b/hotkey_manager.py new file mode 100644 index 0000000..1283d6f --- /dev/null +++ b/hotkey_manager.py @@ -0,0 +1,379 @@ +""" +热键管理系统 +支持键盘和鼠标热键,组合键,以及运行时参数调整 +""" +import win32api +import win32con +import time +from typing import Dict, Any, Callable, Optional + +class GlobalHotkeyManager: + """全局热键管理器""" + + def __init__(self, config_module): + """ + 初始化热键管理器 + + Args: + config_module: 配置模块对象 + """ + self.config = config_module + self.hotkey_states = {} + self.last_trigger_time = {} + + # 热键回调函数映射 + self.hotkey_callbacks = {} + + # 运行时可调参数的当前值 + self.runtime_values = { + 'sensitivity': getattr(config_module, 'aaMovementAmp', 0.75), + 'confidence': getattr(config_module, 'confidence', 0.4), + 'visuals_enabled': getattr(config_module, 'visuals', True), + 'debug_enabled': getattr(config_module, 'cpsDisplay', True), + 'aim_mode_advanced': getattr(config_module, 'enableAdvancedAiming', False), + 'headshot_mode': getattr(config_module, 'headshot_mode', False), + 'smooth_mode': getattr(config_module, 'enableSmoothMovement', True) + } + + # 初始化热键配置 + self._init_hotkeys() + + print("[热键管理] 初始化完成") + print("[热键管理] 支持的热键类型: 键盘按键, 鼠标按键, 组合键") + + def _init_hotkeys(self): + """初始化热键配置和回调""" + # 从配置中获取热键设置 + hotkeys_config = { + 'quit': getattr(self.config, 'aaQuitKey', 'P'), + 'toggle_visuals': getattr(self.config, 'aaToggleVisualsKey', 'V'), + 'toggle_debug': getattr(self.config, 'aaToggleDebugKey', 'F1'), + 'aim_mode_toggle': getattr(self.config, 'aimModeToggleKey', 'F2'), + 'headshot_toggle': getattr(self.config, 'headshotModeToggleKey', 'F3'), + 'smooth_toggle': getattr(self.config, 'smoothModeToggleKey', 'F4'), + 'sensitivity_up': getattr(self.config, 'sensitivityUpKey', 'NUMPAD_PLUS'), + 'sensitivity_down': getattr(self.config, 'sensitivityDownKey', 'NUMPAD_MINUS'), + 'confidence_up': getattr(self.config, 'confidenceUpKey', 'PAGEUP'), + 'confidence_down': getattr(self.config, 'confidenceDownKey', 'PAGEDOWN'), + 'emergency_stop': getattr(self.config, 'emergencyStopKey', 'CTRL+ESC'), + } + + # 解析热键配置 + self.parsed_hotkeys = {} + for name, key_str in hotkeys_config.items(): + if key_str and key_str.strip(): + self.parsed_hotkeys[name] = self._parse_hotkey(key_str) + + # 设置默认回调函数 + self._setup_default_callbacks() + + def _parse_hotkey(self, key_string: str) -> dict: + """解析热键字符串""" + if '+' in key_string: + # 组合键处理 + keys = [k.strip().upper() for k in key_string.split('+')] + result = {'modifiers': [], 'key': None, 'is_mouse': False} + + for key in keys: + if key in ['CTRL', 'ALT', 'SHIFT']: + result['modifiers'].append(self._get_vk_code(key)) + else: + vk_code = self._get_vk_code(key) + result['key'] = vk_code & 0x7FFF # 移除鼠标标记 + result['is_mouse'] = bool(vk_code & 0x8000) + + return result + else: + # 单个按键 + vk_code = self._get_vk_code(key_string) + return { + 'modifiers': [], + 'key': vk_code & 0x7FFF, + 'is_mouse': bool(vk_code & 0x8000) + } + + def _get_vk_code(self, key_name: str) -> int: + """获取虚拟键码""" + key_upper = key_name.upper() + + # 功能键 + function_keys = { + "F1": win32con.VK_F1, "F2": win32con.VK_F2, "F3": win32con.VK_F3, + "F4": win32con.VK_F4, "F5": win32con.VK_F5, "F6": win32con.VK_F6, + "F7": win32con.VK_F7, "F8": win32con.VK_F8, "F9": win32con.VK_F9, + "F10": win32con.VK_F10, "F11": win32con.VK_F11, "F12": win32con.VK_F12 + } + + # 特殊键 + special_keys = { + "SPACE": win32con.VK_SPACE, "TAB": win32con.VK_TAB, "ENTER": win32con.VK_RETURN, + "ESC": win32con.VK_ESCAPE, "CTRL": win32con.VK_CONTROL, "SHIFT": win32con.VK_SHIFT, + "ALT": win32con.VK_MENU, "HOME": win32con.VK_HOME, "END": win32con.VK_END, + "INSERT": win32con.VK_INSERT, "DELETE": win32con.VK_DELETE, + "PAGEUP": win32con.VK_PRIOR, "PAGEDOWN": win32con.VK_NEXT, + "UP": win32con.VK_UP, "DOWN": win32con.VK_DOWN, + "LEFT": win32con.VK_LEFT, "RIGHT": win32con.VK_RIGHT, + "CAPSLOCK": win32con.VK_CAPITAL + } + + # 数字键盘 + numpad_keys = { + "NUMPAD0": win32con.VK_NUMPAD0, "NUMPAD1": win32con.VK_NUMPAD1, + "NUMPAD2": win32con.VK_NUMPAD2, "NUMPAD3": win32con.VK_NUMPAD3, + "NUMPAD4": win32con.VK_NUMPAD4, "NUMPAD5": win32con.VK_NUMPAD5, + "NUMPAD6": win32con.VK_NUMPAD6, "NUMPAD7": win32con.VK_NUMPAD7, + "NUMPAD8": win32con.VK_NUMPAD8, "NUMPAD9": win32con.VK_NUMPAD9, + "NUMPAD_PLUS": win32con.VK_ADD, "NUMPAD_MINUS": win32con.VK_SUBTRACT, + "NUMPAD_MULTIPLY": win32con.VK_MULTIPLY, "NUMPAD_DIVIDE": win32con.VK_DIVIDE + } + + # 鼠标按键(使用特殊标记) + mouse_keys = { + "MOUSE1": 0x01 | 0x8000, # 左键 + 鼠标标记 + "MOUSE2": 0x02 | 0x8000, # 右键 + 鼠标标记 + "MOUSE3": 0x04 | 0x8000, # 中键 + 鼠标标记 + "MOUSE4": 0x05 | 0x8000, # 侧键1 + 鼠标标记 + "MOUSE5": 0x06 | 0x8000 # 侧键2 + 鼠标标记 + } + + # 按优先级查找 + if key_upper in function_keys: + return function_keys[key_upper] + elif key_upper in special_keys: + return special_keys[key_upper] + elif key_upper in numpad_keys: + return numpad_keys[key_upper] + elif key_upper in mouse_keys: + return mouse_keys[key_upper] + elif len(key_name) == 1: + return ord(key_upper) + else: + return ord('F') # 默认值 + + def _setup_default_callbacks(self): + """设置默认热键回调函数""" + + def toggle_visuals(): + self.runtime_values['visuals_enabled'] = not self.runtime_values['visuals_enabled'] + print(f"[热键] 可视化显示: {'开启' if self.runtime_values['visuals_enabled'] else '关闭'}") + + def toggle_debug(): + self.runtime_values['debug_enabled'] = not self.runtime_values['debug_enabled'] + print(f"[热键] 调试信息: {'开启' if self.runtime_values['debug_enabled'] else '关闭'}") + + def toggle_aim_mode(): + self.runtime_values['aim_mode_advanced'] = not self.runtime_values['aim_mode_advanced'] + mode = '高级' if self.runtime_values['aim_mode_advanced'] else '普通' + print(f"[热键] 瞄准模式: {mode}") + + def toggle_headshot_mode(): + self.runtime_values['headshot_mode'] = not self.runtime_values['headshot_mode'] + print(f"[热键] 爆头模式: {'开启' if self.runtime_values['headshot_mode'] else '关闭'}") + + def toggle_smooth_mode(): + self.runtime_values['smooth_mode'] = not self.runtime_values['smooth_mode'] + print(f"[热键] 平滑移动: {'开启' if self.runtime_values['smooth_mode'] else '关闭'}") + + def increase_sensitivity(): + step = getattr(self.config, 'sensitivityStep', 0.05) + max_val = getattr(self.config, 'maxSensitivity', 2.0) + self.runtime_values['sensitivity'] = min(max_val, self.runtime_values['sensitivity'] + step) + print(f"[热键] 灵敏度: {self.runtime_values['sensitivity']:.3f}") + + def decrease_sensitivity(): + step = getattr(self.config, 'sensitivityStep', 0.05) + min_val = getattr(self.config, 'minSensitivity', 0.1) + self.runtime_values['sensitivity'] = max(min_val, self.runtime_values['sensitivity'] - step) + print(f"[热键] 灵敏度: {self.runtime_values['sensitivity']:.3f}") + + def increase_confidence(): + step = getattr(self.config, 'confidenceStep', 0.05) + max_val = getattr(self.config, 'maxConfidence', 0.9) + self.runtime_values['confidence'] = min(max_val, self.runtime_values['confidence'] + step) + print(f"[热键] 检测置信度: {self.runtime_values['confidence']:.3f}") + + def decrease_confidence(): + step = getattr(self.config, 'confidenceStep', 0.05) + min_val = getattr(self.config, 'minConfidence', 0.1) + self.runtime_values['confidence'] = max(min_val, self.runtime_values['confidence'] - step) + print(f"[热键] 检测置信度: {self.runtime_values['confidence']:.3f}") + + def emergency_stop(): + print("[热键] 紧急停止!") + # 这里可以添加紧急停止逻辑 + + # 注册回调函数 + self.hotkey_callbacks = { + 'toggle_visuals': toggle_visuals, + 'toggle_debug': toggle_debug, + 'aim_mode_toggle': toggle_aim_mode, + 'headshot_toggle': toggle_headshot_mode, + 'smooth_toggle': toggle_smooth_mode, + 'sensitivity_up': increase_sensitivity, + 'sensitivity_down': decrease_sensitivity, + 'confidence_up': increase_confidence, + 'confidence_down': decrease_confidence, + 'emergency_stop': emergency_stop, + } + + def check_hotkeys(self): + """检查并处理所有热键""" + if not getattr(self.config, 'enableHotkeys', True): + return + + response_delay = getattr(self.config, 'hotkeyResponseDelay', 0.1) + + for name, key_config in self.parsed_hotkeys.items(): + if name in self.hotkey_callbacks and self._is_key_pressed(key_config, response_delay): + try: + self.hotkey_callbacks[name]() + except Exception as e: + print(f"[热键管理] 回调执行失败 '{name}': {e}") + + def _is_key_pressed(self, key_config: dict, response_delay: float = 0.1) -> bool: + """检查热键是否被按下""" + if not key_config or 'key' not in key_config: + return False + + key_id = str(key_config) + current_time = time.time() + + # 检查响应延迟 + if (key_id in self.last_trigger_time and + current_time - self.last_trigger_time[key_id] < response_delay): + return False + + # 检查组合键 + for modifier in key_config.get('modifiers', []): + if win32api.GetAsyncKeyState(modifier) == 0: + self.hotkey_states[key_id] = False + return False + + # 检查主键 + main_key = key_config['key'] + is_mouse = key_config.get('is_mouse', False) + + if is_mouse: + # 鼠标按键检查 + if not getattr(self.config, 'enableMouseHotkeys', True): + return False + key_pressed = win32api.GetAsyncKeyState(main_key) != 0 + else: + # 键盘按键检查 + key_pressed = win32api.GetAsyncKeyState(main_key) != 0 + + # 检查按键状态变化(只在按下的瞬间触发) + was_pressed = self.hotkey_states.get(key_id, False) + self.hotkey_states[key_id] = key_pressed + + if key_pressed and not was_pressed: + self.last_trigger_time[key_id] = current_time + return True + + return False + + def is_activation_key_pressed(self) -> bool: + """检查激活键是否被按下(用于瞄准激活)""" + activation_key = getattr(self.config, 'aaActivationKey', 'CAPSLOCK') + + if activation_key == 'CAPSLOCK': + # 大写锁定键特殊处理 + return bool(win32api.GetKeyState(win32con.VK_CAPITAL) & 0x8000) + else: + key_config = self._parse_hotkey(activation_key) + return self._is_key_held(key_config) + + def is_mouse_activation_pressed(self) -> bool: + """检查鼠标激活键是否被按下""" + mouse_key = getattr(self.config, 'mouseActivationKey', '') + if not mouse_key or not getattr(self.config, 'enableMouseHotkeys', True): + return False + + key_config = self._parse_hotkey(mouse_key) + return self._is_key_held(key_config) + + def _is_key_held(self, key_config: dict) -> bool: + """检查热键是否持续按下""" + if not key_config or 'key' not in key_config: + return False + + # 检查组合键 + for modifier in key_config.get('modifiers', []): + if win32api.GetAsyncKeyState(modifier) == 0: + return False + + # 检查主键 + main_key = key_config['key'] + is_mouse = key_config.get('is_mouse', False) + + if is_mouse: + return win32api.GetAsyncKeyState(main_key) != 0 + else: + return win32api.GetAsyncKeyState(main_key) != 0 + + def should_quit(self) -> bool: + """检查是否应该退出程序""" + if 'quit' in self.parsed_hotkeys: + quit_config = self.parsed_hotkeys['quit'] + return self._is_key_held(quit_config) + + # 默认退出键检查 + quit_key = getattr(self.config, 'aaQuitKey', 'P') + return win32api.GetAsyncKeyState(ord(quit_key.upper())) != 0 + + def get_runtime_value(self, name: str, default=None): + """获取运行时参数值""" + return self.runtime_values.get(name, default) + + def update_config(self, new_config): + """更新配置(用于热重载)""" + self.config = new_config + + # 重新初始化热键 + self._init_hotkeys() + + # 更新运行时值 + self.runtime_values.update({ + 'sensitivity': getattr(new_config, 'aaMovementAmp', self.runtime_values['sensitivity']), + 'confidence': getattr(new_config, 'confidence', self.runtime_values['confidence']), + }) + + print("[热键管理] 配置已更新") + + def print_hotkey_help(self): + """打印热键帮助信息""" + print("\n========== 热键说明 ==========") + hotkey_help = { + 'aaActivationKey': '瞄准激活', + 'aaQuitKey': '退出程序', + 'aaToggleVisualsKey': '切换可视化', + 'aaToggleDebugKey': '切换调试信息', + 'aimModeToggleKey': '切换瞄准模式', + 'headshotModeToggleKey': '切换爆头模式', + 'smoothModeToggleKey': '切换平滑模式', + 'sensitivityUpKey': '增加灵敏度', + 'sensitivityDownKey': '减少灵敏度', + 'confidenceUpKey': '增加检测置信度', + 'confidenceDownKey': '减少检测置信度', + 'configReloadKey': '重载配置', + 'emergencyStopKey': '紧急停止', + } + + for key_name, description in hotkey_help.items(): + key_value = getattr(self.config, key_name, '未设置') + print(f" {description}: {key_value}") + + print("==============================\n") + +# 全局热键管理器实例 +global_hotkey_manager: Optional[GlobalHotkeyManager] = None + +def init_hotkey_manager(config_module): + """初始化全局热键管理器""" + global global_hotkey_manager + global_hotkey_manager = GlobalHotkeyManager(config_module) + return global_hotkey_manager + +def get_hotkey_manager() -> Optional[GlobalHotkeyManager]: + """获取全局热键管理器""" + return global_hotkey_manager \ No newline at end of file diff --git a/main_onnx.py b/main_onnx.py index ed20be0..225fb8e 100644 --- a/main_onnx.py +++ b/main_onnx.py @@ -1,4 +1,19 @@ import onnxruntime as ort + +# === 增强功能导入 (可选) === +try: + from mouse_driver import move_mouse_relative, is_driver_available + DRIVER_AVAILABLE = True +except ImportError: + DRIVER_AVAILABLE = False + print("驱动级鼠标控制不可用,将使用系统级控制") + +try: + from advanced_aiming import get_advanced_aiming_movement + ADVANCED_AIMING_AVAILABLE = True +except ImportError: + ADVANCED_AIMING_AVAILABLE = False + import numpy as np import gc import numpy as np @@ -13,7 +28,7 @@ # Could be do with # from config import * # But we are writing it out for clarity for new devs -from config import aaMovementAmp, useMask, maskHeight, maskWidth, aaQuitKey, confidence, headshot_mode, cpsDisplay, visuals, onnxChoice, centerOfScreen +from config import aaMovementAmp, useMask, maskHeight, maskWidth, aaQuitKey, confidence, headshot_mode, cpsDisplay, visuals, onnxChoice, centerOfScreen, useDriverMouse, fallbackToSystemMouse import gameSelection def main(): @@ -105,8 +120,10 @@ def main(): s += f"{n} {int(c)}, " # add to string for *xyxy, conf, cls in reversed(det): - targets.append((xyxy2xywh(torch.tensor(xyxy).view( - 1, 4)) / gn).view(-1).tolist() + [float(conf)]) # normalized xywh + # 修复tensor view调用 + tensor_xyxy = torch.tensor(xyxy).reshape(1, 4) + normalized_coords = (xyxy2xywh(tensor_xyxy) / gn).reshape(-1).tolist() + targets.append(normalized_coords + [float(conf)]) # normalized xywh targets = pd.DataFrame( targets, columns=['current_mid_x', 'current_mid_y', 'width', "height", "confidence"]) @@ -143,10 +160,21 @@ def main(): mouseMove = [xMid - cWidth, (yMid - headshot_offset) - cHeight] - # Moving the mouse - if win32api.GetKeyState(0x14): - win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, int( - mouseMove[0] * aaMovementAmp), int(mouseMove[1] * aaMovementAmp), 0, 0) + # Moving the mouse - 修复CapsLock检测并集成驱动级控制 + # 正确检测CapsLock锁定状态:检测切换位而不是按下位 + if win32api.GetKeyState(win32con.VK_CAPITAL) & 0x0001: + move_x = int(mouseMove[0] * aaMovementAmp) + move_y = int(mouseMove[1] * aaMovementAmp) + + # 使用驱动级鼠标控制(如果可用) + if DRIVER_AVAILABLE and useDriverMouse and is_driver_available(): + success = move_mouse_relative(move_x, move_y) + if not success and fallbackToSystemMouse: + # 驱动失败,回退到系统级控制 + win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, move_x, move_y, 0, 0) + else: + # 使用系统级控制 + win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, move_x, move_y, 0, 0) last_mid_coord = [xMid, yMid] else: diff --git a/main_onnx.py.backup b/main_onnx.py.backup new file mode 100644 index 0000000..ed20be0 --- /dev/null +++ b/main_onnx.py.backup @@ -0,0 +1,203 @@ +import onnxruntime as ort +import numpy as np +import gc +import numpy as np +import cv2 +import time +import win32api +import win32con +import pandas as pd +from utils.general import (cv2, non_max_suppression, xyxy2xywh) +import torch + +# Could be do with +# from config import * +# But we are writing it out for clarity for new devs +from config import aaMovementAmp, useMask, maskHeight, maskWidth, aaQuitKey, confidence, headshot_mode, cpsDisplay, visuals, onnxChoice, centerOfScreen +import gameSelection + +def main(): + # External Function for running the game selection menu (gameSelection.py) + camera, cWidth, cHeight = gameSelection.gameSelection() + + # Used for forcing garbage collection + count = 0 + sTime = time.time() + + # Choosing the correct ONNX Provider based on config.py + onnxProvider = "" + if onnxChoice == 1: + onnxProvider = "CPUExecutionProvider" + elif onnxChoice == 2: + onnxProvider = "DmlExecutionProvider" + elif onnxChoice == 3: + import cupy as cp + onnxProvider = "CUDAExecutionProvider" + + so = ort.SessionOptions() + so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL + ort_sess = ort.InferenceSession('yolov5s320Half.onnx', sess_options=so, providers=[ + onnxProvider]) + + # Used for colors drawn on bounding boxes + COLORS = np.random.uniform(0, 255, size=(1500, 3)) + + # Main loop Quit if Q is pressed + last_mid_coord = None + while win32api.GetAsyncKeyState(ord(aaQuitKey)) == 0: + + # Getting Frame + npImg = np.array(camera.get_latest_frame()) + + from config import maskSide # "temporary" workaround for bad syntax + if useMask: + maskSide = maskSide.lower() + if maskSide == "right": + npImg[-maskHeight:, -maskWidth:, :] = 0 + elif maskSide == "left": + npImg[-maskHeight:, :maskWidth, :] = 0 + else: + raise Exception('ERROR: Invalid maskSide! Please use "left" or "right"') + + # If Nvidia, do this + if onnxChoice == 3: + # Normalizing Data + im = torch.from_numpy(npImg).to('cuda') + if im.shape[2] == 4: + # If the image has an alpha channel, remove it + im = im[:, :, :3,] + + im = torch.movedim(im, 2, 0) + im = im.half() + im /= 255 + if len(im.shape) == 3: + im = im[None] + # If AMD or CPU, do this + else: + # Normalizing Data + im = np.array([npImg]) + if im.shape[3] == 4: + # If the image has an alpha channel, remove it + im = im[:, :, :, :3] + im = im / 255 + im = im.astype(np.half) + im = np.moveaxis(im, 3, 1) + + # If Nvidia, do this + if onnxChoice == 3: + outputs = ort_sess.run(None, {'images': cp.asnumpy(im)}) + # If AMD or CPU, do this + else: + outputs = ort_sess.run(None, {'images': np.array(im)}) + + im = torch.from_numpy(outputs[0]).to('cpu') + + pred = non_max_suppression( + im, confidence, confidence, 0, False, max_det=10) + + targets = [] + for i, det in enumerate(pred): + s = "" + gn = torch.tensor(im.shape)[[0, 0, 0, 0]] + if len(det): + for c in det[:, -1].unique(): + n = (det[:, -1] == c).sum() # detections per class + s += f"{n} {int(c)}, " # add to string + + for *xyxy, conf, cls in reversed(det): + targets.append((xyxy2xywh(torch.tensor(xyxy).view( + 1, 4)) / gn).view(-1).tolist() + [float(conf)]) # normalized xywh + + targets = pd.DataFrame( + targets, columns=['current_mid_x', 'current_mid_y', 'width', "height", "confidence"]) + + center_screen = [cWidth, cHeight] + + # If there are people in the center bounding box + if len(targets) > 0: + if (centerOfScreen): + # Compute the distance from the center + targets["dist_from_center"] = np.sqrt((targets.current_mid_x - center_screen[0])**2 + (targets.current_mid_y - center_screen[1])**2) + + # Sort the data frame by distance from center + targets = targets.sort_values("dist_from_center") + + # Get the last persons mid coordinate if it exists + if last_mid_coord: + targets['last_mid_x'] = last_mid_coord[0] + targets['last_mid_y'] = last_mid_coord[1] + # Take distance between current person mid coordinate and last person mid coordinate + targets['dist'] = np.linalg.norm( + targets.iloc[:, [0, 1]].values - targets.iloc[:, [4, 5]], axis=1) + targets.sort_values(by="dist", ascending=False) + + # Take the first person that shows up in the dataframe (Recall that we sort based on Euclidean distance) + xMid = targets.iloc[0].current_mid_x + yMid = targets.iloc[0].current_mid_y + + box_height = targets.iloc[0].height + if headshot_mode: + headshot_offset = box_height * 0.38 + else: + headshot_offset = box_height * 0.2 + + mouseMove = [xMid - cWidth, (yMid - headshot_offset) - cHeight] + + # Moving the mouse + if win32api.GetKeyState(0x14): + win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, int( + mouseMove[0] * aaMovementAmp), int(mouseMove[1] * aaMovementAmp), 0, 0) + last_mid_coord = [xMid, yMid] + + else: + last_mid_coord = None + + # See what the bot sees + if visuals: + # Loops over every item identified and draws a bounding box + for i in range(0, len(targets)): + halfW = round(targets["width"][i] / 2) + halfH = round(targets["height"][i] / 2) + midX = targets['current_mid_x'][i] + midY = targets['current_mid_y'][i] + (startX, startY, endX, endY) = int(midX + halfW), int(midY + + halfH), int(midX - halfW), int(midY - halfH) + + idx = 0 + # draw the bounding box and label on the frame + label = "{}: {:.2f}%".format( + "Human", targets["confidence"][i] * 100) + cv2.rectangle(npImg, (startX, startY), (endX, endY), + COLORS[idx], 2) + y = startY - 15 if startY - 15 > 15 else startY + 15 + cv2.putText(npImg, label, (startX, y), + cv2.FONT_HERSHEY_SIMPLEX, 0.5, COLORS[idx], 2) + + + # Forced garbage cleanup every second + count += 1 + if (time.time() - sTime) > 1: + if cpsDisplay: + print("CPS: {}".format(count)) + count = 0 + sTime = time.time() + + # Uncomment if you keep running into memory issues + # gc.collect(generation=0) + + # See visually what the Aimbot sees + if visuals: + cv2.imshow('Live Feed', npImg) + if (cv2.waitKey(1) & 0xFF) == ord('q'): + exit() + camera.stop() + + +if __name__ == "__main__": + try: + main() + except Exception as e: + import traceback + traceback.print_exception(e) + print("ERROR: " + str(e)) + print("Ask @Wonder for help in our Discord in the #ai-aimbot channel ONLY: https://discord.gg/rootkitorg") \ No newline at end of file diff --git a/mouse_driver.py b/mouse_driver.py new file mode 100644 index 0000000..db7b0de --- /dev/null +++ b/mouse_driver.py @@ -0,0 +1,198 @@ +""" +驱动级鼠标控制模块 +使用ghub_device.dll实现驱动级鼠标移动 +""" +import ctypes +import os +from ctypes import CDLL + +class MouseDriver: + def __init__(self): + """初始化驱动级鼠标控制""" + self.driver = None + self.is_initialized = False + self._initialize_driver() + + def _initialize_driver(self): + """初始化罗技驱动""" + try: + # 获取当前目录下的dll文件路径 + dll_path = os.path.join(os.path.dirname(__file__), 'ghub_move', 'ghub_device.dll') + + if not os.path.exists(dll_path): + print(f'警告: 找不到驱动文件 {dll_path}') + return + + self.driver = CDLL(dll_path) + + # 尝试初始化驱动 + init_result = self.driver.device_open() + if init_result == 1: + self.is_initialized = True + print('驱动级鼠标控制初始化成功!') + else: + print('驱动初始化失败: 未安装GHUB或LGS驱动!') + + except FileNotFoundError: + print('错误: 缺少ghub_device.dll文件') + except Exception as e: + print(f'驱动初始化异常: {e}') + + def move_relative(self, x, y): + """ + 相对移动鼠标 + + Args: + x (int): 水平移动距离,正数向右,负数向左 + y (int): 垂直移动距离,正数向下,负数向上 + """ + if not self.is_initialized: + return False + + try: + # 使用驱动级移动 + self.driver.moveR(int(x), int(y), False) + return True + except Exception as e: + print(f'鼠标移动失败: {e}') + return False + + def move_absolute(self, x, y): + """ + 绝对移动鼠标 + + Args: + x (int): 绝对X坐标 + y (int): 绝对Y坐标 + """ + if not self.is_initialized: + return False + + try: + # 使用驱动级绝对移动 + self.driver.moveR(int(x), int(y), True) + return True + except Exception as e: + print(f'鼠标绝对移动失败: {e}') + return False + + def click_left_down(self): + """左键按下""" + if not self.is_initialized: + return False + + try: + self.driver.mouse_down(1) # 1代表左键 + return True + except Exception as e: + print(f'左键按下失败: {e}') + return False + + def click_left_up(self): + """左键松开""" + if not self.is_initialized: + return False + + try: + self.driver.mouse_up(1) # 1代表左键 + return True + except Exception as e: + print(f'左键松开失败: {e}') + return False + + def click_left(self): + """左键点击""" + if not self.is_initialized: + return False + + self.click_left_down() + self.click_left_up() + return True + + def click_right_down(self): + """右键按下""" + if not self.is_initialized: + return False + + try: + self.driver.mouse_down(3) # 3代表右键 + return True + except Exception as e: + print(f'右键按下失败: {e}') + return False + + def click_right_up(self): + """右键松开""" + if not self.is_initialized: + return False + + try: + self.driver.mouse_up(3) # 3代表右键 + return True + except Exception as e: + print(f'右键松开失败: {e}') + return False + + def click_right(self): + """右键点击""" + if not self.is_initialized: + return False + + self.click_right_down() + self.click_right_up() + return True + + def is_driver_ready(self): + """检查驱动是否准备就绪""" + return self.is_initialized + + def close(self): + """关闭驱动连接""" + if self.driver and self.is_initialized: + try: + # 如果有关闭函数的话调用 + pass + except: + pass + self.is_initialized = False + +# 创建全局鼠标驱动实例 +mouse_driver = MouseDriver() + +def move_mouse_relative(x, y): + """ + 相对移动鼠标的便捷函数 + + Args: + x (int): 水平移动距离 + y (int): 垂直移动距离 + + Returns: + bool: 是否移动成功 + """ + return mouse_driver.move_relative(x, y) + +def move_mouse_absolute(x, y): + """ + 绝对移动鼠标的便捷函数 + + Args: + x (int): 绝对X坐标 + y (int): 绝对Y坐标 + + Returns: + bool: 是否移动成功 + """ + return mouse_driver.move_absolute(x, y) + +def click_mouse_left(): + """左键点击便捷函数""" + return mouse_driver.click_left() + +def click_mouse_right(): + """右键点击便捷函数""" + return mouse_driver.click_right() + +def is_driver_available(): + """检查驱动是否可用""" + return mouse_driver.is_driver_ready() \ No newline at end of file diff --git a/setup_driver.py b/setup_driver.py new file mode 100644 index 0000000..1bde6cf --- /dev/null +++ b/setup_driver.py @@ -0,0 +1,162 @@ +""" +AI-Aimbot 驱动级鼠标控制安装和配置脚本 +""" +import os +import sys + +def check_dll_file(): + """检查DLL文件是否存在""" + dll_path = os.path.join(os.path.dirname(__file__), 'ghub_move', 'ghub_device.dll') + + if os.path.exists(dll_path): + print("✅ ghub_device.dll 文件存在") + return True + else: + print("❌ 找不到 ghub_device.dll 文件") + print(f"应该位于: {dll_path}") + return False + +def check_python_modules(): + """检查必要的Python模块""" + required_modules = ['ctypes', 'numpy', 'torch', 'cv2', 'pandas', 'win32api', 'win32con'] + missing_modules = [] + + print("\n检查Python模块依赖...") + + for module in required_modules: + try: + if module == 'cv2': + import cv2 + elif module == 'win32api': + import win32api + elif module == 'win32con': + import win32con + else: + __import__(module) + print(f"✅ {module}") + except ImportError: + print(f"❌ {module}") + missing_modules.append(module) + + return missing_modules + +def check_mouse_driver(): + """测试鼠标驱动""" + print("\n测试鼠标驱动...") + try: + from mouse_driver import is_driver_available, MouseDriver + + if is_driver_available(): + print("✅ 驱动级鼠标控制初始化成功!") + return True + else: + print("❌ 驱动级鼠标控制初始化失败") + print("可能的原因:") + print("1. 未安装罗技驱动 (GHUB/LGS)") + print("2. 安装的是新版本驱动,需要老版本驱动") + print("3. DLL文件损坏或不兼容") + return False + except Exception as e: + print(f"❌ 驱动测试失败: {e}") + return False + +def install_requirements(): + """安装缺失的依赖""" + print("\n是否要自动安装缺失的Python模块? (y/n): ", end="") + choice = input().lower().strip() + + if choice == 'y' or choice == 'yes': + modules_to_install = { + 'cv2': 'opencv-python', + 'torch': 'torch', + 'numpy': 'numpy', + 'pandas': 'pandas', + 'win32api': 'pywin32', + 'win32con': 'pywin32' + } + + missing = check_python_modules() + + for module in missing: + if module in modules_to_install: + package = modules_to_install[module] + print(f"\n安装 {package}...") + os.system(f'pip install {package}') + else: + print("请手动安装缺失的模块") + +def show_configuration_guide(): + """显示配置指南""" + print("\n" + "="*60) + print("配置指南") + print("="*60) + + print("\n1. 罗技驱动设置:") + print(" - 必须安装老版本罗技驱动 (GHUB/LGS)") + print(" - 如果已安装新版本,请先完全卸载再安装老版本") + + print("\n2. 鼠标设置:") + print(" - 打开Windows设置 -> 设备 -> 鼠标") + print(" - 关闭 '提高鼠标精度' 选项") + print(" - 将鼠标灵敏度调到正中间位置") + + print("\n3. 配置文件:") + print(" - 编辑 config.py 文件") + print(" - 设置 useDriverMouse = True") + print(" - 设置 fallbackToSystemMouse = True (推荐)") + + print("\n4. 测试:") + print(" - 运行 test_driver.py 进行功能测试") + print(" - 或运行任何主程序文件测试实际效果") + +def main(): + """主函数""" + print("🎯 AI-Aimbot 驱动级鼠标控制安装配置助手") + print("="*60) + + # 检查DLL文件 + dll_ok = check_dll_file() + + # 检查Python模块 + missing_modules = check_python_modules() + + if missing_modules: + print(f"\n缺少以下模块: {', '.join(missing_modules)}") + install_requirements() + else: + print("\n✅ 所有必要的Python模块都已安装") + + if dll_ok: + # 测试驱动 + driver_ok = check_mouse_driver() + + if driver_ok: + print("\n🎉 恭喜!驱动级鼠标控制配置成功!") + print("你现在可以运行主程序享受驱动级鼠标控制了。") + else: + print("\n⚠️ 驱动级鼠标控制配置未完成") + print("请按照下面的配置指南完成设置") + + # 显示配置指南 + show_configuration_guide() + + print("\n" + "="*60) + print("安装配置完成!") + + if dll_ok and not missing_modules: + print("\n可以运行以下命令进行测试:") + print(" python test_driver.py # 驱动功能测试") + print(" python main.py # 运行主程序") + + input("\n按回车键退出...") + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print("\n\n程序被用户中断") + except Exception as e: + print(f"\n\n程序发生错误: {e}") + import traceback + traceback.print_exc() + input("\n按回车键退出...") \ No newline at end of file diff --git a/smooth_aiming.py b/smooth_aiming.py new file mode 100644 index 0000000..78a505b --- /dev/null +++ b/smooth_aiming.py @@ -0,0 +1,312 @@ +""" +优化的瞄准逻辑模块 +包含平滑移动、目标锁定、抖动抑制等功能 +""" +import numpy as np +import time +from collections import deque +import math + +class SmoothAiming: + def __init__(self): + """初始化平滑瞄准系统""" + self.last_target = None + self.locked_target_id = None + self.lock_frame_count = 0 + self.movement_history = deque(maxlen=5) # 保存最近5次移动 + self.target_history = deque(maxlen=5) # 增加历史记录数量以提高预测精度 + self.last_move_time = time.time() + self.smooth_x = 0.0 + self.smooth_y = 0.0 + self.frame_times = deque(maxlen=10) # 监控帧率性能 + self.input_lag_compensation = 0.016 # 输入延迟补偿(约16ms) + + def calculate_smooth_movement(self, target_x, target_y, center_x, center_y, config): + """ + 计算平滑的鼠标移动,使用改进的算法 + + Args: + target_x, target_y: 目标坐标 + center_x, center_y: 屏幕中心坐标 + config: 配置对象 + + Returns: + tuple: (smooth_x, smooth_y) 平滑后的移动坐标 + """ + # 计算原始移动量 + raw_x = target_x - center_x + raw_y = target_y - center_y + + # 如果启用平滑移动 + if getattr(config, 'enableSmoothMovement', True): + smoothing_factor = getattr(config, 'smoothingFactor', 0.25) + + # 计算移动距离,用于自适应平滑 + move_distance = math.sqrt(raw_x * raw_x + raw_y * raw_y) + + # 自适应平滑:距离越大,平滑程度越小(响应更快) + if move_distance > 50: # 大距离移动 + adaptive_factor = smoothing_factor * 0.5 # 减少平滑,快速跟进 + elif move_distance > 20: # 中等距离 + adaptive_factor = smoothing_factor * 0.7 + else: # 小距离移动 + adaptive_factor = smoothing_factor # 正常平滑 + + # 使用自适应指数移动平均进行平滑 + self.smooth_x = self.smooth_x * adaptive_factor + raw_x * (1 - adaptive_factor) + self.smooth_y = self.smooth_y * adaptive_factor + raw_y * (1 - adaptive_factor) + + return self.smooth_x, self.smooth_y + + return raw_x, raw_y + + def apply_movement_threshold(self, move_x, move_y, config): + """ + 应用移动阈值,忽略微小移动 + + Args: + move_x, move_y: 移动量 + config: 配置对象 + + Returns: + tuple: 过滤后的移动量 + """ + threshold = getattr(config, 'movementThreshold', 2) + + # 计算移动距离 + move_distance = math.sqrt(move_x * move_x + move_y * move_y) + + # 如果移动距离小于阈值,则不移动 + if move_distance < threshold: + return 0, 0 + + return move_x, move_y + + def select_target_with_lock(self, targets, config): + """ + 带锁定功能的目标选择 + + Args: + targets: 目标数据框 + config: 配置对象 + + Returns: + dict: 选中的目标信息,如果没有目标则返回None + """ + if len(targets) == 0: + self._reset_lock() + return None + + lock_frames = getattr(config, 'targetLockFrames', 15) + max_lock_distance = getattr(config, 'maxLockDistance', 100) + + # 如果有锁定的目标,尝试保持锁定 + if self.locked_target_id is not None and self.lock_frame_count < lock_frames: + locked_target = self._find_locked_target(targets, max_lock_distance) + if locked_target is not None: + self.lock_frame_count += 1 + return locked_target + else: + self._reset_lock() + + # 选择新目标 + selected_target = self._select_best_target(targets, config) + if selected_target is not None: + self._lock_new_target(selected_target) + + return selected_target + + def _find_locked_target(self, targets, max_distance): + """查找已锁定的目标""" + if self.last_target is None: + return None + + last_x, last_y = self.last_target['x'], self.last_target['y'] + + for i, target in targets.iterrows(): + current_x = target['current_mid_x'] + current_y = target['current_mid_y'] + + # 计算与上次目标的距离 + distance = math.sqrt((current_x - last_x)**2 + (current_y - last_y)**2) + + if distance <= max_distance: + return { + 'x': current_x, + 'y': current_y, + 'width': target['width'], + 'height': target['height'], + 'confidence': target['confidence'] + } + + return None + + def _select_best_target(self, targets, config): + """选择最佳目标""" + # 如果启用中心优先,按距离中心的距离排序 + if getattr(config, 'centerOfScreen', True): + best_target = targets.iloc[0] # 已经按距离中心排序 + else: + # 按置信度选择 + best_target = targets.loc[targets['confidence'].idxmax()] + + return { + 'x': best_target['current_mid_x'], + 'y': best_target['current_mid_y'], + 'width': best_target['width'], + 'height': best_target['height'], + 'confidence': best_target['confidence'] + } + + def _lock_new_target(self, target): + """锁定新目标""" + self.locked_target_id = id(target) + self.lock_frame_count = 0 + self.last_target = target.copy() + + def _reset_lock(self): + """重置锁定状态""" + self.locked_target_id = None + self.lock_frame_count = 0 + self.last_target = None + + def apply_predictive_aiming(self, target, config): + """ + 应用预测性瞄准,使用改进的运动预测算法 + + Args: + target: 目标信息 + config: 配置对象 + + Returns: + tuple: 预测后的目标坐标 + """ + if not getattr(config, 'enablePredictiveAim', True): + return target['x'], target['y'] + + current_time = time.time() + current_pos = (target['x'], target['y'], current_time) + + # 记录目标历史位置 + self.target_history.append(current_pos) + + # 需要至少2个历史点来计算速度 + if len(self.target_history) < 2: + return target['x'], target['y'] + + # 计算多点平均速度以提高预测精度 + velocities_x = [] + velocities_y = [] + + for i in range(1, len(self.target_history)): + prev_pos = self.target_history[i-1] + curr_pos = self.target_history[i] + + dt = curr_pos[2] - prev_pos[2] + if dt > 0: + vx = (curr_pos[0] - prev_pos[0]) / dt + vy = (curr_pos[1] - prev_pos[1]) / dt + velocities_x.append(vx) + velocities_y.append(vy) + + if not velocities_x: + return target['x'], target['y'] + + # 使用加权平均(最近的速度权重更大) + weights = [i + 1 for i in range(len(velocities_x))] + avg_vx = sum(vx * w for vx, w in zip(velocities_x, weights)) / sum(weights) + avg_vy = sum(vy * w for vy, w in zip(velocities_y, weights)) / sum(weights) + + # 计算预测时间(根据速度自适应调整) + speed = math.sqrt(avg_vx * avg_vx + avg_vy * avg_vy) + prediction_strength = getattr(config, 'predictionStrength', 0.6) + + # 根据移动速度调整预测时间 + if speed > 200: # 快速移动 + prediction_time = prediction_strength * 0.08 + elif speed > 100: # 中速移动 + prediction_time = prediction_strength * 0.06 + else: # 慢速或静止 + prediction_time = prediction_strength * 0.04 + + # 预测未来位置 + predicted_x = current_pos[0] + avg_vx * prediction_time + predicted_y = current_pos[1] + avg_vy * prediction_time + + return predicted_x, predicted_y + + def calculate_aimpoint_offset(self, target, config): + """ + 计算瞄准点偏移,基于用户配置的精确位置 + + Args: + target: 目标信息 + config: 配置对象 + + Returns: + tuple: (x_offset, y_offset) 瞄准点偏移量 + """ + box_height = target['height'] + box_width = target['width'] + + # 获取用户配置的瞄准偏移比例(默认0.4,即从顶部向下40%) + aim_offset_ratio = getattr(config, 'aimOffsetRatio', 0.4) + + # X轴偏移:始终瞄准中轴线(水平中心),所以X偏移为0 + x_offset = 0.0 + + # Y轴偏移:从目标框顶部向下的偏移量 + # 注意:在屏幕坐标系中,Y轴向下为正,所以我们需要从中心点向上偏移 + # target['y'] 是目标框的中心点,我们需要计算到指定比例位置的偏移 + y_offset_from_center = (aim_offset_ratio - 0.5) * box_height + + # 如果是头部模式,可以进一步微调 + if getattr(config, 'headshot_mode', False): + # 头部模式下,稍微向上调整瞄准点 + y_offset_from_center -= box_height * 0.05 + + return x_offset, y_offset_from_center + +# 全局平滑瞄准实例 +smooth_aiming = SmoothAiming() + +def get_optimized_mouse_movement(targets, center_x, center_y, config): + """ + 获取优化的鼠标移动量,使用精确瞄准逻辑 + + Args: + targets: 检测到的目标数据框 + center_x, center_y: 屏幕中心坐标 + config: 配置对象 + + Returns: + tuple: (move_x, move_y) 或 None 如果没有有效移动 + """ + global smooth_aiming + + # 选择目标(带锁定功能) + selected_target = smooth_aiming.select_target_with_lock(targets, config) + + if selected_target is None: + return None + + # 应用预测性瞄准得到基础目标位置 + predicted_x, predicted_y = smooth_aiming.apply_predictive_aiming(selected_target, config) + + # 计算精确瞄准点偏移(中轴线上方0.4位置) + x_offset, y_offset = smooth_aiming.calculate_aimpoint_offset(selected_target, config) + + # 应用偏移到预测位置 + # X轴:使用预测的X坐标 + X偏移(通常为0,保持中轴线) + # Y轴:使用预测的Y坐标 + Y偏移(向上偏移到0.4位置) + target_x = predicted_x + x_offset + target_y = predicted_y + y_offset + + # 计算平滑移动 + smooth_x, smooth_y = smooth_aiming.calculate_smooth_movement( + target_x, target_y, center_x, center_y, config) + + # 应用移动阈值 + final_x, final_y = smooth_aiming.apply_movement_threshold(smooth_x, smooth_y, config) + + return final_x, final_y \ No newline at end of file diff --git a/yolo_utils.py b/yolo_utils.py new file mode 100644 index 0000000..935679b --- /dev/null +++ b/yolo_utils.py @@ -0,0 +1,110 @@ +""" +Standalone YOLO utility functions without ultralytics dependency +""" +import numpy as np +import torch +import torchvision +import time + +def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False, max_det=300): + """ + Perform Non-Maximum Suppression (NMS) on inference results. + + Args: + prediction: Model output tensor of shape (batch, num_boxes, 5+num_classes) + conf_thres: Confidence threshold + iou_thres: IoU threshold for NMS + classes: List of classes to consider + agnostic: Class-agnostic NMS + multi_label: Allow multiple labels per box + max_det: Maximum number of detections + + Returns: + List of detection tensors, one per image + """ + bs = prediction.shape[0] # batch size + nc = prediction.shape[2] - 4 # number of classes + xc = prediction[..., 4] > conf_thres # candidates + + # Settings + max_wh = 7680 # (pixels) maximum box width and height + max_nms = 30000 # maximum number of boxes into torchvision.ops.nms() + time_limit = 0.3 + 0.03 * bs # seconds to quit after + redundant = True # require redundant detections + multi_label &= nc > 1 # multiple labels per box (adds 0.5ms/img) + merge = False # use merge-NMS + + output = [torch.zeros((0, 6), device=prediction.device)] * bs + + for xi, x in enumerate(prediction): # image index, image inference + # Apply constraints + x = x[xc[xi]] # confidence + + # If none remain process next image + if not x.shape[0]: + continue + + # Compute conf + x[:, 5:] *= x[:, 4:5] # conf = obj_conf * cls_conf + + # Box (center x, center y, width, height) to (x1, y1, x2, y2) + box = xywh2xyxy(x[:, :4]) + + # Detections matrix nx6 (xyxy, conf, cls) + if multi_label: + i, j = (x[:, 5:] > conf_thres).nonzero(as_tuple=False).T + box_tensor = torch.tensor(box[i]) if not isinstance(box, torch.Tensor) else box[i] + x = torch.cat((box_tensor, x[i, j + 5, None], j[:, None].float()), 1) + else: # best class only + conf, j = x[:, 5:].max(1, keepdim=True) + box_tensor = torch.tensor(box) if not isinstance(box, torch.Tensor) else box + x = torch.cat((box_tensor, conf, j.float()), 1)[conf.view(-1) > conf_thres] + + # Filter by class + if classes is not None: + x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)] + + # Apply finite constraint + if not torch.isfinite(x).all(): + x = x[torch.isfinite(x).all(1)] + + # Check shape + n = x.shape[0] # number of boxes + if not n: # no boxes + continue + elif n > max_nms: # excess boxes + x = x[x[:, 4].argsort(descending=True)[:max_nms]] # sort by confidence + + # Batched NMS + c = x[:, 5:6] * (0 if agnostic else max_wh) # classes + boxes, scores = x[:, :4] + c, x[:, 4] # boxes (offset by class), scores + i = torchvision.ops.nms(boxes, scores, iou_thres) # NMS + i = i[:max_det] # limit detections + + output[xi] = x[i] + if (time.time() - time.time()) > time_limit: + break # time limit exceeded + + return output + +def xyxy2xywh(x): + """ + Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] where xy1=top-left, xy2=bottom-right + """ + y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) + y[:, 0] = (x[:, 0] + x[:, 2]) / 2 # x center + y[:, 1] = (x[:, 1] + x[:, 3]) / 2 # y center + y[:, 2] = x[:, 2] - x[:, 0] # width + y[:, 3] = x[:, 3] - x[:, 1] # height + return y + +def xywh2xyxy(x): + """ + Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right + """ + y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) + y[:, 0] = x[:, 0] - x[:, 2] / 2 # top left x + y[:, 1] = x[:, 1] - x[:, 3] / 2 # top left y + y[:, 2] = x[:, 0] + x[:, 2] / 2 # bottom right x + y[:, 3] = x[:, 1] + x[:, 3] / 2 # bottom right y + return y \ No newline at end of file