Загрузка данных
import keyboard
import time
import threading
import pyautogui
from pynput.mouse import Controller
import json
import os
import traceback
import colorsys
try:
import win32api
import win32con
DIRECT_INPUT = True
except ImportError:
DIRECT_INPUT = False
class ParkourBot:
def __init__(self):
self.running = False
self.paused = False
self.total_look_down = 0
self.mouse = Controller()
self.timings = {
'start_delay': 5,
'head_raise_angle': 40,
'head_raise_steps': 5,
'scan_rotation_speed': 0.02,
'scan_angle_step': 10,
'scan_look_down_step': 2,
'scan_look_down_max': 60,
'scan_rotations': 2,
'camera_up_angle': 30,
'camera_up_delay': 0.05,
'first_w_time': 0.08,
'w_gap': 0.03,
'second_w_time': 0.05,
'jump_hold': 0.1,
'post_jump_forward': 0.15,
'shift_delay': 0.2,
'shift_hold': 0.3,
'center_look_down_angle': 80,
'center_move_speed': 0.08,
'center_move_repeat': 5,
'center_find_delay': 0.3,
'center_accuracy': 30, # Допуск центра (пиксели)
'center_move_step': 0.05, # Шаг движения при центрировании
'landing_delay': 0.3,
'stabilization_time': 0.1,
'cycle_delay': 0.3,
'color_tolerance': 15,
}
self.base_colors = [
(255, 23, 23),
(255, 29, 30),
(188, 17, 18),
(196, 17, 18),
]
self.generate_color_palette()
self.settings_file = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'parkour_settings.json'
)
self.load_settings()
def generate_color_palette(self):
self.block_colors = []
for base in self.base_colors:
self.block_colors.append(base)
r, g, b = base
h, s, v = colorsys.rgb_to_hsv(r/255, g/255, b/255)
for brightness in [0.8, 0.9, 1.1, 1.2]:
new_v = min(1.0, max(0.0, v * brightness))
new_r, new_g, new_b = colorsys.hsv_to_rgb(h, s, new_v)
color = (int(new_r * 255), int(new_g * 255), int(new_b * 255))
if color not in self.block_colors:
self.block_colors.append(color)
for saturation in [0.8, 0.9, 1.1, 1.2]:
new_s = min(1.0, max(0.0, s * saturation))
new_r, new_g, new_b = colorsys.hsv_to_rgb(h, new_s, v)
color = (int(new_r * 255), int(new_g * 255), int(new_b * 255))
if color not in self.block_colors:
self.block_colors.append(color)
print("Сгенерировано оттенков красного: " + str(len(self.block_colors)))
def load_settings(self):
try:
if os.path.exists(self.settings_file):
with open(self.settings_file, 'r', encoding='utf-8') as f:
saved = json.load(f)
for key in self.timings:
if key in saved:
self.timings[key] = saved[key]
if 'base_colors' in saved:
self.base_colors = [tuple(c) for c in saved['base_colors']]
self.generate_color_palette()
print("Настройки загружены")
except Exception as e:
print("Стандартные настройки: " + str(e))
def save_settings(self):
try:
save_data = {}
for key, value in self.timings.items():
save_data[key] = value
save_data['base_colors'] = self.base_colors
try:
with open(self.settings_file, 'w', encoding='utf-8') as f:
json.dump(save_data, f, indent=4, ensure_ascii=False)
print("Настройки сохранены")
except PermissionError:
home_dir = os.path.expanduser('~')
backup_file = os.path.join(home_dir, 'parkour_settings.json')
with open(backup_file, 'w', encoding='utf-8') as f:
json.dump(save_data, f, indent=4, ensure_ascii=False)
print("Настройки сохранены в " + backup_file)
except Exception as e:
print("Ошибка сохранения: " + str(e))
def release_keys(self):
try:
keyboard.release('w')
keyboard.release('a')
keyboard.release('s')
keyboard.release('d')
keyboard.release('space')
keyboard.release('shift')
except:
pass
def move_mouse(self, dx, dy):
try:
if DIRECT_INPUT:
win32api.mouse_event(win32con.MOUSEEVENTF_MOVE, int(dx), int(dy), 0, 0)
else:
pos = self.mouse.position
self.mouse.position = (pos[0] + dx, pos[1] + dy)
except:
pass
def color_match_percent(self, color1, color2):
try:
tolerance = self.timings['color_tolerance']
dr = abs(color1[0] - color2[0])
dg = abs(color1[1] - color2[1])
db = abs(color1[2] - color2[2])
max_diff = 255 * 3
actual_diff = dr + dg + db
similarity = 100 - (actual_diff / max_diff * 100)
return similarity >= (100 - tolerance)
except:
return False
def find_block_position(self):
"""
Ищет красную точку на экране и возвращает её координаты относительно центра.
Возвращает (dx, dy) - смещение точки от центра экрана.
Если точка не найдена, возвращает None.
"""
try:
screenshot = pyautogui.screenshot()
w, h = screenshot.size
cx, cy = w // 2, h // 2
# Проверяем центр
pixel = screenshot.getpixel((cx, cy))
for color in self.block_colors:
if self.color_match_percent(pixel[:3], color):
return (0, 0) # Точка уже в центре
# Ищем точку в области вокруг центра
search_radius = 150
for dy in range(-search_radius, search_radius + 1, 5):
for dx in range(-search_radius, search_radius + 1, 5):
px = cx + dx
py = cy + dy
if px < 0 or px >= w or py < 0 or py >= h:
continue
try:
pixel = screenshot.getpixel((px, py))
for color in self.block_colors:
if self.color_match_percent(pixel[:3], color):
return (dx, dy)
except:
continue
return None
except:
return None
def find_block(self):
"""Проверяет есть ли красная точка в центре"""
pos = self.find_block_position()
if pos is None:
return False
dx, dy = pos
accuracy = self.timings['center_accuracy']
return abs(dx) <= accuracy and abs(dy) <= accuracy
def raise_head(self):
print(" [ПОДЪЁМ ГОЛОВЫ]")
angle = self.timings['head_raise_angle']
steps = self.timings['head_raise_steps']
for _ in range(steps):
if not self.running or self.paused:
break
self.move_mouse(0, -angle)
time.sleep(0.02)
self.total_look_down = max(0, self.total_look_down - angle * steps)
time.sleep(0.1)
print(" Голова поднята")
def scan_for_block(self):
print(" [ПОИСК БЛОКА] Вращение + опускание головы...")
look_down_current = 0
look_down_max = self.timings['scan_look_down_max']
look_down_step = self.timings['scan_look_down_step']
scan_step = self.timings['scan_angle_step']
rotations = self.timings['scan_rotations']
total_steps = (360 // scan_step) * rotations
if look_down_step > 0 and look_down_max > 0:
look_down_every_n_steps = max(1, total_steps // (look_down_max // look_down_step))
else:
look_down_every_n_steps = total_steps + 1
step_count = 0
for rotation in range(rotations):
if not self.running or self.paused:
break
for _ in range(0, 360, scan_step):
if not self.running or self.paused:
break
self.move_mouse(scan_step, 0)
time.sleep(self.timings['scan_rotation_speed'])
step_count += 1
if step_count % look_down_every_n_steps == 0 and look_down_current < look_down_max:
self.move_mouse(0, look_down_step)
look_down_current += look_down_step
if self.find_block():
print(" БЛОК НАЙДЕН!")
self.total_look_down += look_down_current
return True
self.total_look_down += look_down_current
print(" Блок не найден")
return False
def do_jump(self):
print("=" * 40)
print("[ПРЫЖОК]")
print("=" * 40)
print(" Подъём камеры...")
angle = self.timings['camera_up_angle']
for _ in range(5):
self.move_mouse(0, -angle)
time.sleep(0.005)
time.sleep(self.timings['camera_up_delay'])
print(" Разгон W W...")
keyboard.press('w')
time.sleep(self.timings['first_w_time'])
keyboard.release('w')
time.sleep(self.timings['w_gap'])
keyboard.press('w')
time.sleep(self.timings['second_w_time'])
print(" ПРЫЖОК!")
keyboard.press('space')
time.sleep(self.timings['jump_hold'])
keyboard.release('space')
jump_time = time.time()
time.sleep(self.timings['post_jump_forward'])
keyboard.release('w')
print(" Приземление...")
time.sleep(self.timings['landing_delay'])
for _ in range(5):
self.move_mouse(0, angle)
time.sleep(0.005)
elapsed = time.time() - jump_time
shift_wait = max(0, self.timings['shift_delay'] - elapsed)
if shift_wait > 0:
time.sleep(shift_wait)
print(" Зажимаю шифт...")
keyboard.press('shift')
time.sleep(self.timings['stabilization_time'])
print("Прыжок завершён (шифт зажат)")
print("=" * 40)
def center_on_block(self):
"""
Центрирование на блоке НА ШИФТЕ.
Голова опущена вниз. Ищет красную точку и двигается к ней (WASD).
Не отпускает клавиши пока точка не окажется в центре.
"""
print("=" * 40)
print("[ЦЕНТРИРОВАНИЕ НА ШИФТЕ]")
print("=" * 40)
# Шифт уже должен быть зажат!
# Опускаем голову вниз
print(" Опускание головы...")
angle = self.timings['center_look_down_angle']
for _ in range(5):
self.move_mouse(0, angle)
time.sleep(0.01)
self.total_look_down += angle * 5
time.sleep(0.2)
accuracy = self.timings['center_accuracy']
max_attempts = 30 # Максимум попыток чтобы не зациклиться
for attempt in range(max_attempts):
if not self.running or self.paused:
break
# Ищем положение красной точки
pos = self.find_block_position()
if pos is None:
print(" Точка не видна, маленький шаг назад...")
keyboard.press('s')
time.sleep(self.timings['center_move_step'])
keyboard.release('s')
time.sleep(0.05)
continue
dx, dy = pos
# Проверяем, в центре ли точка
if abs(dx) <= accuracy and abs(dy) <= accuracy:
print(" ТОЧКА В ЦЕНТРЕ! (смещение: " + str(dx) + ", " + str(dy) + ")")
time.sleep(self.timings['center_find_delay'])
print("Центрирование завершено")
print("=" * 40)
return True
# Точка не в центре - двигаемся к ней
print(" Точка смещена: dx=" + str(dx) + " dy=" + str(dy))
# Определяем направление движения
move_w = False
move_a = False
move_s = False
move_d = False
# Горизонтальное смещение
if dx < -accuracy:
move_a = True # Точка слева - идём влево
elif dx > accuracy:
move_d = True # Точка справа - идём вправо
# Вертикальное смещение
if dy < -accuracy:
move_w = True # Точка сверху - идём вперёд
elif dy > accuracy:
move_s = True # Точка снизу - идём назад
# Нажимаем нужные клавиши
if move_w:
keyboard.press('w')
if move_a:
keyboard.press('a')
if move_s:
keyboard.press('s')
if move_d:
keyboard.press('d')
# Двигаемся короткими шагами
time.sleep(self.timings['center_move_step'])
# Отпускаем клавиши
if move_w:
keyboard.release('w')
if move_a:
keyboard.release('a')
if move_s:
keyboard.release('s')
if move_d:
keyboard.release('d')
time.sleep(0.02)
print(" Не удалось отцентрироваться за " + str(max_attempts) + " попыток")
print("=" * 40)
return False
def main_loop(self):
print("\n=== ЗАПУСК ЦИКЛА ===\n")
while self.running:
if self.paused:
time.sleep(0.1)
continue
try:
# Шаг 1: Подъём головы
self.raise_head()
# Шаг 2: Поиск блока (вращение + опускание)
found = self.scan_for_block()
if found:
# Шаг 3: Прыжок (шифт зажимается внутри)
self.do_jump()
# Шаг 4: Центрирование на шифте
self.center_on_block()
# Отпускаем шифт после центрирования
keyboard.release('shift')
time.sleep(0.1)
# Цикл повторяется
else:
print(" Пропуск цикла (блок не найден)")
keyboard.release('shift')
time.sleep(self.timings['cycle_delay'])
except Exception as e:
print("Ошибка: " + str(e))
traceback.print_exc()
self.release_keys()
time.sleep(0.5)
def setup_menu(self):
print("=" * 50)
print("НАСТРОЙКА БОТА")
print("=" * 50)
if os.path.exists(self.settings_file):
use = input("Загрузить сохранённые настройки? (Y/n): ").strip().lower()
if use == 'n':
print("Настройка заново...")
else:
print("Загружены сохранённые настройки")
self.show_settings()
return
print("Вводите значения или Enter (пропустить)")
print("Введите 0 для сброса на стандартное\n")
settings_groups = [
("ЗАДЕРЖКА СТАРТА", [
('start_delay', 'Задержка перед запуском', 1, 15, 'сек'),
]),
("ЦВЕТА", [
('color_tolerance', 'Допуск цвета (%)', 5, 50, '%'),
]),
("1. ПОДЪЁМ ГОЛОВЫ", [
('head_raise_angle', 'Угол подъёма', 10, 200, 'пикс'),
('head_raise_steps', 'Шагов подъёма', 2, 10, 'шагов'),
]),
("2. ПОИСК БЛОКА", [
('scan_rotation_speed', 'Скорость вращения', 0.01, 0.1, 'сек'),
('scan_angle_step', 'Шаг вращения', 5, 30, 'градусов'),
('scan_look_down_step', 'Шаг опускания', 1, 10, 'пикс'),
('scan_look_down_max', 'Макс. опускание', 10, 200, 'пикс'),
('scan_rotations', 'Оборотов', 1, 5, 'раз'),
]),
("3. ПРЫЖОК", [
('camera_up_angle', 'Подъём камеры', 10, 200, 'пикс'),
('first_w_time', 'Первое W', 0.03, 0.2, 'сек'),
('w_gap', 'Пауза между W', 0.01, 0.1, 'сек'),
('second_w_time', 'Второе W', 0.03, 0.2, 'сек'),
('jump_hold', 'Длительность прыжка', 0.05, 0.5, 'сек'),
('post_jump_forward', 'Полёт вперёд', 0.05, 0.5, 'сек'),
]),
("4. ШИФТ", [
('shift_delay', 'Задержка перед шифтом', 0.1, 1.0, 'сек'),
]),
("5. ЦЕНТРИРОВАНИЕ (на шифте, WASD к точке)", [
('center_look_down_angle', 'Опускание головы', 10, 200, 'пикс'),
('center_accuracy', 'Допуск центра (точность)', 10, 60, 'пикс'),
('center_move_step', 'Шаг движения', 0.02, 0.2, 'сек'),
('center_find_delay', 'Пауза после центрирования', 0.1, 0.5, 'сек'),
]),
("ПРИЗЕМЛЕНИЕ", [
('landing_delay', 'Ожидание приземления', 0.1, 1.0, 'сек'),
('stabilization_time', 'Стабилизация', 0.05, 0.3, 'сек'),
]),
]
defaults = self.timings.copy()
for group_name, params in settings_groups:
print("--- " + group_name + " ---")
for key, desc, min_v, max_v, unit in params:
cur = self.timings[key]
print(desc)
print("Текущее: " + str(cur) + " " + unit + " (от " + str(min_v) + " до " + str(max_v) + ")")
inp = input("> ").strip()
if inp == "":
print("Оставлено: " + str(cur) + " " + unit)
elif inp == "0":
self.timings[key] = defaults[key]
print("Сброшено: " + str(defaults[key]) + " " + unit)
else:
try:
val = float(inp)
if min_v <= val <= max_v:
self.timings[key] = val
print("Установлено: " + str(val) + " " + unit)
else:
print("Значение должно быть от " + str(min_v) + " до " + str(max_v))
except:
print("Введите число!")
self.save_settings()
print("Настройка завершена!\n")
def show_settings(self):
print("\nТекущие настройки:")
print(" Задержка старта: " + str(self.timings['start_delay']) + " сек")
print(" Подъём головы: " + str(self.timings['head_raise_angle']) + " пикс")
print(" Опускание при поиске: до " + str(self.timings['scan_look_down_max']) + " пикс")
print(" Камера при прыжке: " + str(self.timings['camera_up_angle']) + " пикс")
print("