Игровые боты. Начало

в 16:53, , рубрики: c++, qt, Программирование, метки: ,

Что может быть интереснее процесса игры в игры? Правильно! Процесс наблюдения за тем, как играет в игры написанный тобой бот.

Некоторое время, я размышлял, о чём бы написать свою первую статейку. Хотел написать о программировании микроконтроллеров, но оказалось трудно отделить части рабочих проектов от тех, что можно опубликовать без оглядки на коллег :-) Остановился на идее о ботах.

Введение

Боты для онлайн игр я бы грубо разделил на 3 разновидности по способам реализации:
1. Боты не использующие приложение игры. Имитирующие протокол обмена с сервером.
2. Боты работающие с процессом приложения игры. В случае с Web, работающие с окном браузера.
3. Боты работающие со скриншотом и имитирующие устройства ввода мышь и клавиатуру.

Первая разновидность скорее гипотетическая, т.к. протоколы, как правило, закрыты и не тривиальны.

Вторая разновидность более реальна и может быть реализована. Бот второго вида получает полезную информацию из памяти процесса игры. Недостаток — версии клиентов могут регулярно обновляться и тогда может потребоваться заново искать интересующие адреса памяти.

Мы рассмотрим третюю разновидность ботов, т.к. ИМХО они более привлекательны, хоть и не лишены недостатков.

В этой статье я рассмотрю набор инструментов для самого простого бота для Windows.

Для самого простого бота достаточно эмитировать события мыши и клавиатуры. В большинстве случаев этого оказывается достаточно для решения (не самого эффективного, но не требующего участия человека) рутинных дел в различных играх. Для более эффективной работы бота требуется обратная связь с игрой, т.е. получение и обработка скриншотов игры.

Код

Для разработки приложений я буду использовать Qt Creator + Qt 5 либы (так мне привычнее) и раз бот для Виндовс то + windows.h (WinAPI).

Инклуды:

#include <windows.h>   // WinAPI
#include <iostream>    // std::cout
#include <unistd.h>    // sleep(), usleep()
#include <math.h>

Хидер бота:

// Индексы точек
enum {
    menu=0,
    elm_1,
    points_cnt
};

class MyBot
{
public:
    MyBot();
    void run();
    void move_to(int inx);
    void lclick_to(int inx);
    void rclick_to(int inx);
    void drag(int from_inx,int to_inx);
    POINT point[points_cnt];
};

Конструктор:

MyBot::MyBot() :
    // Массив координат органов управления (по которым мы будем кликать мышкой)
    point({
      {100,100}, // 0 - menu
      {130,130}, // 1 - elm_1
    })
{
}

Регистрация горячих кнопок для управления ботом:

    RegisterHotKey((HWND)Widget::winId(), 101, MOD_ALT, VK_F1);    // Запуск бота
    RegisterHotKey((HWND)Widget::winId(), 102, MOD_ALT, VK_F2);    // inx++
    RegisterHotKey((HWND)Widget::winId(), 103, MOD_ALT, VK_F3);    // Проверить точку inx
    RegisterHotKey((HWND)Widget::winId(), 104, MOD_ALT, VK_F4);    // Запомнить точку inx
    RegisterHotKey((HWND)Widget::winId(), 105, MOD_ALT, VK_F5);    // Вывести в консоль массив координат

Обработка событий нажатия кнопок управления ботом:

int inx=0;
MyBot bot;

bool Widget::nativeEvent(const QByteArray & eventType, void * message, long * result){
    Q_UNUSED(result);
    Q_UNUSED(eventType);
    MSG* msg = reinterpret_cast<MSG*>(message);
    if(msg->message!=WM_HOTKEY)return false;

    switch(msg->wParam){
    case 101: // Alt-F1 - запуск бота
        bot.run();
        return true;
    case 102: // Alt-F2 - inx++
        if(inx<points_cnt-1)inx++;
        return true;
    case 103: // Alt-F3 - Проверить точку inx
        bot.move_to(inx);
        return true;
    case 104: // Alt-F4 - Запомнить точку inx
        GetCursorPos(&point[inx]);
        return true;
    case 105: // Alt-F5 - Вывести в консоль массив координат
        for(i=0;i<points_cnt;i++){
            std::cout << "{" << bot.point[i].x << "," << bot.point[i].y << "}, //" << i << std::endl;
        }
        return true;
    }
    return false;
}

