Пишу Бота-Кликера На Python Для Lineage 2.



Пишу бота-кликера на Python для Lineage 2.



Предисловие

Как можно развлечься в новогодние праздники? Играть в компьютерные игры? Нет! Лучше написать бота, который будет делать это за вас, а сами пойти лепить снеговика и пить глинтвейн.

Когда-то в школьные годы меня увлекла одна из популярных ММОРПГ - Lineage 2. В игре можно вступать в кланы, группы, дружить и сражаться с соперниками, но в целом игра наполнена однообразными действиями: прохождением квесты и фарм (сбор ресурсов, получение опыта).

В итоге я решил, что бот должен решать одну задачу: фармить.

Для управления будут использоваться эмулированные щелчки мыши и нажатия клавиш клавиатуры, а для пространственной ориентации — компьютерное зрение с использованием языка программирования Python. В общем, создание бота для Л2 — дело не новое и готовых существует довольно много.

Они разделены на 2 основные группы: те, которые внедряются в работу клиента и кликеры.

Первые — жесткий чит; в плане игры использовать их слишком неспортивно.

Второй вариант более интересен, учитывая, что его можно применить к любой другой игре с некоторыми доработками, и реализация будет интереснее.

Те кликеры, которые я нашел, по разным причинам не работали, либо работали нестабильно.

Внимание: вся информация здесь представлена исключительно в образовательных целях.

Специально для разработчиков игр, чтобы помочь им лучше бороться с ботами.

Итак, приступим к делу.



Работа с окном

Здесь все просто.

Мы будем работать со скриншотами из окна игры.

Для этого определяем координаты окна.

Работаем с окном с помощью модуля win32gui. Нужное окно мы определим по названию – «Lineage 2».

Код методов получения положения окна

  
  
  
  
  
  
  
  
  
  
   

def get_window_info(): # set window info window_info = {} win32gui.EnumWindows(set_window_coordinates, window_info) return window_info # EnumWindows handler # sets L2 window coordinates def set_window_coordinates(hwnd, window_info): if win32gui.IsWindowVisible(hwnd): if WINDOW_SUBSTRING in win32gui.GetWindowText(hwnd): rect = win32gui.GetWindowRect(hwnd) x = rect[0] y = rect[1] w = rect[2] - x h = rect[3] - y window_info['x'] = x window_info['y'] = y window_info['width'] = w window_info['height'] = h window_info['name'] = win32gui.GetWindowText(hwnd) win32gui.SetForegroundWindow(hwnd)

Получаем изображение нужного окна с помощью ImageGrab:

def get_screen(x1, y1, x2, y2): box = (x1 + 8, y1 + 30, x2 - 8, y2) screen = ImageGrab.grab(box) img = array(screen.getdata(), dtype=uint8).

reshape((screen.size[1], screen.size[0], 3)) return img

Теперь поработаем с контентом.



Искать монстра

Самое интересное.

Те реализации, которые я нашел, меня не устроили.

Например, в одной из популярных и даже платных это сделано через игровой макрос.

И «игрок» должен написать макрос типа «/target Monster Name Bla Bla» для каждого типа монстров.

В нашем случае мы будем следовать такой логике: в первую очередь мы найдем на экране весь белый текст. Белый текст может быть не только именем монстра, но и именем самого персонажа, именем NPC или других игроков.

Поэтому нам необходимо навести курсор на объект и если появится подсветка с нужным нам рисунком, то мы можем атаковать цель.

Вот исходное изображение, с которым мы будем работать:

Пишу бота-кликера на Python для Lineage 2.

Закрасим наше имя черным цветом, чтобы оно не мешало, и превратим картинку в черно-белую.

Исходное изображение имеет формат RGB — каждый пиксель представляет собой массив из трех значений от 0 до 255, когда ч/б — это одно значение.

Таким образом мы существенно сократим объем данных:

img[210:230, 350:440] = (0, 0, 0) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)



Пишу бота-кликера на Python для Lineage 2.

Найдите все белые объекты (это белый текст с именами монстров)

