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 0000000..82a950b Binary files /dev/null and b/ghub_move/ghub_device.dll differ 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