Перемещение указателя мыши к нужной точке:
(выполнено не очень аккуратно, обещаю исправиться :-))

#define width 1920
#define height 1080

void MyBot::move_to(int inx){
    int x=point[id].x;
    int y=point[id].y;
    POINT pt;
    GetCursorPos(&pt);

    int from_x=pt.x;
    int from_y=pt.y;

    int to_x=x;
    int to_y=y;

    int dx=to_x-from_x;
    int dy=to_y-from_y;

    float fdx;
    float fdy;
    int loop_cnt;

    if(abs(dx)>abs(dy) && dx!=0){
        fdx=dx<0? -1.0 :1.0;
        fdy=(float)dy/abs(dx);
        loop_cnt=abs(dx);
    }
    else if(dy!=0){
        fdy=dy<0? -1.0 :1.0;
        fdx=(float)dx/abs(dy);
        loop_cnt=abs(dy);
    }
    else return;

    // двинуть за 1 секунду
    int time=1000000/loop_cnt;
    float fx=from_x;
    float fy=from_y;
    for(int i=0;i<loop_cnt;i++){
        fy+=fdy;
        fx+=fdx;
        int nx=(fx)*(65536 / width);
        int ny=(fy)*(65536 / height);
        mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,nx,ny,0,0);
        usleep(time);
    }
    usleep(50000);
}

Клики:

void MyBot::lclick_to(int inx){
    move_to(inx);
    mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
    usleep(50000);
    mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);
    usleep(100000);
}

void MyBot::rclick_to(int inx){
    move_to(inx);
    mouse_event(MOUSEEVENTF_RIGHTDOWN,0,0,0,0);
    usleep(50000);
    mouse_event(MOUSEEVENTF_RIGHTUP,0,0,0,0);
    usleep(100000);
}

Перетаскивание:

void MyBot::drag(int from_inx, to_inx){
    move_to(from_inx);
    usleep(50000);
    mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
    usleep(70000);
    move_to(to_inx);
    mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);
    usleep(30000);
}

Работа бота:

void MyBot::run(){
    rclick_to(menu);  // кликнем правой кнопкой для вызова контекстного меню
    lclick_to(elm_1); // кликнем левой кнопкой по строке меню
}
Мануал юзера

Перед запуском бота горячей кнопкой Alt-F1, бот следует сначала настроить, определив верные координаты органов управления по которым бот будет кликать.
Для запоминания координат точки наводим указатель месту и жмём Alt-F4.
Для проверки корректности точки отводим указатель в сторону и жмём Alt-F3.
Для настройки следующей точки жмём Alt-F2.
Для сохранения верных координат жмём Alt-F5.

Подводные камни

Опыт показывает, что не стоит торопиться кликать по органам управления и двигать указатель. Часто игры притормаживают, при наведении мыши на кнопку срабатывает отрисовка подсветки и прочие неведанные процессы и торопливость приводит к несрабатыванию клика или захвата при перетягивании и т.п. и как следствие неработоспособность бота. При ручном управлении, люди обычно таких досадных мелочей даже не замечают, так как работает обратная связь через органы зрения. Решение — делать паузы после всех элементарных действий.

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

Qt Creator: qt-project.org/downloads
Исходный код проекта на гитхабе: github.com/rumaster/my_bot_v1

P.S. Не подумайте что я ярый противник онлайн игр, раз публикую исходники ботов. Я противник дискриминации ИИ (ботов) и за развитие онного. А ещё, игры — двигатель прогресса.

P.P.S. Говоря ИИ, я подразумеваю программу способную получать и обрабатывать (анализировать) информацию, планировать и выполнять действия в соответствии с целями и результатами анализа ситуации.

Автор: ru_master

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js