ret, threshold1 = cv2.threshold(gray, 252, 255, cv2.THRESH_BINARY)



Пишу бота-кликера на Python для Lineage 2.

Морфологические преобразования:

  1. Мы будем фильтровать по прямоугольнику 50х5. Этот прямоугольник работал лучше всего.

  2. Удаление шума внутри прямоугольников с текстом (по сути, закрашивание всего, что между буквами, в белый цвет)
  3. Еще раз убираем шум путем размытия и растягивания с помощью фильтра.



kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50, 5)) closed = cv2.morphologyEx(threshold1, cv2.MORPH_CLOSE, kernel) closed = cv2.erode(closed, kernel, iterations=1) closed = cv2.dilate(closed, kernel, iterations=1)



Пишу бота-кликера на Python для Lineage 2.

Нахождение середины получившихся пятен

(_, centers, hierarchy) = cv2.findContours(closed, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

Работает, но можно сделать круче (например, для монстров, имена которых не видны, потому что они далеко) — используя TensorFlow Object Detection, например здесь , но когда-нибудь в следующей жизни.

Теперь наводим курсор на найденного монстра и смотрим, появилось ли выделение с помощью метода cv2.matchTemplate. Остается только нажать ЛКМ и кнопку атаки.



Плакать

С поиском монстра мы разобрались, бот уже может находить цели на экране и наводить на них мышку.

Чтобы атаковать цель, нужно щелкнуть левой кнопкой мыши и нажать «атака» (можно привязать атаку к кнопке «1»).

Щелчок правой кнопкой мыши необходим для поворота камеры.

На сервере, где я тестировал бота, я вызывал клик через AutoIt, но по каким-то причинам это не сработало.

Как оказалось, игры защищены от автокликеров разными способами:

  • поиск процессов, эмулирующих клики
  • запись кликов и определение цвета объекта, на который нажимает бот
  • определение шаблонов кликов
  • идентификация бота по частоте кликов
А некоторые приложения, например клиент этого сервера, могут определять источник клика на уровне ОС.

(Было бы здорово, если бы кто-нибудь мог сказать мне, как именно).

Мы попробовали некоторые фреймворки, которые умеют кликать (в том числе pyautogui, robot framework и что-то еще), но ни один из вариантов не сработал.

Проскочила идея построить устройство, которое будет нажимать кнопку (кто-то даже это делал).

Похоже, вам нужно нажать как можно сильнее.

В результате я начал подумывать о написании собственного драйвера.

В Интернете был найден способ решения проблемы: USB-устройство, которое можно запрограммировать на отправку нужного сигнала — Digispark.

Пишу бота-кликера на Python для Lineage 2.

Ждать несколько недель с Алиэкспресс не хочется, поэтому поиск продолжился.

В конце концов было найдено замечательная библиотека C Нашел для нее и Python-обертка Моя библиотека не запустилась на Python 3.6 — у меня возникла ошибка нарушения прав доступа или что-то в этом роде.

Поэтому мне пришлось перейти на Python 2.7, там все работало как часы.



Движение курсора

Библиотека может отправлять любые команды, в том числе куда двигать мышкой.

Но это похоже на телепортацию курсора.

Нам нужно сделать движение курсора плавным, чтобы нас не забанили.

По сути задача сводится к перемещению курсора из точки А в точку Б с помощью обертки AutoHotPy. Вам действительно нужно помнить математику? После некоторых раздумий я наконец решил загуглить.

Оказалось, что ничего изобретать не нужно — задачу решает алгоритм Брезенхема, один из старейших алгоритмов компьютерной графики:

Пишу бота-кликера на Python для Lineage 2.

Вы можете взять это прямо из Википедии.

выполнение

Логика работы

Все инструменты есть, осталось самое простое — написать скрипт.
  1. Если монстр жив, продолжаем атаковать
  2. Если цели нет, найдите цель и начните атаковать.

  3. Если мы не смогли найти цель, немного развернёмся
  4. Если 5 раз не удалось никого найти, отойдите в сторону и начните заново.

С более-менее интересного момента опишу, как я получил состояние здоровья пострадавшего.

В общих чертах: с помощью паттерна OpenCV находим элемент управления, показывающий состояние здоровья цели, берём полоску высотой в один пиксель и считаем в процентах, сколько закрашено красным.

Код метода для получения уровня здоровья жертвы

def get_targeted_hp(self): """ return victim's hp or -1 if there is no target """ hp_color = [214, 24, 65] target_widget_coordinates = {} filled_red_pixels = 1 img = get_screen( self.window_info["x"], self.window_info["y"], self.window_info["x"] + self.window_info["width"], self.window_info["y"] + self.window_info["height"] - 190 ) img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) template = cv2.imread('img/target_bar.png', 0) # w, h = template.shape[::-1] res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED) threshold = 0.8 loc = np.where(res >= threshold) if count_nonzero(loc) == 2: for pt in zip(*loc[::-1]): target_widget_coordinates = {"x": pt[0], "y": pt[1]} # cv2.rectangle(img, pt, (pt[0] + w, pt[1] + h), (255, 255, 255), 2) if not target_widget_coordinates: return -1 pil_image_hp = get_screen( self.window_info["x"] + target_widget_coordinates['x'] + 15, self.window_info["y"] + target_widget_coordinates['y'] + 31, self.window_info["x"] + target_widget_coordinates['x'] + 164, self.window_info["y"] + target_widget_coordinates['y'] + 62 ) pixels = pil_image_hp[0].

