Загрузка данных
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
# Попытка импорта официальной библиотеки Makcu
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")
# Попытка импорта pygrabber для получения РЕАЛЬНЫХ имен USB устройств
try:
from pygrabber.dshow_graph import FilterGraph
PYGRABBER_AVAILABLE = True
except ImportError:
PYGRABBER_AVAILABLE = False
print("Библиотека pygrabber не найдена. Установите: pip install pygrabber")
# Повышаем точность системного таймера до 1 мс
try:
ctypes.windll.winmm.timeBeginPeriod(1)
except:
pass
try:
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/Mouse Left", 0x02: "Makcu/Mouse Right", 0x04: "Makcu/Mouse Middle",
0x05: "Makcu/Mouse 4 (Back)", 0x06: "Makcu/Mouse 5 (Forward)", 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():
random_name = str(uuid.uuid4())[:8]
try:
ctypes.windll.kernel32.SetConsoleTitleW(random_name)
except:
pass
set_random_console_title()
class CaptureManager:
"""Управление картой видеозахвата (USB HDMI)."""
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 Exception as e:
print(f"[Capture] Ошибка pygrabber: {e}")
devices = []
index = 0
while index < 5:
cap = cv2.VideoCapture(index, cv2.CAP_DSHOW)
if cap.isOpened():
devices.append(f"Device {index} (Generic)")
cap.release()
index += 1
return devices
def start_capture(self, device_index=0):
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_FRAME_WIDTH, 1920)
self.capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
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):
while self.running and self.capture and self.capture.isOpened():
ret, frame = self.capture.read()
if ret:
with self.lock:
self.current_frame = frame
else:
time.sleep(0.001)
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:
"""Интеграция Makcu для сетапа 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 Exception as e:
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):
"""Проверка кнопок для 2PC (Сначала Makcu, потом клавиатура второго ПК)."""
makcu_btn = self.button_mapping.get(vk_code)
# 1. Запрос по COM-порту (Физическая мышь на PC1/Makcu)
if self.connected and self.controller and makcu_btn:
try:
if self.controller.is_pressed(makcu_btn):
return True
except:
pass
# 2. Локальный Fallback (Клавиатура на PC2)
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("420x780")
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 = 100
self.tolerance = 15
# Физика
self.gravity = 0.15
self.wind = 0.1
self.min_speed = 1.0
self.max_speed = 15.0
self.noise_state_x = 0.0
self.noise_state_y = 0.0
self.noise_velocity_x = 0.0
self.noise_velocity_y = 0.0
self.velocity_x = 0.0
self.velocity_y = 0.0
self.remainder_x = 0.0
self.remainder_y = 0.0
self.smooth_target_x = None
self.smooth_target_y = None
self.smoothing_factor = 0.6
self.last_target_center = None
self.last_raw_target_x = None
self.last_raw_target_y = None
# Триггер
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.05
self.min_hold = 0.03
self.max_hold = 0.07
self.show_fov_overlay = tk.BooleanVar(value=True)
self.show_preview_window = tk.BooleanVar(value=False)
self._preview_created = False
self.scan_interval = 0.005
self.mouse_send_rate = 125
self.mouse_send_interval = 1.0 / self.mouse_send_rate
self.mouse_jitter = 0.03
self.mouse_max_step = 20
self.mouse_skip_probability = 0.02
self.mouse_target_noise = 1.0
self.pending_move_x = 0.0
self.pending_move_y = 0.0
self.last_mouse_send_time = 0.0
self.last_aim_time = 0.0
self.last_trigger_time = 0.0
self.aim_interval = 0.008
self.trigger_interval = 0.01
# Метрики экрана для локального захвата
self.screen_width = win32api.GetSystemMetrics(0)
self.screen_height = win32api.GetSystemMetrics(1)
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.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()
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, self._update_makcu_status_connected)
break
else:
time.sleep(3)
attempts += 1
if attempts > 10:
break
except:
time.sleep(2)
def _update_makcu_status_connected(self):
self.makcu_status_label.config(text="Status: Connected (SDK)", fg="#00ff00")
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 - tol
h_high = 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):
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)
return final_mask
# ---------- UI ----------
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 - Advanced 2PC", 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="Tracking", 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="Action", 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_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 Target Tracking", 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", 10, 800, self.fov, "fov")
self.create_slider(self.aim_frame, "Smoothing Factor", 0.0, 0.95, self.smoothing_factor, "smoothing_factor", res=0.05)
tk.Label(self.aim_frame, text="- Kinematics Physics -", bg="#050505", fg="#5A5A5A", font=("Candara", 9)).pack(pady=(5, 0))
self.create_slider(self.aim_frame, "Gravity (Acceleration)", 0.05, 0.5, self.gravity, "gravity", res=0.01)
self.create_slider(self.aim_frame, "Wind (Sway Smoothness)", 0.0, 0.5, self.wind, "wind", res=0.01)
self.create_slider(self.aim_frame, "Max Speed", 1.0, 30.0, self.max_speed, "max_speed", res=0.1)
# === ВКЛАДКА 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)
# === ВКЛАДКА 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) - For 1PC", 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) - For 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)
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.makcu_note = tk.Label(self.makcu_frame, text="If Makcu not connected,\nmouse_event will be used (Local PC2)", bg="#050505", fg="#5A5A5A", font=("Candara", 8))
self.makcu_note.pack(pady=(10,0))
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):
delta_x = event.x - self.root._drag_data["x"]
delta_y = event.y - self.root._drag_data["y"]
x = self.root.winfo_x() + delta_x
y = self.root.winfo_y() + delta_y
self.root.geometry(f"+{x}+{y}")
def show_aim_tab(self):
self.trig_frame.pack_forget()
self.capture_frame.pack_forget()
self.makcu_frame.pack_forget()
self.aim_frame.pack(fill="both", expand=True)
self.btn_tab_aim.config(bg="#36005c")
self.btn_tab_trig.config(bg="#12001f")
self.btn_tab_capture.config(bg="#12001f")
self.btn_tab_makcu.config(bg="#12001f")
def show_trig_tab(self):
self.aim_frame.pack_forget()
self.capture_frame.pack_forget()
self.makcu_frame.pack_forget()
self.trig_frame.pack(fill="both", expand=True)
self.btn_tab_trig.config(bg="#36005c")
self.btn_tab_aim.config(bg="#12001f")
self.btn_tab_capture.config(bg="#12001f")
self.btn_tab_makcu.config(bg="#12001f")
def show_capture_tab(self):
self.aim_frame.pack_forget()
self.trig_frame.pack_forget()
self.makcu_frame.pack_forget()
self.capture_frame.pack(fill="both", expand=True)
self.btn_tab_capture.config(bg="#36005c")
self.btn_tab_aim.config(bg="#12001f")
self.btn_tab_trig.config(bg="#12001f")
self.btn_tab_makcu.config(bg="#12001f")
def show_makcu_tab(self):
self.aim_frame.pack_forget()
self.trig_frame.pack_forget()
self.capture_frame.pack_forget()
self.makcu_frame.pack(fill="both", expand=True)
self.btn_tab_makcu.config(bg="#36005c")
self.btn_tab_aim.config(bg="#12001f")
self.btn_tab_trig.config(bg="#12001f")
self.btn_tab_capture.config(bg="#12001f")
def refresh_devices(self):
def _refresh():
devices = self.capture_manager.list_capture_devices()
self.capture_device_list = devices
self.root.after(0, self._update_device_combo)
threading.Thread(target=_refresh, daemon=True).start()
def _update_device_combo(self):
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):
selected_str = self.capture_device_list[selection_idx]
self._extract_device_index(selected_str)
def _extract_device_index(self, selected_str):
# Если строка "1: USB Video Device", парсим 1
if ":" in selected_str:
try:
self.capture_device_index = int(selected_str.split(":")[0])
except:
pass
# Если строка "Device 1 (Generic)", парсим 1
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:
self.root.after(0, lambda: self.capture_status_text.set("Connecting to Capture Card..."))
try:
self.capture_manager.start_capture(self.capture_device_index)
time.sleep(1.5) # Ждем прогрузки буфера карты
frame = self.capture_manager.get_frame()
if frame is not None:
h, w = frame.shape[:2]
self.root.after(0, lambda: self.capture_status_text.set(f"Stream Active: {w}x{h}"))
else:
self.capture_manager.stop_capture()
self.root.after(0, lambda: self.capture_status_text.set("Stream Error (Black Screen)"))
self.root.after(0, lambda: messagebox.showerror("Capture", "Не удалось получить кадр. Возможно карта занята OBS или другим приложением."))
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, self._update_makcu_status_connected)
self.root.after(0, lambda: messagebox.showinfo("Makcu", "Успешное подключение. Аппаратный обход включен."))
else:
self.root.after(0, lambda: messagebox.showerror("Makcu", "Подключение не удалось. Проверьте средний провод и драйвер CH343."))
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():
r, g, b = r_var.get(), g_var.get(), b_var.get()
hex_color = '#%02x%02x%02x' % (r, g, b)
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('#'):
h = cb.lstrip('#')
r, g, b = tuple(int(h[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
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)
def show_menu(event):
c_menu.tk_popup(event.x_root, event.y_root)
preview_canvas.bind("<Button-3>", show_menu)
color_label.bind("<Button-3>", show_menu)
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 for input...")
threading.Thread(target=self._wait_for_key, args=(target,), daemon=True).start()
def _wait_for_key(self, target):
time.sleep(0.4) # Защита от случайной привязки клика по UI интерфейса
while self.running:
# 1. Поиск аппаратного клика с физической мыши через Makcu
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
# 2. Поиск нажатий на локальной клавиатуре ПК2. Цикл с 1, чтобы видеть ЛКМ.
for vk in range(1, 256):
if ctypes.windll.user32.GetAsyncKeyState(vk) & 0x8000:
if vk != 0x1B: # Игнорируем ESC
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")
# Overlay рисуется на локальном ПК (PC2)
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.local_center_x = self.screen_width // 2
self.local_center_y = self.screen_height // 2
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)
# ---------- Физика движения ----------
def _generate_noise(self):
self.noise_velocity_x += random.uniform(-0.1, 0.1)
self.noise_velocity_y += random.uniform(-0.1, 0.1)
self.noise_velocity_x *= 0.98
self.noise_velocity_y *= 0.98
self.noise_state_x += self.noise_velocity_x
self.noise_state_y += self.noise_velocity_y
self.noise_state_x = max(-2.0, min(2.0, self.noise_state_x))
self.noise_state_y = max(-2.0, min(2.0, self.noise_state_y))
return self.noise_state_x, self.noise_state_y
def calculate_smooth_step(self, target_dx, target_dy):
distance = math.hypot(target_dx, target_dy)
if distance < 1.0:
self.velocity_x, self.velocity_y = 0.0, 0.0
self.remainder_x, self.remainder_y = 0.0, 0.0
return 0.0, 0.0
noise_x, noise_y = self._generate_noise()
noise_x *= self.wind * (distance ** 0.3)
noise_y *= self.wind * (distance ** 0.3)
force_scale = self.gravity
if distance < 10:
force_scale *= 0.6
accel_x = (target_dx * force_scale) + noise_x
accel_y = (target_dy * force_scale) + noise_y
if self.velocity_x != 0 or self.velocity_y != 0:
dot_product = (self.velocity_x * accel_x + self.velocity_y * accel_y)
if dot_product < 0:
self.velocity_x *= 0.3
self.velocity_y *= 0.3
current_damping = 0.65 if distance < 15 else 0.85
self.velocity_x = (self.velocity_x + accel_x) * current_damping
self.velocity_y = (self.velocity_y + accel_y) * current_damping
speed = math.hypot(self.velocity_x, self.velocity_y)
if speed > self.max_speed:
scale = self.max_speed / speed
self.velocity_x *= scale
self.velocity_y *= scale
return self.velocity_x, self.velocity_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()
jitter_factor = 1.0 + random.uniform(-self.mouse_jitter, self.mouse_jitter)
dynamic_interval = self.mouse_send_interval * jitter_factor
if random.random() < self.mouse_skip_probability:
time.sleep(random.uniform(0.005, 0.015))
continue
if current_time - self.last_mouse_send_time >= 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:
noise_x = random.uniform(-self.mouse_target_noise, self.mouse_target_noise)
noise_y = random.uniform(-self.mouse_target_noise, self.mouse_target_noise)
vx, vy = self.calculate_smooth_step(rel_x + noise_x, rel_y + noise_y)
self.pending_move_x += vx
self.pending_move_y += vy
else:
self.velocity_x *= 0.7
self.velocity_y *= 0.7
self.pending_move_x += self.velocity_x
self.pending_move_y += self.velocity_y
if abs(self.velocity_x) < 0.1: self.velocity_x = 0.0
if abs(self.velocity_y) < 0.1: self.velocity_y = 0.0
current_speed = math.hypot(self.pending_move_x, self.pending_move_y)
if current_speed > self.mouse_max_step:
scale_factor = self.mouse_max_step / current_speed
self.pending_move_x *= scale_factor
self.pending_move_y *= scale_factor
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:
sleep_time = max(0, dynamic_interval - (current_time - self.last_mouse_send_time))
if sleep_time > 0.002:
time.sleep(sleep_time * 0.5)
else:
time.sleep(0.0001)
# ---------- Триггер ----------
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
if random.random() < 0.03:
return
self.is_clicking = True
self.trigger_queue.put(True)
# ---------- Основной цикл распознавания (Мощная оптимизация FOV) ----------
def logic_loop(self):
sct = mss.mss()
while self.running:
# Менеджер превью окна OpenCV
if self.show_preview_window.get():
self._preview_created = True
else:
if getattr(self, '_preview_created', False):
cv2.destroyWindow("Live Preview")
self._preview_created = False
if not self.active:
if self.show_preview_window.get():
cv2.waitKey(50) # Поддерживаем окно живым в паузе
else:
time.sleep(0.05)
continue
loop_start = time.perf_counter()
current_fov = int(self.fov)
# --- 1. Получение и обрезка кадра ДО конвертации (Снижает нагрузку на CPU) ---
if self.capture_source_var.get() == "screen":
# Режим: Захват локального экрана (1PC)
left = max(0, self.local_center_x - current_fov // 2)
top = max(0, self.local_center_y - current_fov // 2)
width = min(current_fov, self.screen_width - left)
height = min(current_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))
img_bgr = img_bgra[:, :, :3]
except:
continue
if width < current_fov or height < current_fov:
pad_x = (current_fov - width) // 2
pad_y = (current_fov - height) // 2
img_bgr = cv2.copyMakeBorder(img_bgr, pad_y, current_fov - height - pad_y,
pad_x, current_fov - width - pad_x,
cv2.BORDER_CONSTANT, value=(0,0,0))
else:
# Режим: Карта видеозахвата (2PC)
if not self.capture_manager.is_active():
if self.show_preview_window.get(): cv2.waitKey(50)
else: time.sleep(0.05)
continue
frame = self.capture_manager.get_frame()
if frame is None:
if self.show_preview_window.get(): cv2.waitKey(1)
continue
h, w = frame.shape[:2]
# Находим реальный центр кадра с карты захвата
cx, cy = w // 2, h // 2
half_fov = current_fov // 2
# Идеальная вырезка зоны FOV напрямую из сырого кадра
x1 = max(0, cx - half_fov)
y1 = max(0, cy - half_fov)
x2 = min(w, cx + half_fov)
y2 = min(h, cy + half_fov)
img_bgr = frame[y1:y2, x1:x2]
if img_bgr.shape[0] != current_fov or img_bgr.shape[1] != current_fov:
img_bgr = cv2.copyMakeBorder(img_bgr, 0, current_fov - img_bgr.shape[0],
0, current_fov - img_bgr.shape[1],
cv2.BORDER_CONSTANT, value=(0,0,0))
if 'img_bgr' in locals() and img_bgr.size > 0:
# Центр локальной матрицы (FOV) всегда равен половине FOV
act_h, act_w = img_bgr.shape[:2]
center_x, center_y = act_w // 2, act_h // 2
# Конвертируем только микро-зону (быстро)
hsv_fov = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)
debug_target_pos = None
trigger_zone_size = 4
y1_t = max(0, center_y - trigger_zone_size)
y2_t = min(act_h, center_y + trigger_zone_size)
x1_t = max(0, center_x - trigger_zone_size)
x2_t = min(act_w, center_x + trigger_zone_size)
# --- 2. Триггер ---
if self.trigger_enabled.get():
now = time.perf_counter()
if now - self.last_trigger_time >= self.trigger_interval:
self.last_trigger_time = now
trigger_active = self.makcu.is_button_pressed(self.trigger_key)
if trigger_active:
hsv_area = hsv_fov[y1_t:y2_t, x1_t:x2_t]
trig_mask = self.apply_mask(hsv_area, self.trig_bounds)
if trig_mask is not None and cv2.countNonZero(trig_mask) > 4:
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
aim_active = self.makcu.is_button_pressed(self.tracking_key)
if aim_active:
mask = self.apply_mask(hsv_fov, self.aim_bounds)
if mask is not None and cv2.countNonZero(mask) > 0:
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
best_score = -float('inf')
best_center = None
if self.last_target_center is not None:
last_cx, last_cy = self.last_target_center
for contour in contours:
area = cv2.contourArea(contour)
if area < 2: continue
M = cv2.moments(contour)
if M["m00"] == 0: continue
cx_exact = M["m10"] / M["m00"]
cy_exact = M["m01"] / M["m00"]
dist_to_center = math.hypot(cx_exact - center_x, cy_exact - center_y)
score = -dist_to_center + 10.0 * math.log(area + 1)
if self.last_target_center is not None:
dist_to_last = math.hypot(cx_exact - last_cx, cy_exact - last_cy)
if dist_to_last < 15: score += 30.0
else: score -= min(20.0, dist_to_last * 0.5)
if score > best_score:
best_score = score
best_center = (cx_exact, cy_exact)
if best_center is not None:
target_x, target_y = best_center
debug_target_pos = best_center
self.last_raw_target_x = target_x
self.last_raw_target_y = target_y
self.last_target_center = (target_x, target_y)
else:
self.last_target_center = None
target_x, target_y = center_x, center_y
else:
self.last_target_center = None
self.last_raw_target_x = None
self.last_raw_target_y = None
with self.mouse_lock:
self.has_target = False
# Окно отладки должно обновляться даже если таргет утерян
if self.show_preview_window.get():
self._render_preview(img_bgr, center_x, center_y, x1_t, y1_t, x2_t, y2_t, None)
continue
if self.smooth_target_x is None:
self.smooth_target_x = target_x
self.smooth_target_y = target_y
else:
if self.last_raw_target_x is not None:
jump_dist = math.hypot(target_x - self.last_raw_target_x, target_y - self.last_raw_target_y)
else:
jump_dist = 0
jump_threshold = max(15.0, current_fov * 0.15)
if jump_dist > jump_threshold:
self.smooth_target_x = target_x
self.smooth_target_y = target_y
self.velocity_x = 0.0
self.velocity_y = 0.0
with self.mouse_lock:
self.pending_move_x = 0.0
self.pending_move_y = 0.0
self.remainder_x = 0.0
self.remainder_y = 0.0
else:
alpha = self.smoothing_factor
self.smooth_target_x = alpha * self.smooth_target_x + (1 - alpha) * target_x
self.smooth_target_y = alpha * self.smooth_target_y + (1 - alpha) * target_y
with self.mouse_lock:
self.has_target = True
self.target_rel_x = self.smooth_target_x - center_x
self.target_rel_y = self.smooth_target_y - center_y
else:
with self.mouse_lock:
self.has_target = False
self.target_rel_x = 0.0
self.target_rel_y = 0.0
self.smooth_target_x = None
self.smooth_target_y = None
self.velocity_x = 0.0
self.velocity_y = 0.0
self.last_target_center = None
self.last_raw_target_x = None
self.last_raw_target_y = None
# Рендер превью с маркерами (только если включено)
if self.show_preview_window.get():
self._render_preview(img_bgr, center_x, center_y, x1_t, y1_t, x2_t, y2_t, debug_target_pos)
elapsed = time.perf_counter() - loop_start
sleep_time = self.scan_interval - elapsed
if sleep_time > 0:
time.sleep(sleep_time)
def _render_preview(self, img_bgr, cx, cy, tx1, ty1, tx2, ty2, target_pos):
"""Отрисовывает интерфейс отладки в реальном времени."""
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)
# Зона триггера (Желтая)
cv2.rectangle(debug_frame, (tx1, ty1), (tx2, ty2), (0, 255, 255), 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)
# Увеличиваем масштаб для удобства просмотра (т.к. FOV обычно очень маленький 100х100)
scale = 3
resized_debug = cv2.resize(debug_frame, (debug_frame.shape[1]*scale, debug_frame.shape[0]*scale), interpolation=cv2.INTER_NEAREST)
cv2.imshow("Live Preview", resized_debug)
cv2.waitKey(1)
def run(self):
self.root.mainloop()
if __name__ == "__main__":
bot = StealthColorBot()
bot.run()