Загрузка данных


import mss
import numpy as np
import win32api
import win32con
import ctypes
import threading
import time
import tkinter as tk
from tkinter import ttk, messagebox
import uuid
import random
import sys
import math
import cv2
import queue

# ==========================================
# ЯДРО ОПТИМИЗАЦИИ ДЛЯ СЛАБЫХ ПК
# ==========================================
# 1. Форсируем использование векторных инструкций процессора (AVX2/SSE4)
cv2.setUseOptimized(True)
# 2. Выделяем все доступные ядра CPU под обработку кадров
cv2.setNumThreads(max(1, cv2.getNumberOfCPUs() - 1))
# 3. Отключаем OpenCL (Запрещаем перекидывать кадры на слабую видеокарту ноутбука, это убирает Input Lag)
cv2.ocl.setUseOpenCL(False)

try:
    from makcu import create_controller, MouseButton
    MAKCU_SDK_AVAILABLE = True
except ImportError:
    MAKCU_SDK_AVAILABLE = False
    class MouseButton:
        LEFT = 1; RIGHT = 2; MIDDLE = 3; MOUSE4 = 4; MOUSE5 = 5
    print("Библиотека makcu не найдена. Установите: pip install makcu")

try:
    from pygrabber.dshow_graph import FilterGraph
    PYGRABBER_AVAILABLE = True
except ImportError:
    PYGRABBER_AVAILABLE = False

try:
    from PIL import Image, ImageTk
    PILLOW_AVAILABLE = True
except ImportError:
    PILLOW_AVAILABLE = False

try:
    ctypes.windll.winmm.timeBeginPeriod(1)
    ctypes.windll.user32.SetProcessDPIAware()
except:
    pass

MOUSEEVENTF_MOVE = 0x0001
MOUSEEVENTF_LEFTDOWN = 0x0002
MOUSEEVENTF_LEFTUP = 0x0004

def get_vk_name(vk_code):
    special_keys = {
        0x01: "Makcu Left", 0x02: "Makcu Right", 0x04: "Makcu Middle",
        0x05: "Makcu Mouse 4", 0x06: "Makcu Mouse 5", 0x08: "Backspace",
        0x09: "Tab", 0x10: "Shift", 0x11: "Ctrl", 0x12: "Alt",
        0x14: "Caps Lock", 0x1B: "Esc", 0x20: "Space"
    }
    if vk_code in special_keys: return special_keys[vk_code]
    if 0x30 <= vk_code <= 0x5A: return chr(vk_code)
    return f"Key [0x{vk_code:02X}]"

def set_random_console_title():
    try: ctypes.windll.kernel32.SetConsoleTitleW(str(uuid.uuid4())[:8])
    except: pass

set_random_console_title()

class CaptureManager:
    """Индустриальный DSHOW захват с Hardware-ускорением."""
    def __init__(self):
        self.capture = None
        self.current_frame = None
        self.lock = threading.Lock()
        self.running = False
        self.thread = None
        self.device_index = 0

    def list_capture_devices(self):
        if PYGRABBER_AVAILABLE:
            try:
                graph = FilterGraph()
                devices = graph.get_input_devices()
                if devices: return [f"{i}: {name}" for i, name in enumerate(devices)]
            except: pass
        
        devices = []
        for index in range(5):
            cap = cv2.VideoCapture(index, cv2.CAP_DSHOW)
            if cap.isOpened():
                devices.append(f"Device {index} (Generic)")
                cap.release()
        return devices

    def start_capture(self, device_index, width=1920, height=1080):
        self.stop_capture()
        self.device_index = device_index
        self.capture = cv2.VideoCapture(device_index, cv2.CAP_DSHOW)
        
        if not self.capture.isOpened():
            raise Exception(f"Не удалось открыть устройство {device_index}")
        
        # Строгий порядок: кодек -> разрешение -> буфер
        self.capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M','J','P','G'))
        self.capture.set(cv2.CAP_PROP_FRAME_WIDTH, width)
        self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
        self.capture.set(cv2.CAP_PROP_FPS, 60)
        self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 1) 
        
        self.running = True
        self.thread = threading.Thread(target=self._capture_loop, daemon=True)
        self.thread.start()

    def _capture_loop(self):
        empty_frames = 0
        while self.running and self.capture and self.capture.isOpened():
            ret, frame = self.capture.read()
            if ret:
                empty_frames = 0
                with self.lock:
                    self.current_frame = frame
            else:
                empty_frames += 1
                if empty_frames > 150: break # Защита от зависания USB
                time.sleep(0.002)
        self.running = False

    def get_frame(self):
        with self.lock:
            if self.current_frame is not None:
                return self.current_frame.copy()
            return None

    def stop_capture(self):
        self.running = False
        if self.thread and self.thread.is_alive():
            self.thread.join(timeout=1)
        if self.capture:
            self.capture.release()
            self.capture = None
        self.current_frame = None

    def is_active(self):
        return self.running and self.capture is not None and self.capture.isOpened()

class MakcuManager:
    """Менеджер аппаратной мыши 2PC."""
    def __init__(self):
        self.controller = None
        self.connected = False
        self.button_mapping = {
            0x01: MouseButton.LEFT, 0x02: MouseButton.RIGHT,
            0x04: MouseButton.MIDDLE, 0x05: MouseButton.MOUSE4,
            0x06: MouseButton.MOUSE5
        }

    def auto_connect(self):
        if not MAKCU_SDK_AVAILABLE: return False
        try:
            self.controller = create_controller(debug=False, auto_reconnect=True)
            self.connected = self.controller.is_connected()
            if self.connected:
                self.controller.enable_button_monitoring(True)
            return self.connected
        except:
            self.connected = False
            return False

    def disconnect(self):
        if self.controller and self.connected:
            try: self.controller.disconnect()
            except: pass
        self.connected = False
        self.controller = None

    def is_button_pressed(self, vk_code):
        makcu_btn = self.button_mapping.get(vk_code)
        if self.connected and self.controller and makcu_btn:
            try:
                if self.controller.is_pressed(makcu_btn): return True
            except: pass
        return (ctypes.windll.user32.GetAsyncKeyState(vk_code) & 0x8000) != 0

    def send_mouse_move(self, dx, dy):
        if not self.connected or not self.controller: return False
        dx = max(-127, min(127, int(dx)))
        dy = max(-127, min(127, int(dy)))
        if dx == 0 and dy == 0: return True
        try:
            self.controller.move(dx, dy)
            return True
        except: return False

    def send_mouse_click(self, vk_code=0x01, down=True):
        if not self.connected or not self.controller: return False
        makcu_btn = self.button_mapping.get(vk_code)
        if not makcu_btn: return False
        try:
            if down: self.controller.press(makcu_btn)
            else: self.controller.release(makcu_btn)
            return True
        except: return False