tolist() for pixel in pixels: if pixel == hp_color: filled_red_pixels += 1 percent = 100 * filled_red_pixels / 150 return percent

Теперь бот понимает, сколько ХП у жертвы и жива ли она еще.

Базовая логика готова, вот как она выглядит сейчас в действии: Для тех, кто занят, я ускорил процесс.

1.30

Прекратить работу

Вся работа с курсором и клавиатурой осуществляется через объект autohotpy, работу которого можно остановить в любой момент, нажав кнопку ESC. Проблема в том, что все время бот занят выполнением цикла, отвечающего за логику действий персонажа, а обработчики событий объекта и autohotpy не начинают слушать события, пока цикл не завершится.

Программу невозможно остановить даже с помощью мыши, потому что ею управляет бот и перемещает курсор туда, куда ему нужно.

Нас это не устраивает, поэтому пришлось разделить бота на 2 потока: прослушивание событий и выполнение логики действий персонажа.

Давайте создадим 2 темы

# init bot stop event self.bot_thread_stop_event = threading.Event() # init threads self.auto_py_thread = threading.Thread(target=self.start_auto_py, args=(auto_py,)) self.bot_thread = threading.Thread(target=self.start_bot, args=(auto_py, self.bot_thread_stop_event, character_class)) # start threads self.auto_py_thread.start() self.bot_thread.start()

и теперь присоединяем обработчик к ESC:

auto_py.registerExit(auto_py.ESC, self.stop_bot_event_handler)

при нажатии ESC устанавливаем событие

self.bot_thread_stop_event.set()

и в цикле символьной логики проверяем, установлено ли событие:

while not stop_event.is_set():

Теперь спокойно остановите бота кнопкой ESC.

Заключение

Казалось бы, зачем тратить время на продукт, который не приносит никакой практической пользы? По сути, с точки зрения компьютерного зрения компьютерная игра — это почти то же самое, что и реальность, заснятая на камеру, и возможности для применения в ней огромны.

Отличный пример описан в статье о подводных роботах, которые они стреляют в лосося лазером .

Статья также может помочь разработчикам игр в борьбе с ботами.

Что ж, я познакомился с Python, прикоснулся к компьютерному зрению, написал свой первый слабоумный искусственный интеллект и получил массу удовольствия.

Надеюсь, вам это тоже было интересно.

P.S. Ссылка к репозиторий Теги: #python #бот #AI #Lineage 2 #компьютерное зрение #python #программирование #Разработка игр

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.