Пример кликера для Lineage 2
Last updated
Last updated
Напишем простого бота-кликера для MMORPG Lineage 2, чтобы закрепить полученные знания о техниках внедрения данных на уровне ОС и перехвате устройств вывода.
Игровой процесс Lineage 2 типичен для жанра RPG. Сначала надо выбрать расу и класс для своего персонажа. Для получения новых умений и покупки предметов игрок должен выполнять задания (или квесты) и охотиться на монстров. Этот процесс получения ресурсов называется фарминг (farming). При этом у игроков всегда есть возможность общаться и взаимодействовать между собой, как и в любой MMORPG. Они могут помогать или мешать друг другу. Если несколько игроков хотят получить один и тот же ресурс, они должны сражаться за него. Этот элемент соперничества представляет наиболее привлекательную часть игрового процесса. Поэтому пользователи стремятся как можно быстрее и лучше развить своего персонажа, чтобы сражаться между собой.
Самый прямолинейный путь развития персонажа – это охота на монстров. После убийства каждого из них, игрок получает очки опыта для улучшения умений персонажа, а также золото для покупки новых предметов. Мы попытаемся автоматизировать именно этот процесс, поскольку он ведёт к разностороннему развитию героя. Однако, есть и другие пути получения игровых ресурсов: торговля, рыбалка, создание предметов и выполнение заданий.
На иллюстрации 2-12 приведён скриншот игры. Рассмотрим на нём элементы игрового интерфейса, помеченные номерам:
Окно состояния с параметрами персонажа игрока. К наиболее важным из них относятся очки здоровья (health points или HP) и мана (mana points или MP).
Окно цели с информацией о выделенном в данный момент монстре. В нём есть полоска с HP цели.
Панель горячих клавиш с иконками возможных действий и доступных умений.
Окно чата для ввода команд и отправки сообщений другим игрокам.
{caption: "Иллюстрация 2-12. Интерфейс Lineage 2"}
Тщательное изучение интерфейса поможет вам разработать наиболее простой и эффективный алгоритм взаимодействия бота с игрой. Более подробно интерфейс Lineage 2 описан на вики-странице.
В интернете есть множество серверов Lineage 2. Они отличаются версией игры, дополнительными возможностями и системами защиты, которые предотвращают использование ботов. Наиболее эффективная защита работает на официальных серверах, которые поддерживают разработчики игры. Кроме них есть так называемые пиратские сервера, которые поддерживаются энтузиастами. Как правило, их защита значительно слабее. В нашем примере мы будем подключаться к серверу РПГ-Клуб.
Чтобы лучше понять механику игры, попробуйте зарегистрироваться на сервере РПГ-Клуб, создать персонажа и убить нескольких монстров. Вы заметите, что почти всё время нажимаете одни и те же кнопки на панели горячих клавиш.
Теперь составим список действий, которые надо автоматизировать. Предлагаю следующий вариант:
Выбрать монстра для атаки. Это можно сделать двумя способами: левым щелчком мыши по нему или ввести в окно чата команду "/target". Например:
Полный список игровых команд приведён на официальном сайте. Их можно комбинировать в одно действие с помощью макросов.
Атаковать монстра. Для этого можно нажать кнопку "атака" на панели горячих клавиш или горячую клавишу F1.
Ожидать пока персонаж убьёт монстра.
Подобрать выпавшие из монстра предметы и золото. Опять же можно щёлкнуть мышью по действию на панели горячих клавиш или нажать F8.
Рассмотренные нами действия выглядят достаточно просто и прямолинейно. По сути у нас получился алгоритм работы бота. Напишем скрипт, который будет по нему работать.
Начнём с того, что будем строго следовать нашему алгоритму охоты на монстров. На каждом его шаге бот должен симулировать нажатие одной клавиши. Такой кликер можно считать слепым, поскольку он не получает никакой информации о состоянии игровых объектов.
Перед тем как начать писать код, рассмотрим конфигурацию панели горячих клавиш. Вам нужно настроить её так же как на иллюстрации 2-13.
Таблица 2-7 описывает конфигурацию панели.
{caption: "Таблица 2-7. Действия и соответствующие им горячие клавиши"}
Теперь стало очевидно, как надо связать горячие клавиши с шагами алгоритма бота. Скрипт BlindBot.au3
, приведённый в листинге 2-21, демонстрирует это.
В первой строчке скрипта стоит ключевое слово #RequireAdmin
. Благодаря ему при старте скрипт потребует предоставить ему права администратора. Получив эти права, он сможет взаимодействовать с другими приложениями независимо от того, какой пользователь их запустил. Некоторые клиенты Lineage 2 при старте также требуют прав администратора. Поэтому к ним не смогут получить доступ скрипты AutoIt, запущенные от имени пользователя с меньшими правами. Я рекомендую всегда использовать #RequireAdmin
в ваших кликерах.
Скрипт начинает своё выполнение с двухсекундной задержки. Она нужна для того, чтобы вы успели переключиться на окно Lineage 2. Текущая версия бота работает только с активным окном игры.
После вызова Sleep
идёт бесконечный цикл while
, в котором выполняются все действия бота:
Send("{F9}")
– выбрать монстра с помощью макроса, настроенного на клавишу F9.
Sleep(200)
– подождать 200 миллисекунд. Это время требуется клиенту Lineage 2, чтобы выделить монстра и отрисовать окно цели.
I> Помните, что все действия в окне игрового приложения происходят не мгновенно. Зачастую время на их выполнение намного меньше скорости реакции человека, поэтому вы его не замечаете. Но оно есть.
Send("{F1}")
– атаковать выбранного монстра.
Sleep(5000)
– ожидать пять секунд, пока персонаж не подбежит к монстру и не убьёт его.
Send("{F8}")
– подобрать один выпавший предмет.
Sleep(1000)
– ждать одну секунду, пока персонаж подбирает предмет.
В нашем примере последовательность действий бота строго определена. Поэтому каждое действие может завершиться успешно только в том случае, если предыдущее также было успешно. Это значит, что макрос выбора монстра должен отработать правильно. Если первый шаг не удался, все дальнейшие действия не имеют смысла. Затем персонаж должен успеть подбежать к монстру и убить его за пять секунд. Очевидно, это время может меняться в зависимости от расстояния до цели. Наконец, бот ожидает, что из монстра выпадет только один предмет. Наш скрипт отработает правильно только тогда, когда все перечисленные условия выполнятся, иначе неизбежны ошибки.
Попробуйте запустить скрипт и проверить его работу. Часто бот будет совершать не те действия, которые нужны в данный момент. Причина в том, что одно из условий его работы нарушено. С другой стороны, все его ошибки не критичны, поскольку он продолжает свою работу. Это возможно благодаря особенности команды /target
и механизму атаки цели. Если выполнить макрос /target
дважды, бот будет атаковать уже выбранного монстра. Таким образом он всегда будет добивать цель. Даже если монстр выжил после первой итерации цикла while
, атака на него продолжится в следующих итерациях. Кроме того, команда "поднять предмет" не прерывает атаку, если поблизости от персонажа нет предметов. Поэтому он будет продолжать бить цель и после пятисекундной задержки, отведённой на убийство монстра.
Единственная проблема, которую бот не сможет решить, заключается в подбирании выпадающих предметов. Число их случайно и зависит от вида монстра. Поэтому иногда они будут оставаться лежать на земле и персонаж недополучит свои ресурсы. В такой ситуации повторение действия "поднять" несколько раз будет лучшим, что можно придумать без дополнительных проверок. Даже если зачастую число нажатий будет больше необходимого, персонаж подберёт все выпавшие ресурсы.
Можно сделать скрипт более удобным для чтения и модификации, если вынести каждый шаг алгоритма в отдельную функцию с говорящим названием. Результат такого улучшения приведён в скрипте BlindBotFunc.au3
из листинга 2-22.
Теперь скрипт выглядит намного понятнее. Он начинает свою работу с вызова Sleep(2000)
. Выше этой строчки находятся только объявления пользовательских функций, которые определены разработчиком для своих целей. Их код будет выполнен только в местах вызова, то есть в цикле while
. Обратите внимание, что несмотря на изменившуюся структуру кода, алгоритмы скриптов BlindBotFunc.au3
и BlindBot.au3
остались идентичны.
Попробуем улучшить нашего бота и сделать его более эффективным. Он будет реже ошибаться, если сможет проверять результат каждого своего действия. Применим функцию анализа пикселей для чтения состояния окружающих его игровых объектов.
Перед тем как мы продолжим, было бы полезно добавить к текущей реализации бота механизм вывода диагностических сообщений. Техника вывода сообщений в местах принятия программой важных решений известна как [трассировка](https://ru.wikipedia.org/wiki/Трассировка_(программирование)) (tracing). С её помощью мы сможем отследить, какие решения принимает бот в ходе своей работы.
Реализация функции вывода сообщений в файл представлена в листинге 2-23.
После выполнения этого скрипта в одной папке с ним будет создан файл debug.log
, содержащий строку "Hello world!". Функция LogWrite
является обёрткой над AutoIt вызовом FileWrite
. Она будет удобна, если вам понадобиться отключить вывод в лог-файл. Для этого достаточно будет закомментировать в ней вызов FileWrite
. Вы можете изменить путь до лог-файла и его имя с помощью константы LogFile
.
I> Всегда предусматривайте способ отладки (обнаружения и устранения ошибок) вашего приложения. Самый простой подход заключается в печати на консоль или в файл наиболее важных решений, принятых его алгоритмом.
Первое условие, которое бот должен проверить, – это результат выбора цели. Попробуйте несколько раз выделить монстров с помощью мыши. Заметили ли вы элемент интерфейса, который отличается при наличии и отсутствии цели? Я имею в виду окно цели. Оно появляется каждый раз при выборе цели и пропадает при её убийстве или отмене по клавише Esc. Наш бот может найти это окно на экране с помощью функции FFBestSpot
библиотеки FastFind.
Чтобы отличить окно цели от остальных, нам нужно выбрать уникальный для него цвет. Другими словами, надо найти такой цвет, который встречается только в окне цели. Для этого подошёл бы красный цвет полосы HP монстра. Код из листинга 2-24 проверяет, есть ли окно цели на экране.
Рассмотрим функцию IsTargetExist
подробнее. Константы PosX и PosY – это примерные координаты полосы HP цели. Мы передаём их и красный цвет полосы (равный 871D18) в функцию FFBestSpot
в качестве входных параметров. Она ищет указанную область по всему экрану.
Внимательный читатель заметит, что вместо окна цели может быть найдено окно состояния персонажа. Ведь в нём тоже встречается красный цвет на полоске HP персонажа. В таком случае бот всегда будет делать вывод, что цель есть. Чтобы избежать этой ошибки, мы проверяем координаты области, найденной функцией FFBestSpot
. Сравниваем их (coords[0]
и coords[1]
) с максимальными (MaxX
и MaxY
) и минимальными (MinX
) допустимыми значениями. Эти значения задают область экрана, в которой ожидается появление окна цели. Они зависят от разрешения экрана и конфигурации интерфейса игры. Поэтому вам придётся подбирать их самостоятельно.
В каждой ветви операторов if
мы вызываем функцию LogWrite
, чтобы отследить принятые решения. Благодаря этому мы сможем обнаружить возможные ошибки, связанные с несоответствием входных и выходных данных функции IsTargetExist
.
IsTargetExist
позволяет нам решить сразу две задачи:
Проверка успешности выбора цели в функции SelectTarget
.
Проверка состояния атакуемого ботом монстра (жив или нет).
Скрипт AnalysisBot.au3
, представленный в листинге 2-25, использует функцию IsTargetExist
для проверки наличия цели.
Обратите внимание на новую реализацию функций SelectTarget
и Attack
. В SelectTarget
бот пытается выделить цель в цикле до тех пор, пока функция IsTargetExist
не вернёт значение True
. Только после этого он переходит в функцию Attack
. В ней бот продолжает атаковать монстра (выбирая действие "атака" по клавише F1) до тех пор, пока тот жив.
Мы печатаем в лог-файл названия функций SelectTarget
и Attack
, когда они получают управление. Этот вывод позволяет определить, которая из них вызывает IsTargetExist
.
Теперь наш кликер выбирает действие, согласно игровой ситуации. Тем не менее, по-прежнему возможны случаи, когда бот допустит критическую ошибку и умрёт.
Первая проблема заключается в агрессивных монстрах. Большинство из них неагрессивны. Они остаются в одной и той же области карты, не реагируя на приближение игрока. Но некоторые из них в такой ситуации атакуют и преследуют.
Наш бот выбирает цель для атаки и бежит к ней. При этом он игнорирует всех других существ, которые встретятся ему по пути. Можно сказать, что они невидимы для бота, поскольку его алгоритм их не учитывает. Таким образом, агрессивные монстры могут напасть на бота, бегущего к своей цели. Он будет отрабатывать алгоритм сражения с одним противником, но на самом деле их может оказаться два или больше. Вместе они легко убьют бота.
Чтобы решить эту проблему, воспользуемся командой "выбор ближайшей цели". На нашей панели горячих клавиш она доступна по нажатию F10. Ближайшая цель находится на минимальной (по сравнению с другими) дистанции от бота. Это значительно уменьшит время бота в пути, а значит и вероятность встречи с агрессивными монстрами.
Листинг 2-26 демонстрирует дополненную версию функции SelectTarget
.
Теперь бот в первую очередь пытается найти ближайшую цель по клавише F10. Только тогда, когда ему это не удалось, он использует команду /target
. Таким образом бот всегда стремится выбрать ближайшего к нему монстра. Если тот окажется агрессивным, то побежит навстречу и будет ближе всего.
Вторую серьёзную проблему для бота представляют собой преграды на карте. При движении к цели он может зацепиться за камень или дерево и застрять. Самое простое решение заключается в тайм-ауте на атаку. Если отведённое время на убийство монстра прошло, а цель осталась жива, можно предположить, что бот застрял. Тогда для обхода препятствия ему помогут случайные перемещения. Новые версии функций Move
и Attack
из листинга 2-27 демонстрируют это решение.
Мы добавили счётчик timeout
в функцию Attack
. На каждой итерации цикла while
он инкрементируется и сравнивается с пороговым значением константы TimeoutMax
. Когда счётчик достигает TimeoutMax
, бот делает вывод, что застрял. В этом случае вызывается функция Move
, которая симулирует щелчок левой кнопки мыши по точке со случайной координатой. Чтобы получить случайное число, используются функции AutoIt SRandom
и Random
. Первая из них инициализирует генератор псевдослучайных чисел. Вторая возвращает следующее число из очереди сгенерированных. В качестве параметров функция Random
принимает границы интервала для случайного числа.
Возможно, вы заметили дополнительное действие, появившееся в новой функции Attack
. Это симуляция нажатия клавиши F2. Мы можем назначить на неё любое атакующее умение персонажа, и бот будет применять его в сражении. Благодаря этому он сможет быстрее убивать монстров.
Теперь наш кликер способен самостоятельно работать достаточно долгое время. Он умеет обходить препятствия и первым атаковать агрессивных монстров. Но есть одно улучшение, способное значительно увеличить выживаемость бота. Речь идёт об использовании зелья восстановления здоровья, которое привязано к горячей клавише F5. Чтобы правильно его применять, необходимо анализировать полосу HP персонажа в окне состояния. Вы можете реализовать этот механизм самостоятельно в качестве упражнения. Алгоритм чтения уровня HP будет похож на функцию IsTargetExist
.
Мы реализовали кликера для игры Lineage 2. Он использует самые распространённые техники симуляции действий и анализа окна игрового приложения. Попробуем оценить их эффективность и обобщить результат на всех ботов этого типа.
Преимущества кликеров:
Простота разработки, отладки и расширения функциональности.
Просто адаптировать под любую версию игры, даже если её интерфейс поменялся.
Защититься от этого типа ботов достаточно сложно.
Недостатки кликеров:
Каждому пользователю приходится подгонять цвета и координаты искомых пикселей под своё разрешение экрана.
Бот может зависнуть в некоторых непредвиденных случаях (смерть персонажа, отключение от сервера и т.д.).
Тайм-ауты на симулируемые действия часто приводят к потере времени и низкой эффективности.
При анализе изображений на экране возможны ошибки. Поэтому в некоторых случаях бот будет выбирать неподходящие действия.
Кликеры хорошо подходят для автоматизации задач, состоящих из строгой последовательности шагов с минимальным количеством условий. Также обязательным требованием для их стабильной работы является относительно невысокая цена ошибки. То есть при выборе нескольких неверных действия, бот должен иметь возможность вернуться в известное ему состояние.
{caption: "Иллюстрация 2-13. Панель горячих клавиш"}
{caption: "Листинг 2-21. Скрипт BlindBot.au3
", format: AutoIt}
{caption: "Листинг 2-22. Скрипт BlindBotFunc.au3
", format: AutoIt}
{caption: "Листинг 2-23. Реализация функции LogWrite
", format: AutoIt}
{caption: "Листинг 2-24. Функция IsTargetExist
", format: AutoIt}
{caption: "Листинг 2-25. Скрипт AnalysisBot.au3
", format: AutoIt}
{caption: "Листинг 2-26. Функция SelectTarget
", format: AutoIt}
{caption: "Листинг 2-27. Функции Move
и Attack
", format: AutoIt}
Клавиша
Действие
F1
Атаковать выделенного в данный момент монстра.
F2
Использовать наступательное умение по текущей цели.
F5
Использовать зелье лечения для восстановления HP.
F8
Подобрать с земли предметы, лежащие около персонажа.
F9
Макрос с командой /target ИмяМонстра
для выбора цели.
F10
Выбор ближайшего монстра.