AutoKachalka/bot.py

143 lines
No EOL
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
import threading
import time
import keyboard
import config
import vision
from capture import ScreenCapture
from input_sender import press_action_key
if sys.platform == "win32":
import winsound
def _play_sound(frequency, duration_ms):
threading.Thread(target=winsound.Beep, args=(frequency, duration_ms), daemon=True).start()
else:
def _play_sound(frequency, duration_ms):
# На Linux/macOS нет аналога winsound.Beep без доп. зависимостей -
# используем системный звуковой сигнал терминала (BEL).
print("\a", end="", flush=True)
class Bot:
def __init__(self):
self.capture = ScreenCapture()
self.enabled = False
self.bar_bbox = None
self.was_in_zone = False
self.miss_count = 0
self.last_locate = 0.0
self.last_track_time = 0.0
self.prev_cursor_x = None
def toggle(self):
self.enabled = not self.enabled
print(f"[Bot] {'ВКЛЮЧЕН' if self.enabled else 'выключен'}")
if self.enabled:
_play_sound(config.SOUND_ON_FREQ, config.SOUND_DURATION_MS)
else:
_play_sound(config.SOUND_OFF_FREQ, config.SOUND_DURATION_MS)
self.bar_bbox = None
self.was_in_zone = False
self.prev_cursor_x = None
def run(self):
keyboard.add_hotkey(config.TOGGLE_HOTKEY, self.toggle)
print(f"Захват экрана: {self.capture.backend}")
print(f"{config.TOGGLE_HOTKEY.upper()} - включить/выключить бота. Ctrl+C - выход.")
frame_interval = 1.0 / config.TRACK_FPS
try:
while True:
t0 = time.time()
if not self.enabled:
time.sleep(0.05)
continue
if self.bar_bbox is None:
self._try_locate()
else:
self._track()
elapsed = time.time() - t0
if elapsed < frame_interval:
time.sleep(frame_interval - elapsed)
except KeyboardInterrupt:
pass
def _try_locate(self):
now = time.time()
if now - self.last_locate < config.LOCATE_RETRY_INTERVAL:
time.sleep(0.01)
return
self.last_locate = now
frame = self.capture.grab()
if frame is None:
return
bbox = vision.locate_bar(frame)
if bbox:
self.bar_bbox = bbox
self.miss_count = 0
print(f"[Bot] Шкала найдена: {bbox}")
def _track(self):
now = time.time()
loop_dt = now - self.last_track_time if self.last_track_time else 0.0
self.last_track_time = now
bx, by, bw, bh = self.bar_bbox
screen_w, screen_h = self.capture.screen_size()
margin_x = bw // 10
region_height = bh + int(bh * config.CURSOR_SEARCH_HEIGHT_RATIO)
region = (
max(0, bx - margin_x),
by,
min(screen_w, bx + bw + margin_x),
min(screen_h, by + region_height),
)
frame = self.capture.grab(region=region)
if frame is None:
return
local_bbox = (bx - region[0], 0, bw, bh)
zone = vision.find_target_zone(frame, local_bbox)
cursor_x = vision.locate_cursor(frame, local_bbox)
if zone is None or cursor_x is None:
self.miss_count += 1
if self.miss_count > config.MAX_TRACK_MISSES:
print("[Bot] Шкала пропала с экрана, ищу заново...")
self.bar_bbox = None
self.was_in_zone = False
self.prev_cursor_x = None
return
self.miss_count = 0
# Предсказываем, где курсор окажется через CURSOR_LEAD_TIME секунд,
# чтобы скомпенсировать задержку захвата/обработки/нажатия - иначе
# к моменту срабатывания клавиши курсор уже покидает узкую зону.
velocity = 0.0
if self.prev_cursor_x is not None and loop_dt > 0:
velocity = (cursor_x - self.prev_cursor_x) / loop_dt
self.prev_cursor_x = cursor_x
predicted_x = cursor_x + velocity * config.CURSOR_LEAD_TIME
x_min, x_max = zone
in_zone = x_min <= predicted_x <= x_max
if in_zone and not self.was_in_zone:
press_action_key()
print(
f"[Bot] E нажата | cursor_x={cursor_x:.1f} predicted_x={predicted_x:.1f} "
f"zone=({x_min},{x_max}) | задержка цикла={loop_dt * 1000:.1f} мс"
)
self.was_in_zone = in_zone
if __name__ == "__main__":
Bot().run()