class StealthColorBot:
    def __init__(self):
        self.root = tk.Tk()
        self.root.overrideredirect(True)
        self.root.geometry("440x880")
        self.root.configure(bg="#050505")
        self.root.attributes("-topmost", True)
        self.root.attributes("-alpha", 0.97)

        try:
            hwnd = ctypes.windll.user32.GetParent(self.root.winfo_id())
            style = ctypes.windll.user32.GetWindowLongW(hwnd, -20)
            style = style | 0x00000080 | 0x00000008
            ctypes.windll.user32.SetWindowLongW(hwnd, -20, style)
        except: pass

        self.running = True
        self.active = False
        self.visible = True

        self.capture_manager = CaptureManager()
        self.makcu = MakcuManager()

        self.mouse_lock = threading.Lock()
        self.has_target = False
        self.target_rel_x = 0.0
        self.target_rel_y = 0.0

        # Настройки Аима
        self.tracking_enabled = tk.BooleanVar(value=True)
        self.tracking_key = 0x06  
        self.target_color = (255, 0, 0)
        self.target_color_bgr = (0, 0, 255)
        self.fov = 120
        self.tolerance = 15

        # Индустриальный Bone Targeting
        self.target_bone_var = tk.StringVar(value="HEAD") # HEAD (Макушка) или CENTER (Тело)
        self.head_offset_y = 5 # Пикселей вниз от самого верхнего пикселя обводки (чтобы пуля летела в лицо)

        # PD-Kinematics & Stretched Fix
        self.smoothing_factor = 0.6  
        self.max_speed = 30.0        
        self.x_multiplier = 0.75  # Идеально для 1440x1080 по умолчанию    
        self.y_multiplier = 1.0
        self.deadzone = 1.0 # Анти-тряска
        
        self.last_error_x = 0.0
        self.last_error_y = 0.0

        self.pending_move_x = 0.0
        self.pending_move_y = 0.0
        self.remainder_x = 0.0
        self.remainder_y = 0.0

        # Триггербот
        self.trigger_enabled = tk.BooleanVar(value=False)
        self.trigger_key = 0x05  
        self.trigger_tolerance = 15
        self.trigger_color = (255, 0, 0)
        self.trigger_color_bgr = (0, 0, 255)
        self.is_clicking = False
        self.trigger_queue = queue.Queue()

        self.min_reaction = 0.0
        self.max_reaction = 0.03
        self.min_hold = 0.03
        self.max_hold = 0.07

        # Vision Filters (Склейка разорванных пикселей карты захвата)
        self.color_amp = 1 # Dilation по умолчанию 1 (спаивает обводку Valorant)
        self.min_area = 5  # Игнорировать искры

        # Превью
        self.show_fov_overlay = tk.BooleanVar(value=True)
        self.show_preview_window = tk.BooleanVar(value=False)
        self._latest_preview_frame = None
        self.preview_tk_window = None
        self.preview_tk_label = None

        self.scan_interval = 0.001
        self.mouse_send_rate = 144
        self.mouse_send_interval = 1.0 / self.mouse_send_rate
        self.last_mouse_send_time = time.perf_counter()
        self.last_aim_time = time.perf_counter()
        self.last_trigger_time = time.perf_counter()

        self.screen_width = win32api.GetSystemMetrics(0)
        self.screen_height = win32api.GetSystemMetrics(1)
        self.local_center_x = self.screen_width // 2
        self.local_center_y = self.screen_height // 2

        self.capture_source_var = tk.StringVar(value="capture")
        self.capture_device_list = []
        self.capture_device_index = 0
        self.capture_status_text = tk.StringVar(value="Not connected")
        
        self.cap_res_width = tk.IntVar(value=1920)
        self.cap_res_height = tk.IntVar(value=1080)

        self.aim_bounds = []
        self.trig_bounds = []

        self.setup_ui()
        self.setup_overlay()
        self._update_hsv_cache()

        threading.Thread(target=self.logic_loop, daemon=True).start()
        threading.Thread(target=self.mouse_movement_loop, daemon=True).start()
        threading.Thread(target=self.key_listener, daemon=True).start()
        threading.Thread(target=self.trigger_worker, daemon=True).start()
        threading.Thread(target=self._auto_connect_makcu, daemon=True).start()
        
        self.root.after(30, self.update_preview_ui_loop)

    def _auto_connect_makcu(self):
        attempts = 0
        while self.running and not self.makcu.connected:
            try:
                if self.makcu.auto_connect():
                    self.root.after(0, lambda: self.makcu_status_label.config(text="Status: Connected (SDK)", fg="#00ff00"))
                    break
                else:
                    time.sleep(3)
                    attempts += 1
                    if attempts > 10: break
            except: time.sleep(2)

    def _get_hsv_bounds(self, bgr_color, tol):
        bgr = np.uint8([[bgr_color]])
        hsv = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV)[0][0]
        h, s, v = int(hsv[0]), int(hsv[1]), int(hsv[2])
        
        lower_s, upper_s = max(0, s - tol), min(255, s + tol)
        lower_v, upper_v = max(0, v - tol), min(255, v + tol)
        h_low, h_high = h - tol, h + tol
        
        bounds = []
        if h_low < 0:
            bounds.append((np.array([0, lower_s, lower_v], dtype=np.uint8), np.array([h_high, upper_s, upper_v], dtype=np.uint8)))
            bounds.append((np.array([179 + h_low, lower_s, lower_v], dtype=np.uint8), np.array([179, upper_s, upper_v], dtype=np.uint8)))
        elif h_high > 179:
            bounds.append((np.array([h_low, lower_s, lower_v], dtype=np.uint8), np.array([179, upper_s, upper_v], dtype=np.uint8)))
            bounds.append((np.array([0, lower_s, lower_v], dtype=np.uint8), np.array([h_high - 179, upper_s, upper_v], dtype=np.uint8)))
        else:
            bounds.append((np.array([h_low, lower_s, lower_v], dtype=np.uint8), np.array([h_high, upper_s, upper_v], dtype=np.uint8)))
        return bounds

    def _update_hsv_cache(self):
        self.aim_bounds = self._get_hsv_bounds(self.target_color_bgr, self.tolerance)
        self.trig_bounds = self._get_hsv_bounds(self.trigger_color_bgr, self.trigger_tolerance)

    def apply_mask(self, hsv_img, bounds, amplification):
        final_mask = None
        for lower, upper in bounds:
            m = cv2.inRange(hsv_img, lower, upper)
            if final_mask is None: final_mask = m
            else: final_mask = cv2.bitwise_or(final_mask, m)
                
        # Спаивание размытых пикселей карты видеозахвата
        if amplification > 0 and final_mask is not None:
            kernel = np.ones((3, 3), np.uint8)
            final_mask = cv2.dilate(final_mask, kernel, iterations=int(amplification))
            
        return final_mask

    # ---------- UI Setup ----------
    def setup_ui(self):
        header = tk.Frame(self.root, bg="#12001f", height=30)
        header.pack(fill="x")
        self.make_draggable(header)
        title_lbl = tk.Label(header, text="angelpriv - Valorant Premium", bg="#12001f", fg="#b042ff", font=("Consolas", 11, "bold"))
        title_lbl.pack(side="left", padx=10)
        self.make_draggable(title_lbl)
        close_btn = tk.Button(header, text="X", bg="#12001f", fg="#b042ff", bd=0, activebackground="#36005c", activeforeground="#ffffff", command=self.exit_app, font=("Candara", 10, "bold"))
        close_btn.pack(side="right", padx=5)

        tab_frame = tk.Frame(self.root, bg="#0a0011", height=30)
        tab_frame.pack(fill="x")
        
        self.btn_tab_aim = tk.Button(tab_frame, text="Aim", bg="#36005c", fg="#e0e0e0", bd=0, font=("Candara", 9, "bold"), command=self.show_aim_tab)
        self.btn_tab_aim.pack(side="left", fill="both", expand=True)
        self.btn_tab_trig = tk.Button(tab_frame, text="Trigger", bg="#12001f", fg="#e0e0e0", bd=0, font=("Candara", 9, "bold"), command=self.show_trig_tab)
        self.btn_tab_trig.pack(side="left", fill="both", expand=True)
        self.btn_tab_vis = tk.Button(tab_frame, text="Vision", bg="#12001f", fg="#e0e0e0", bd=0, font=("Candara", 9, "bold"), command=self.show_vis_tab)
        self.btn_tab_vis.pack(side="left", fill="both", expand=True)
        self.btn_tab_capture = tk.Button(tab_frame, text="Capture", bg="#12001f", fg="#e0e0e0", bd=0, font=("Candara", 9, "bold"), command=self.show_capture_tab)
        self.btn_tab_capture.pack(side="left", fill="both", expand=True)
        self.btn_tab_makcu = tk.Button(tab_frame, text="Makcu", bg="#12001f", fg="#e0e0e0", bd=0, font=("Candara", 9, "bold"), command=self.show_makcu_tab)
        self.btn_tab_makcu.pack(side="left", fill="both", expand=True)

        self.content_container = tk.Frame(self.root, bg="#050505")
        self.content_container.pack(fill="both", expand=True, padx=15, pady=5)

        # === ВКЛАДКА AIM ===
        self.aim_frame = tk.Frame(self.content_container, bg="#050505")
        tk.Checkbutton(self.aim_frame, text="Enable Aimbot", variable=self.tracking_enabled, bg="#050505", fg="#e0e0e0", selectcolor="#1a1a1a", activebackground="#050505", activeforeground="#b042ff", font=("Candara", 10)).pack(anchor="w")
        tk.Checkbutton(self.aim_frame, text="Show FOV Overlay (Screen 2)", variable=self.show_fov_overlay, bg="#050505", fg="#e0e0e0", selectcolor="#1a1a1a", activebackground="#050505", activeforeground="#b042ff", font=("Candara", 10)).pack(anchor="w")
        bind_frame_aim = tk.Frame(self.aim_frame, bg="#050505")
        bind_frame_aim.pack(fill="x", pady=5)
        tk.Label(bind_frame_aim, text="Aim Activation:", bg="#050505", fg="#e0e0e0").pack(side="left")
        self.bind_btn_aim = tk.Button(bind_frame_aim, text=f"Bind: {get_vk_name(self.tracking_key)}", bg="#1a1a1a", fg="#b042ff", bd=0, command=lambda: self.start_keybind('aim'))
        self.bind_btn_aim.pack(side="right", padx=5)
        
        self.create_inline_color_picker(self.aim_frame, "Target Base Color", "target_color")
        self.create_slider(self.aim_frame, "Color Tolerance", 0, 100, self.tolerance, "tolerance")
        self.create_slider(self.aim_frame, "Tracking FOV (Adaptive)", 10, 800, self.fov, "fov")

        # Topmost Pixel Bone Selection
        bone_frame = tk.Frame(self.aim_frame, bg="#050505")
        bone_frame.pack(fill="x", pady=5)
        tk.Label(bone_frame, text="Bone:", bg="#050505", fg="#e0e0e0").pack(side="left")
        tk.Radiobutton(bone_frame, text="TOPMOST (Head)", variable=self.target_bone_var, value="HEAD", bg="#050505", fg="#00ff00", selectcolor="#1a1a1a", activebackground="#050505", activeforeground="#00ff00").pack(side="left")
        tk.Radiobutton(bone_frame, text="CENTER (Body)", variable=self.target_bone_var, value="CENTER", bg="#050505", fg="#e0e0e0", selectcolor="#1a1a1a", activebackground="#050505", activeforeground="#b042ff").pack(side="left")
        
        self.create_slider(self.aim_frame, "Head Y-Offset (Down from top pixel)", 0, 30, self.head_offset_y, "head_offset_y", res=1)

        tk.Label(self.aim_frame, text="- Sticky Kinematics (Zero Overshoot) -", bg="#050505", fg="#5A5A5A", font=("Candara", 9)).pack(pady=(5, 0))
        self.create_slider(self.aim_frame, "Sticky Pull (Speed)", 0.05, 1.0, self.smoothing_factor, "smoothing_factor", res=0.05)
        self.create_slider(self.aim_frame, "Deadzone (Anti-Jitter Pixels)", 0.0, 10.0, self.deadzone, "deadzone", res=0.5)
        self.create_slider(self.aim_frame, "Max Pixels per Tick", 1.0, 50.0, self.max_speed, "max_speed", res=1.0)
        
        tk.Label(self.aim_frame, text="- 4:3 Stretched Fix -", bg="#050505", fg="#5A5A5A", font=("Candara", 9)).pack(pady=(5, 0))
        self.create_slider(self.aim_frame, "X-Axis Multiplier (1440x1080 = 0.75)", 0.1, 2.0, self.x_multiplier, "x_multiplier", res=0.05)
        self.create_slider(self.aim_frame, "Y-Axis Multiplier", 0.1, 2.0, self.y_multiplier, "y_multiplier", res=0.05)

        # === ВКЛАДКА TRIGGER ===
        self.trig_frame = tk.Frame(self.content_container, bg="#050505")
        tk.Checkbutton(self.trig_frame, text="Enable Event Trigger", variable=self.trigger_enabled, bg="#050505", fg="#e0e0e0", selectcolor="#1a1a1a", activebackground="#050505", activeforeground="#b042ff", font=("Candara", 10)).pack(anchor="w")
        bind_frame_trig = tk.Frame(self.trig_frame, bg="#050505")
        bind_frame_trig.pack(fill="x", pady=5)
        tk.Label(bind_frame_trig, text="Trigger Activation:", bg="#050505", fg="#e0e0e0").pack(side="left")
        self.bind_btn_trig = tk.Button(bind_frame_trig, text=f"Bind: {get_vk_name(self.trigger_key)}", bg="#1a1a1a", fg="#b042ff", bd=0, command=lambda: self.start_keybind('trig'))
        self.bind_btn_trig.pack(side="right", padx=5)
        self.create_inline_color_picker(self.trig_frame, "Action Base Color", "trigger_color")
        self.create_slider(self.trig_frame, "Trigger Tolerance", 0, 100, self.trigger_tolerance, "trigger_tolerance")
        tk.Label(self.trig_frame, text="- Humanized Constraints -", bg="#050505", fg="#5A5A5A", font=("Candara", 9)).pack(pady=(10, 0))
        self.create_slider(self.trig_frame, "Min Reaction (s)", 0.0, 0.1, self.min_reaction, "min_reaction", res=0.005)
        self.create_slider(self.trig_frame, "Max Reaction (s)", 0.0, 0.2, self.max_reaction, "max_reaction", res=0.005)
        self.create_slider(self.trig_frame, "Min Hold (s)", 0.01, 0.1, self.min_hold, "min_hold", res=0.01)
        self.create_slider(self.trig_frame, "Max Hold (s)", 0.02, 0.2, self.max_hold, "max_hold", res=0.01)

        # === ВКЛАДКА VISION ===
        self.vis_frame = tk.Frame(self.content_container, bg="#050505")
        tk.Label(self.vis_frame, text="Advanced Vision & Masking", bg="#050505", fg="#e0e0e0", font=("Candara", 10, "bold")).pack(anchor="w", pady=(5,0))
        tk.Label(self.vis_frame, text="Склейка тонкой обводки врагов (Morphological Dilation).", bg="#050505", fg="#5A5A5A", font=("Candara", 8)).pack(anchor="w")
        self.create_slider(self.vis_frame, "Color Amplification (Рекомендуется 1-2)", 0, 10, self.color_amp, "color_amp", res=1)
        self.create_slider(self.vis_frame, "Noise Filter (Ignored Pixels Size)", 1, 50, self.min_area, "min_area", res=1)

        # === ВКЛАДКА CAPTURE ===
        self.capture_frame = tk.Frame(self.content_container, bg="#050505")
        tk.Label(self.capture_frame, text="Video Source (2PC Capture)", bg="#050505", fg="#e0e0e0", font=("Candara", 10, "bold")).pack(anchor="w", pady=(5,0))
        tk.Radiobutton(self.capture_frame, text="Local Screen (mss) - 1PC Only", variable=self.capture_source_var, value="screen", bg="#050505", fg="#e0e0e0", selectcolor="#1a1a1a", activebackground="#050505", activeforeground="#b042ff", font=("Candara", 10)).pack(anchor="w")
        tk.Radiobutton(self.capture_frame, text="Capture Card (USB HDMI) - 2PC", variable=self.capture_source_var, value="capture", bg="#050505", fg="#e0e0e0", selectcolor="#1a1a1a", activebackground="#050505", activeforeground="#b042ff", font=("Candara", 10)).pack(anchor="w")
        
        list_frame = tk.Frame(self.capture_frame, bg="#050505")
        list_frame.pack(fill="x", pady=5)
        self.device_combo = ttk.Combobox(list_frame, state="readonly", width=35)
        self.device_combo.pack(side="left", padx=2)
        self.device_combo.bind("<<ComboboxSelected>>", self.on_device_selected)
        btn_refresh = tk.Button(list_frame, text="↻", bg="#1a1a1a", fg="#b042ff", bd=0, command=self.refresh_devices, width=3)
        btn_refresh.pack(side="left", padx=2)

        res_frame = tk.Frame(self.capture_frame, bg="#050505")
        res_frame.pack(fill="x", pady=5)
        tk.Label(res_frame, text="Resolution:", bg="#050505", fg="#e0e0e0").pack(side="left")
        tk.Entry(res_frame, textvariable=self.cap_res_width, width=5, bg="#1a1a1a", fg="#e0e0e0", bd=0).pack(side="left", padx=2)
        tk.Label(res_frame, text="x", bg="#050505", fg="#e0e0e0").pack(side="left")
        tk.Entry(res_frame, textvariable=self.cap_res_height, width=5, bg="#1a1a1a", fg="#e0e0e0", bd=0).pack(side="left", padx=2)
        
        tk.Checkbutton(self.capture_frame, text="Show Live Capture Preview (Debug)", variable=self.show_preview_window, bg="#050505", fg="#00ff00", selectcolor="#1a1a1a", activebackground="#050505", activeforeground="#00ff00", font=("Candara", 10, "bold")).pack(anchor="w", pady=(10,0))
        
        btn_start_cap = tk.Button(self.capture_frame, text="Start Video Stream", bg="#1a1a1a", fg="#b042ff", bd=0, command=self.start_capture_card)
        btn_start_cap.pack(fill="x", pady=5)
        self.capture_status_label = tk.Label(self.capture_frame, textvariable=self.capture_status_text, bg="#050505", fg="#a0a0a0", font=("Candara", 9))
        self.capture_status_label.pack(anchor="w")
        self.refresh_devices()

        # === ВКЛАДКА MAKCU ===
        self.makcu_frame = tk.Frame(self.content_container, bg="#050505")
        tk.Label(self.makcu_frame, text="Makcu Device", bg="#050505", fg="#e0e0e0", font=("Candara", 10, "bold")).pack(anchor="w", pady=(5,0))
        self.makcu_status_label = tk.Label(self.makcu_frame, text="Status: Not connected", bg="#050505", fg="#a0a0a0", font=("Candara", 9))
        self.makcu_status_label.pack(anchor="w")
        btn_auto = tk.Button(self.makcu_frame, text="Auto-connect", bg="#1a1a1a", fg="#b042ff", bd=0, command=self.auto_connect_makcu)
        btn_auto.pack(fill="x", pady=5)
        btn_disconnect = tk.Button(self.makcu_frame, text="Disconnect Makcu", bg="#1a1a1a", fg="#b042ff", bd=0, command=self.disconnect_makcu)
        btn_disconnect.pack(fill="x", pady=2)

        self.aim_frame.pack(fill="both", expand=True)

        bottom_frame = tk.Frame(self.root, bg="#050505")
        bottom_frame.pack(fill="x", side="bottom", padx=15, pady=10)
        self.toggle_btn = tk.Button(bottom_frame, text="START ENGINE", bg="#1a1a1a", fg="#b042ff", font=("Arial", 9, "bold"), bd=0, activebackground="#2a2a2a", activeforeground="#ffffff", command=self.toggle_active, height=2, cursor="hand2")
        self.toggle_btn.pack(fill="x")
        tk.Label(bottom_frame, text="Press [END] to Hide Menu\nRight-Click colors to Copy/Paste", bg="#050505", fg="#3D3D3D", font=("Segoe UI", 7)).pack(pady=(2,0))

    def make_draggable(self, widget):
        widget.bind("<Button-1>", self.on_drag_start)
        widget.bind("<B1-Motion>", self.on_drag_motion)

    def on_drag_start(self, event):
        self.root._drag_data = {"x": event.x, "y": event.y}

    def on_drag_motion(self, event):
        x = self.root.winfo_x() + (event.x - self.root._drag_data["x"])
        y = self.root.winfo_y() + (event.y - self.root._drag_data["y"])
        self.root.geometry(f"+{x}+{y}")

    def show_aim_tab(self):
        self._hide_all_tabs(); self.aim_frame.pack(fill="both", expand=True); self._color_tabs(self.btn_tab_aim)

    def show_trig_tab(self):
        self._hide_all_tabs(); self.trig_frame.pack(fill="both", expand=True); self._color_tabs(self.btn_tab_trig)

    def show_vis_tab(self):
        self._hide_all_tabs(); self.vis_frame.pack(fill="both", expand=True); self._color_tabs(self.btn_tab_vis)

    def show_capture_tab(self):
        self._hide_all_tabs(); self.capture_frame.pack(fill="both", expand=True); self._color_tabs(self.btn_tab_capture)

    def show_makcu_tab(self):
        self._hide_all_tabs(); self.makcu_frame.pack(fill="both", expand=True); self._color_tabs(self.btn_tab_makcu)

    def _hide_all_tabs(self):
        self.aim_frame.pack_forget(); self.trig_frame.pack_forget(); self.vis_frame.pack_forget()
        self.capture_frame.pack_forget(); self.makcu_frame.pack_forget()

    def _color_tabs(self, active_btn):
        for btn in [self.btn_tab_aim, self.btn_tab_trig, self.btn_tab_vis, self.btn_tab_capture, self.btn_tab_makcu]:
            btn.config(bg="#36005c" if btn == active_btn else "#12001f")

    def refresh_devices(self):
        threading.Thread(target=lambda: self.root.after(0, self._update_device_combo_task), daemon=True).start()

    def _update_device_combo_task(self):
        devices = self.capture_manager.list_capture_devices()
        self.capture_device_list = devices
        if self.capture_device_list:
            self.device_combo['values'] = self.capture_device_list
            self.device_combo.current(0)
            self._extract_device_index(self.capture_device_list[0])
        else:
            self.device_combo['values'] = []
            self.capture_status_text.set("No devices found")

    def on_device_selected(self, event):
        selection_idx = self.device_combo.current()
        if 0 <= selection_idx < len(self.capture_device_list):
            self._extract_device_index(self.capture_device_list[selection_idx])

    def _extract_device_index(self, selected_str):
        if ":" in selected_str:
            try: self.capture_device_index = int(selected_str.split(":")[0])
            except: pass
        elif "Device" in selected_str:
            try: self.capture_device_index = int(selected_str.split(" ")[1])
            except: pass

    def start_capture_card(self):
        def _start():
            if self.capture_source_var.get() == "screen":
                self.root.after(0, lambda: self.capture_status_text.set("Ready: Local Screen (mss)"))
            else:
                w = self.cap_res_width.get(); h = self.cap_res_height.get()
                self.root.after(0, lambda: self.capture_status_text.set(f"Connecting to Capture Card ({w}x{h})..."))
                try:
                    self.capture_manager.start_capture(self.capture_device_index, width=w, height=h)
                    time.sleep(2.0)
                    frame = self.capture_manager.get_frame()
                    if frame is not None:
                        real_h, real_w = frame.shape[:2]
                        self.root.after(0, lambda: self.capture_status_text.set(f"Stream Active: {real_w}x{real_h}"))
                    else:
                        self.capture_manager.stop_capture()
                        self.root.after(0, lambda: self.capture_status_text.set("Stream Error (Black Screen)"))
                except Exception as e:
                    self.root.after(0, lambda: self.capture_status_text.set(f"Error: {e}"))
        threading.Thread(target=_start, daemon=True).start()

    def auto_connect_makcu(self):
        def _auto():
            try:
                if self.makcu.auto_connect():
                    self.root.after(0, lambda: self.makcu_status_label.config(text="Status: Connected (SDK)", fg="#00ff00"))
                else:
                    self.root.after(0, lambda: messagebox.showerror("Makcu", "Подключение не удалось."))
            except Exception as e:
                self.root.after(0, lambda: messagebox.showerror("Makcu", str(e)))
        threading.Thread(target=_auto, daemon=True).start()

    def connect_makcu(self): self.auto_connect_makcu()
    def disconnect_makcu(self):
        self.makcu.disconnect()
        self.makcu_status_label.config(text="Status: Not connected", fg="#a0a0a0")

    def create_inline_color_picker(self, parent, label_text, target_attr):
        frame = tk.Frame(parent, bg="#0a0a0a", highlightthickness=1, highlightbackground="#1a1a1a")
        frame.pack(fill="x", pady=5)
        top_frame = tk.Frame(frame, bg="#0a0a0a")
        top_frame.pack(fill="x", padx=5, pady=5)
        tk.Label(top_frame, text=label_text, bg="#0a0a0a", fg="#e0e0e0", font=("Candara", 9, "bold")).pack(side="left")
        preview_canvas = tk.Canvas(top_frame, width=24, height=24, bg="#ffffff", highlightthickness=1, highlightbackground="#36005c", cursor="hand2")
        preview_canvas.pack(side="right")
        color_label = tk.Label(top_frame, text="", bg="#0a0a0a", fg="#a0a0a0", font=("Consolas", 8), cursor="hand2")
        color_label.pack(side="right", padx=10)
        sliders_frame = tk.Frame(frame, bg="#0a0a0a")
        sliders_frame.pack(fill="x", padx=5, pady=(0, 5))

        current_rgb = getattr(self, target_attr)
        r_var, g_var, b_var = tk.IntVar(value=current_rgb[0]), tk.IntVar(value=current_rgb[1]), tk.IntVar(value=current_rgb[2])

        def update_color(*args):
            r, g, b = r_var.get(), g_var.get(), b_var.get()
            setattr(self, target_attr, (r, g, b))
            setattr(self, target_attr + '_bgr', (b, g, r))
            self._update_hsv_cache()
            hex_color = '#%02x%02x%02x' % (r, g, b)
            color_label.config(text=f"({r},{g},{b}) {hex_color.upper()}")
            preview_canvas.config(bg=hex_color)

        def copy_color():
            hex_color = '#%02x%02x%02x' % (r_var.get(), g_var.get(), b_var.get())
            self.root.clipboard_clear()
            self.root.clipboard_append(hex_color.upper())

        def paste_color():
            try:
                cb = self.root.clipboard_get().strip().replace(' ', '')
                if len(cb) == 7 and cb.startswith('#'):
                    r, g, b = tuple(int(cb.lstrip('#')[i:i+2], 16) for i in (0, 2, 4))
                elif ',' in cb:
                    parts = cb.split(',')
                    if len(parts) == 3: r, g, b = int(parts[0]), int(parts[1]), int(parts[2])
                else: return
                r_var.set(r); g_var.set(g); b_var.set(b)
                update_color()
            except: pass
            
        def preset_purple():
            r_var.set(200); g_var.set(0); b_var.set(255)
            self.tolerance = 25; self.update_attr("tolerance", 25)
            update_color()

        def preset_yellow():
            r_var.set(255); g_var.set(255); b_var.set(0)
            self.tolerance = 15; self.update_attr("tolerance", 15)
            update_color()

        c_menu = tk.Menu(self.root, tearoff=0, bg="#1a1a1a", fg="#e0e0e0", bd=0, activebackground="#36005c")
        c_menu.add_command(label="Copy Hex", command=copy_color)
        c_menu.add_command(label="Paste Color", command=paste_color)
        c_menu.add_separator()
        c_menu.add_command(label="Valorant: Purple (Meta)", command=preset_purple)
        c_menu.add_command(label="Valorant: Yellow", command=preset_yellow)
        
        preview_canvas.bind("<Button-3>", lambda e: c_menu.tk_popup(e.x_root, e.y_root))
        color_label.bind("<Button-3>", lambda e: c_menu.tk_popup(e.x_root, e.y_root))

        r_scale = tk.Scale(sliders_frame, from_=0, to=255, variable=r_var, orient="horizontal", bg="#0a0a0a", fg="#ff4d4d", troughcolor="#1a0000", highlightthickness=0, bd=0, activebackground="#330000", command=update_color, showvalue=0)
        r_scale.pack(fill="x")
        g_scale = tk.Scale(sliders_frame, from_=0, to=255, variable=g_var, orient="horizontal", bg="#0a0a0a", fg="#4dff4d", troughcolor="#001a00", highlightthickness=0, bd=0, activebackground="#003300", command=update_color, showvalue=0)
        g_scale.pack(fill="x")
        b_scale = tk.Scale(sliders_frame, from_=0, to=255, variable=b_var, orient="horizontal", bg="#0a0a0a", fg="#4d4dff", troughcolor="#00001a", highlightthickness=0, bd=0, activebackground="#000033", command=update_color, showvalue=0)
        b_scale.pack(fill="x")
        update_color()

    def create_slider(self, parent, label_text, min_val, max_val, default_val, attr_name, res=1):
        frame = tk.Frame(parent, bg="#050505")
        frame.pack(fill="x", pady=2)
        tk.Label(frame, text=label_text, bg="#050505", fg="#b5b5b5", font=("Candara", 9)).pack(anchor="w")
        slider = tk.Scale(frame, from_=min_val, to=max_val, resolution=res, orient="horizontal",
                          bg="#050505", fg="#b042ff", troughcolor="#1a1a1a", highlightthickness=0, bd=0, activebackground="#36005c")
        slider.set(default_val)
        slider.pack(fill="x")
        slider.config(command=lambda val, a=attr_name: self.update_attr(a, val))

    def update_attr(self, attr_name, val):
        setattr(self, attr_name, float(val) if '.' in str(val) else int(float(val)))
        if attr_name in ('tolerance', 'trigger_tolerance'):
            self._update_hsv_cache()

    def start_keybind(self, target='aim'):
        if getattr(self, f'_binding_{target}', False): return
        setattr(self, f'_binding_{target}', True)
        btn = self.bind_btn_aim if target == 'aim' else self.bind_btn_trig
        btn.config(text="Listening...")
        threading.Thread(target=self._wait_for_key, args=(target,), daemon=True).start()

    def _wait_for_key(self, target):
        time.sleep(0.4) 
        while self.running:
            if self.makcu.connected and self.makcu.controller:
                for vk_code, makcu_btn in self.makcu.button_mapping.items():
                    try:
                        if self.makcu.controller.is_pressed(makcu_btn):
                            self._apply_bind(target, vk_code)
                            return
                    except: pass
            
            for vk in range(1, 256):
                if ctypes.windll.user32.GetAsyncKeyState(vk) & 0x8000:
                    if vk != 0x1B:  
                        self._apply_bind(target, vk)
                        return
            time.sleep(0.01)

    def _apply_bind(self, target, vk_code):
        if target == 'aim': self.tracking_key = vk_code
        else: self.trigger_key = vk_code
        btn = self.bind_btn_aim if target == 'aim' else self.bind_btn_trig
        val = self.tracking_key if target == 'aim' else self.trigger_key
        self.root.after(0, lambda b=btn, v=val: b.config(text=f"Bind: {get_vk_name(v)}"))
        setattr(self, f'_binding_{target}', False)

    def toggle_active(self):
        self.active = not self.active
        if self.active: self.toggle_btn.config(text="STOP ENGINE", fg="#ff3333", bg="#260000", activebackground="#400000")
        else: self.toggle_btn.config(text="START ENGINE", fg="#b042ff", bg="#1a1a1a", activebackground="#2a2a2a")

    def exit_app(self):
        self.running = False
        self.capture_manager.stop_capture()
        self.makcu.disconnect()
        self.root.destroy()
        sys.exit()

    def key_listener(self):
        while self.running:
            if win32api.GetAsyncKeyState(win32con.VK_END) & 1:
                if self.visible: self.root.after(0, self.root.withdraw)
                else:
                    self.root.after(0, self.root.deiconify)
                    self.root.after(0, self.root.lift)
                    self.root.after(0, self.root.focus_force)
                self.visible = not self.visible
            time.sleep(0.1)

    # ---------- Overlay ----------
    def setup_overlay(self):
        self.overlay = tk.Toplevel(self.root)
        self.overlay.overrideredirect(True)
        self.overlay.attributes("-topmost", True)
        self.overlay.attributes("-transparentcolor", "black")
        self.overlay.config(bg="black")
        self.overlay.geometry(f"{self.screen_width}x{self.screen_height}+0+0")
        hwnd = ctypes.windll.user32.GetParent(self.overlay.winfo_id())
        style = ctypes.windll.user32.GetWindowLongW(hwnd, -20)
        ctypes.windll.user32.SetWindowLongW(hwnd, -20, style | 0x00080000 | 0x00000020)
        
        self.overlay_canvas = tk.Canvas(self.overlay, width=self.screen_width, height=self.screen_height, bg="black", highlightthickness=0)
        self.overlay_canvas.pack()
        self.overlay_circle = self.overlay_canvas.create_oval(0, 0, 0, 0, outline="white", width=1)
        self.last_fov = self.fov; self.last_show = self.show_fov_overlay.get()
        self.update_overlay_loop()

    def update_overlay_loop(self):
        current_show = self.show_fov_overlay.get()
        current_fov = self.fov
        if current_show != self.last_show or current_fov != self.last_fov:
            if current_show:
                self.overlay.deiconify()
                r = int(current_fov) // 2
                self.overlay_canvas.coords(self.overlay_circle, self.local_center_x - r, self.local_center_y - r, self.local_center_x + r, self.local_center_y + r)
            else:
                self.overlay.withdraw()
            self.last_show = current_show; self.last_fov = current_fov
        self.root.after(200, self.update_overlay_loop)

    # ---------- GUI Превью ----------
    def update_preview_ui_loop(self):
        if self.show_preview_window.get() and PILLOW_AVAILABLE:
            if self.preview_tk_window is None or not self.preview_tk_window.winfo_exists():
                self.preview_tk_window = tk.Toplevel(self.root)
                self.preview_tk_window.title("FOV Live Preview (Adaptive Tracker)")
                self.preview_tk_window.attributes("-topmost", True)
                self.preview_tk_window.geometry("300x300")
                self.preview_tk_window.configure(bg="black")
                self.preview_tk_label = tk.Label(self.preview_tk_window, bg="black")
                self.preview_tk_label.pack(fill="both", expand=True)

            if self._latest_preview_frame is not None:
                rgb_img = cv2.cvtColor(self._latest_preview_frame, cv2.COLOR_BGR2RGB)
                img = Image.fromarray(rgb_img)
                imgtk = ImageTk.PhotoImage(image=img)
                self.preview_tk_label.config(image=imgtk)
                self.preview_tk_label.image = imgtk
        else:
            if self.preview_tk_window is not None and self.preview_tk_window.winfo_exists():
                self.preview_tk_window.destroy()
                self.preview_tk_window = None; self.preview_tk_label = None

        self.root.after(30, self.update_preview_ui_loop)

    # ---------- Кинематика: PD-Контроллер ----------
    def calculate_sticky_step(self, target_dx, target_dy, dt):
        target_dx *= self.x_multiplier
        target_dy *= self.y_multiplier

        distance = math.hypot(target_dx, target_dy)
        if distance < self.deadzone:
            self.last_error_x = 0.0
            self.last_error_y = 0.0
            return 0.0, 0.0

        move_x = target_dx * self.smoothing_factor
        move_y = target_dy * self.smoothing_factor
        
        if dt > 0:
            d_x = (target_dx - self.last_error_x) / dt
            d_y = (target_dy - self.last_error_y) / dt
            move_x += d_x * 0.002 
            move_y += d_y * 0.002
            
        self.last_error_x = target_dx
        self.last_error_y = target_dy

        speed = math.hypot(move_x, move_y)
        if speed > self.max_speed:
            scale = self.max_speed / speed
            move_x *= scale
            move_y *= scale

        return move_x, move_y

    def mouse_movement_loop(self):
        while self.running:
            if not self.active or not self.tracking_enabled.get():
                time.sleep(0.01)
                continue

            current_time = time.perf_counter()
            dynamic_interval = self.mouse_send_interval

            dt = current_time - self.last_mouse_send_time
            if dt >= dynamic_interval:
                with self.mouse_lock:
                    has_target = self.has_target
                    rel_x = self.target_rel_x
                    rel_y = self.target_rel_y

                    if has_target:
                        vx, vy = self.calculate_sticky_step(rel_x, rel_y, dt)
                        self.pending_move_x += vx
                        self.pending_move_y += vy
                    else:
                        self.pending_move_x = 0.0
                        self.pending_move_y = 0.0
                        self.remainder_x = 0.0
                        self.remainder_y = 0.0
                        self.last_error_x = 0.0
                        self.last_error_y = 0.0

                    step_x = int(round(self.pending_move_x + self.remainder_x))
                    step_y = int(round(self.pending_move_y + self.remainder_y))
                    self.remainder_x = (self.pending_move_x + self.remainder_x) - step_x
                    self.remainder_y = (self.pending_move_y + self.remainder_y) - step_y

                    final_step_x = step_x; final_step_y = step_y
                    self.pending_move_x = 0.0; self.pending_move_y = 0.0

                if final_step_x != 0 or final_step_y != 0:
                    if self.makcu.connected: self.makcu.send_mouse_move(final_step_x, final_step_y)
                    else: ctypes.windll.user32.mouse_event(MOUSEEVENTF_MOVE, final_step_x, final_step_y, 0, 0)

                self.last_mouse_send_time = current_time
            else:
                pass 

    # ---------- Триггер ----------
    def trigger_worker(self):
        while self.running:
            try:
                item = self.trigger_queue.get(timeout=0.1)
                if item is None: continue
                self._do_trigger()
                self.trigger_queue.task_done()
            except queue.Empty: pass

    def _do_trigger(self):
        reaction = random.lognormvariate(math.log((self.min_reaction + self.max_reaction) / 2), 0.5)
        reaction = max(self.min_reaction, min(self.max_reaction, reaction))
        if reaction > 0.002: time.sleep(reaction)

        hold = random.lognormvariate(math.log((self.min_hold + self.max_hold) / 2), 0.5)
        hold = max(self.min_hold, min(self.max_hold, hold))

        shots = random.choices([1, 2, 3], weights=[70, 20, 10])[0]
        for _ in range(shots):
            if self.makcu.connected:
                self.makcu.send_mouse_click(0x01, True)
                time.sleep(hold)
                self.makcu.send_mouse_click(0x01, False)
            else:
                ctypes.windll.user32.mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0)
                time.sleep(hold)
                ctypes.windll.user32.mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0)
            time.sleep(random.uniform(0.03, 0.08))
        self.is_clicking = False

    def execute_trigger(self):
        if self.is_clicking: return
        self.is_clicking = True
        self.trigger_queue.put(True)

    # ---------- Ультимативный Adaptive ROI Цикл Оптимизации ----------
    def logic_loop(self):
        sct = mss.mss()
        current_roi_size = self.fov 
        
        while self.running:
            if not self.active:
                time.sleep(0.05)
                continue

            loop_start = time.perf_counter()
            max_fov = int(self.fov)

            offset_x, offset_y = 0, 0
            micro_roi_active = False

            # --- 1. Memory-Zero Slicing ---
            if self.capture_source_var.get() == "screen":
                left = max(0, self.local_center_x - max_fov // 2)
                top = max(0, self.local_center_y - max_fov // 2)
                width = min(max_fov, self.screen_width - left)
                height = min(max_fov, self.screen_height - top)
                if width <= 0 or height <= 0: continue
                region = {"left": left, "top": top, "width": width, "height": height}
                try:
                    img_data = sct.grab(region)
                    img_bgra = np.frombuffer(img_data.bgra, dtype=np.uint8).reshape((height, width, 4))
                    full_bgr = img_bgra[:, :, :3]
                except: continue
                
            else:
                if not self.capture_manager.is_active():
                    time.sleep(0.05)
                    continue
                
                frame = self.capture_manager.get_frame()
                if frame is None:
                    continue
                    
                h, w = frame.shape[:2]
                cx, cy = w // 2, h // 2
                half_fov = max_fov // 2
                
                x1 = max(0, cx - half_fov)
                y1 = max(0, cy - half_fov)
                x2 = min(w, cx + half_fov)
                y2 = min(h, cy + half_fov)
                
                full_bgr = frame[y1:y2, x1:x2]

            if 'full_bgr' in locals() and full_bgr.size > 0:
                act_h, act_w = full_bgr.shape[:2]
                center_x, center_y = act_w // 2, act_h // 2
                
                # --- Adaptive ROI: Дышащее окно ---
                if self.tracking_enabled.get() and self.has_target and self.last_raw_target_x is not None:
                    target_x_int = int(self.last_raw_target_x)
                    target_y_int = int(self.last_raw_target_y)
                    
                    half_roi = current_roi_size // 2
                    mx1 = max(0, target_x_int - half_roi)
                    my1 = max(0, target_y_int - half_roi)
                    mx2 = min(act_w, target_x_int + half_roi)
                    my2 = min(act_h, target_y_int + half_roi)
                    
                    search_bgr = full_bgr[my1:my2, mx1:mx2]
                    offset_x, offset_y = mx1, my1
                    micro_roi_active = True
                else:
                    search_bgr = full_bgr

                hsv_search = cv2.cvtColor(search_bgr, cv2.COLOR_BGR2HSV)
                debug_target_pos = None

                # --- 2. Триггербот (Всегда ищет в центре полного FOV) ---
                if self.trigger_enabled.get():
                    now = time.perf_counter()
                    if now - self.last_trigger_time >= self.trigger_interval:
                        self.last_trigger_time = now
                        if self.makcu.is_button_pressed(self.trigger_key):
                            trigger_zone_size = 4
                            local_cx = center_x - offset_x
                            local_cy = center_y - offset_y
                            
                            if 0 <= local_cx <= search_bgr.shape[1] and 0 <= local_cy <= search_bgr.shape[0]:
                                y1_t = max(0, local_cy - trigger_zone_size)
                                y2_t = min(search_bgr.shape[0], local_cy + trigger_zone_size)
                                x1_t = max(0, local_cx - trigger_zone_size)
                                x2_t = min(search_bgr.shape[1], local_cx + trigger_zone_size)
                                
                                hsv_area = hsv_search[y1_t:y2_t, x1_t:x2_t]
                                trig_mask = self.apply_mask(hsv_area, self.trig_bounds, self.color_amp)
                                
                                if trig_mask is not None and cv2.countNonZero(trig_mask) > 0:
                                    self.execute_trigger()

                # --- 3. Аимбот ---
                if self.tracking_enabled.get():
                    now = time.perf_counter()
                    if now - self.last_aim_time >= self.aim_interval:
                        self.last_aim_time = now
                        if self.makcu.is_button_pressed(self.tracking_key):
                            mask = self.apply_mask(hsv_search, self.aim_bounds, self.color_amp)
                            
                            if mask is not None and cv2.countNonZero(mask) > 0:
                                contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
                                best_dist = float('inf')
                                best_contour = None

                                for c in contours:
                                    if cv2.contourArea(c) < self.min_area: continue
                                    
                                    x, y, w_box, h_box = cv2.boundingRect(c)
                                    cx_box = x + w_box / 2 + offset_x 
                                    cy_box = y + h_box / 2 + offset_y
                                    dist = math.hypot(cx_box - center_x, cy_box - center_y)
                                    if dist < best_dist:
                                        best_dist = dist
                                        best_contour = c

                                if best_contour is not None:
                                    
                                    # VALORANT BONE TARGETING: HEAD (Макушка обводки) vs CENTER
                                    if self.target_bone_var.get() == "HEAD":
                                        # Математический поиск самого высокого пикселя в обводке
                                        topmost_point = tuple(best_contour[best_contour[:, :, 1].argmin()][0])
                                        target_x = topmost_point[0] + offset_x
                                        target_y = topmost_point[1] + offset_y + self.head_offset_y # Опускаем прицел в шею/лицо
                                    else:
                                        M = cv2.moments(best_contour)
                                        if M["m00"] != 0:
                                            target_x = (M["m10"] / M["m00"]) + offset_x
                                            target_y = (M["m01"] / M["m00"]) + offset_y
                                        else:
                                            x, y, w_box, h_box = cv2.boundingRect(best_contour)
                                            target_x = x + w_box / 2 + offset_x
                                            target_y = y + h_box / 2 + offset_y
                                            
                                    debug_target_pos = (target_x, target_y)
                                    
                                    with self.mouse_lock:
                                        self.has_target = True
                                        self.target_rel_x = target_x - center_x
                                        self.target_rel_y = target_y - center_y
                                    self.last_raw_target_x = target_x
                                    self.last_raw_target_y = target_y
                                    
                                    current_roi_size = max(60, int(max_fov * 0.4))
                                else:
                                    with self.mouse_lock: self.has_target = False
                                    current_roi_size = min(max_fov, current_roi_size + 30)
                            else:
                                with self.mouse_lock: self.has_target = False
                                current_roi_size = min(max_fov, current_roi_size + 30)
                        else:
                            with self.mouse_lock: self.has_target = False
                            current_roi_size = max_fov

                if self.show_preview_window.get():
                    m_rect = (offset_x, offset_y, search_bgr.shape[1], search_bgr.shape[0]) if micro_roi_active else None
                    self._prepare_preview(full_bgr, center_x, center_y, debug_target_pos, m_rect)

            elapsed = time.perf_counter() - loop_start
            sleep_time = self.scan_interval - elapsed
            if sleep_time > 0:
                time.sleep(sleep_time)

    def _prepare_preview(self, img_bgr, cx, cy, target_pos, micro_rect):
        debug_frame = img_bgr.copy()
        
        cv2.line(debug_frame, (cx, 0), (cx, debug_frame.shape[0]), (50, 50, 50), 1)
        cv2.line(debug_frame, (0, cy), (debug_frame.shape[1], cy), (50, 50, 50), 1)
        
        if micro_rect is not None:
            mx, my, mw, mh = micro_rect
            cv2.rectangle(debug_frame, (mx, my), (mx + mw, my + mh), (255, 100, 0), 1)
        
        if target_pos is not None:
            cv2.circle(debug_frame, (int(target_pos[0]), int(target_pos[1])), 4, (0, 255, 0), -1)
            cv2.line(debug_frame, (cx, cy), (int(target_pos[0]), int(target_pos[1])), (0, 255, 0), 1)
            
        scale = 3
        self._latest_preview_frame = cv2.resize(debug_frame, (debug_frame.shape[1]*scale, debug_frame.shape[0]*scale), interpolation=cv2.INTER_NEAREST)

    def run(self):
        self.root.mainloop()

if __name__ == "__main__":
    bot = StealthColorBot()
    bot.run()