127 lines
No EOL
4.2 KiB
Python
127 lines
No EOL
4.2 KiB
Python
import time
|
||
|
||
import keyboard
|
||
|
||
import config
|
||
import vision
|
||
from capture import ScreenCapture
|
||
from input_sender import press_action_key
|
||
|
||
|
||
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 not self.enabled:
|
||
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() |