Как написать аим самп

Привет, сегодня напишем простой аимбот на SF.​

Алгоритм:
— найти id игрока
— получить координаты игрока
— рассчитать угол поворота
— повернуть камеру на этот угол

Получаем id и координаты игрока по зеленому треугольнику:

Cvector enpos;
int enid = SF->getSAMP()->getPlayers()->pLocalPlayer->sAimingAtPid;
if (enid != 65535)
{
enpos.fX = SF->getSAMP()->getPlayers()->pRemotePlayer[enid]->pPlayerData->onFootData.fPosition[0];
enpos.fY = SF->getSAMP()->getPlayers()->pRemotePlayer[enid]->pPlayerData->onFootData.fPosition[1];
enpos.fZ = SF->getSAMP()->getPlayers()->pRemotePlayer[enid]->pPlayerData->onFootData.fPosition[2];
}

второй вариант получения id игрока с помощью этой функции — https://blast.hk/threads/10970/#post-124093

int enid = GetPlayerTarget(50);
if (enid != -1)
{
enpos.fX = SF->getSAMP()->getPlayers()->pRemotePlayer[enid]->pPlayerData->onFootData.fPosition[0];
enpos.fY = SF->getSAMP()->getPlayers()->pRemotePlayer[enid]->pPlayerData->onFootData.fPosition[1];
enpos.fZ = SF->getSAMP()->getPlayers()->pRemotePlayer[enid]->pPlayerData->onFootData.fPosition[2];
}

Получаем свои координаты и рассчитываем угол:

CCamera *pCamera = GAME->GetCamera();
mypos = *pCamera->GetCam(pCamera->GetActiveCam())->GetSource();
vector = mypos - enpos;
float AngleX = atan2f(vector.fY, -vector.fX) - M_PI / 2;

Поворачиваем камеру:

*(float*)0xB6F258 = -(AngleX - M_PI / 2);

Поворот с учетом смещения прицела(взято из собейта — https://github.com/BlastHackNet/mod_s0beit_sa/blob/master/src/cheat_actor.cpp#L173):

float    *crosshairOffset = (float *)0xB6EC10;
 float mult = tan(pCamera->GetCam(pCamera->GetActiveCam())->GetFOV() * 0.5f * 0.017453292f);
 float fx = M_PI - atan2(1.0f, mult * (crosshairOffset[1] - 0.5f + crosshairOffset[1] - 0.5f));

Поворачиваем камеру:

*(float*)0xB6F258 = -(AngleX - fx );

полный листинг:

void Aimbot()
{
    CVector mypos;
    CVector enpos;
    CVector vector;
    int enid = SF->getSAMP()->getPlayers()->pLocalPlayer->sAimingAtPid;
    if (enid != 65535)
    {
        enpos.fX = SF->getSAMP()->getPlayers()->pRemotePlayer[enid]->pPlayerData->onFootData.fPosition[0];
        enpos.fY = SF->getSAMP()->getPlayers()->pRemotePlayer[enid]->pPlayerData->onFootData.fPosition[1];
        enpos.fZ = SF->getSAMP()->getPlayers()->pRemotePlayer[enid]->pPlayerData->onFootData.fPosition[2];
        CCamera *pCamera = GAME->GetCamera();
        mypos = *pCamera->GetCam(pCamera->GetActiveCam())->GetSource();
        vector = mypos - enpos;
       
        float    *crosshairOffset = (float *)0xB6EC10;
        float mult = tan(pCamera->GetCam(pCamera->GetActiveCam())->GetFOV() * 0.5f * 0.017453292f);
        float fx = M_PI - atan2(1.0f, mult * (crosshairOffset[1] - 0.5f + crosshairOffset[1] - 0.5f));
       
        float AngleX = atan2f(vector.fY, -vector.fX) - M_PI / 2;

        *(float*)0xB6F258 = -(AngleX - fx);
    }
}

0 / 0 / 0

Регистрация: 25.07.2013

Сообщений: 14

1

29.07.2013, 23:32. Показов 9622. Ответов 1


Подскажите пожалуйста. Есть игра Gta SA, хотелось бы написать Aim для этой игры, подскажите план создания.

__________________
Помощь в написании контрольных, курсовых и дипломных работ, диссертаций здесь



0



5 / 1 / 0

Регистрация: 28.03.2013

Сообщений: 13

30.07.2013, 00:52

2

Цитата
Сообщение от Sеrgo
Посмотреть сообщение

Подскажите пожалуйста. Есть игра Gta SA, хотелось бы написать Aim для этой игры, подскажите план создания.

Можешь почитать здесь приблизительно как все это реализовывается на примере игры CS:Sourse. Но если задаешься таким вопросом, то реализация тебе явно будет не под силу.



0



IT_Exp

Эксперт

87844 / 49110 / 22898

Регистрация: 17.06.2006

Сообщений: 92,604

30.07.2013, 00:52

2

Время на прочтение
11 мин

Количество просмотров 13K

В этом посте мы расскажем о процессе создания aimbot — программы, автоматически прицеливающейся во врагов в игре жанра «шутер от первого лица» (FPS). Создавать будем aimbot для игры Half-Life 2, работающей на движке Source. Aimbot будет работать внутри процесса игры и использовать для своей работы внутренние функции игры, подвергнутые реверс-инжинирингу (в отличие от других систем, работающих снаружи игры и сканирующих экран).

Для начала изучим Source SDK и используем его как руководство для реверс-инжиниринга исполняемого файла Half-Life 2. Затем мы применим полученные данные, для создания бота. К концу статьи у нас будет написан aimbot, привязывающий прицел игрока к ближайшему врагу.

▍ Реверс-инжиниринг и Source SDK

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

  • Позицию глаз игрока
  • Позицию глаз ближайшего врага
  • Вектор от глаза игрока к глазу врага (получаемый по двум предыдущим пунктам)
  • Способ изменения положения камеры игрока так, чтобы глаз игрока смотрел по вектору к глазу врага

Для всего, кроме третьего пункта, требуется получать информацию от состояния запущенной игры. Эту информацию получают реверс-инжинирингом игры для поиска классов, содержащих соответствующие поля. Обычно это оказывается чрезвычайно трудоёмкой задачей со множеством проб и ошибок; однако в данном случае у нас есть доступ к Source SDK, который мы используем в качестве руководства.

Начнём поиск с нахождения ссылок на позицию глаз в репозитории. Изучив несколько страниц с результатами поиска, мы выйдем на огромный класс CBaseEntity. Внутри этого класса есть две функции:

virtual Vector EyePosition(void);
virtual const QAngle &EyeAngles(void);

Так как CBaseEntity является базовым классом, от которого происходят все сущности (entity) игры, и он содержит члены для позиции глаза и углов камеры, то похоже, именно с ним нам и нужно работать. Дальше нам нужно посмотреть, откуда ссылаются на эти функции. Снова немного поискав на GitHub Source SDK, мы находим интерфейс IServerTools, у которого есть несколько весьма многообещающих функций:

virtual IServerEntity *GetIServerEntity(IClientEntity *pClientEntity) = 0;
virtual bool SnapPlayerToPosition(const Vector &org, const QAngle &ang, IClientEntity *pClientPlayer = NULL) = 0;
virtual bool GetPlayerPosition(Vector &org, QAngle &ang, IClientEntity  *pClientPlayer = NULL) = 0;

// ...

virtual CBaseEntity *FirstEntity(void) = 0;
virtual CBaseEntity *NextEntity(CBaseEntity *pEntity) = 0;

В этом интерфейсе очень удобно то, что он предоставляет доступ к позиции локального игрока, что позволяет привязать игрока к другой позиции и углу обзора, а также обеспечивает возможность итеративно обходить сущности. К тому же его экземпляр создаётся глобально и привязан к жёстко прописанной в коде строке.

#define VSERVERTOOLS_INTERFACE_VERSION_1	"VSERVERTOOLS001"
#define VSERVERTOOLS_INTERFACE_VERSION_2	"VSERVERTOOLS002"
#define VSERVERTOOLS_INTERFACE_VERSION		"VSERVERTOOLS003"
#define VSERVERTOOLS_INTERFACE_VERSION_INT	3

// ...

EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerTools, IServerTools001, VSERVERTOOLS_INTERFACE_VERSION_1, g_ServerTools);
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CServerTools, IServerTools, VSERVERTOOLS_INTERFACE_VERSION, g_ServerTools);

Можем начать разработку aimbot с поиска этого класса в памяти. Запустив Half-Life 2 и подключив к нему отладчик, поищем строковые ссылки на VSERVERTOOLS.

Мы видим, откуда на них ссылаются:

7BCAB090 | 68 88FA1F7C              | push server.7C1FFA88                    | 7C1FFA88:"VSERVERTOOLS001"
7BCAB095 | 68 00C4087C              | push server.7C08C400                    |
7BCAB09A | B9 B02A337C              | mov ecx,server.7C332AB0                 |
7BCAB09F | E8 8CCA3F00              | call server.7C0A7B30                    |
7BCAB0A4 | C3                       | ret                                     |
7BCAB0A5 | CC                       | int3                                    |
7BCAB0A6 | CC                       | int3                                    |
7BCAB0A7 | CC                       | int3                                    |
7BCAB0A8 | CC                       | int3                                    |
7BCAB0A9 | CC                       | int3                                    |
7BCAB0AA | CC                       | int3                                    |
7BCAB0AB | CC                       | int3                                    |
7BCAB0AC | CC                       | int3                                    |
7BCAB0AD | CC                       | int3                                    |
7BCAB0AE | CC                       | int3                                    |
7BCAB0AF | CC                       | int3                                    |
7BCAB0B0 | 68 98FA1F7C              | push server.7C1FFA98                    | 7C1FFA98:"VSERVERTOOLS002"
7BCAB0B5 | 68 00C4087C              | push server.7C08C400                    |
7BCAB0BA | B9 BC2A337C              | mov ecx,server.7C332ABC                 |
7BCAB0BF | E8 6CCA3F00              | call server.7C0A7B30                    |
7BCAB0C4 | C3                       | ret                                     |

В ассемблерном листинге видно, что функция-член server.7C0A7B30 вызывается в server.7C332AB0 и server.7C332ABC. Эта функция получает два аргумента, один из которых — это имя-строка интерфейса. После изучения отладчика становится понятно, что второй параметр — это статический экземпляр чего-то.

Посмотрев на то, что делает в коде макрос EXPOSE_SINGLE_INTERFACE_GLOBALVAR, становится понятнее, что это синглтон CServerTools, предоставленный как глобальный интерфейс. Зная это, мы легко сможем получить указатель на этот синглтон в среде исполнения: мы просто берём адрес этой псевдофункции, перемещающей указатель в EAX, и вызываем её напрямую. Чтобы сделать это, можно написать следующий дженерик-код, который мы продолжим применять для нового использования других функций:

template <typename T>
T GetFunctionPointer(const std::string moduleName, const DWORD_PTR offset) {

    auto moduleBaseAddress{ GetModuleHandleA(moduleName.c_str()) };
    if (moduleBaseAddress == nullptr) {
        std::cerr << "Could not get base address of " << moduleName
            << std::endl;
        std::abort();
    }
    return reinterpret_cast<T>(
        reinterpret_cast<DWORD_PTR>(moduleBaseAddress) + offset);
}

IServerTools* GetServerTools() {

    constexpr auto globalServerToolsOffset{ 0x3FC400 };
    static GetServerToolsFnc getServerToolsFnc{ GetFunctionPointer<GetServerToolsFnc>(
        "server.dll", globalServerToolsOffset) };

    return getServerToolsFnc();
}

Здесь мы берём базовый адрес, в который загружена server.dll, добавляем смещение, чтобы попасть туда, откуда можно получить доступ к синглтону CServerTools, и возвращаем его как указатель вызывающей функции. Благодаря этому мы сможем вызывать нужные нам функции в интерфейсе и игра будет реагировать соответствующим образом. Нас интересуют две функции: GetPlayerPosition и SnapPlayerToPosition.

Внутри GetPlayerPosition при помощи вызова UTIL_GetLocalPlayer получается класс локального игрока, а также вызываются EyePosition и EyeAngles; внутри SnapPlayerToPosition при помощи SnapEyeAngles корректируются углы обзора игрока. Всё вместе это даёт нам то, что необходимо для получения позиций и углов обзора сущностей, благодаря чему можно выполнить соответствующие вычисления нового вектора и угла обзора, привязывающихся к глазам врагов.

Давайте разбираться по порядку, начнём с GetPlayerPosition. Так как мы можем получить указатель на IServerTools и имеем определение интерфейса, можно выполнить явный вызов GetPlayerPosition и пошагово пройтись по вызову при помощи отладчика. При этом мы попадём сюда:

7C08BEF0 | 55                       | push ebp                                |
7C08BEF1 | 8BEC                     | mov ebp,esp                             |
7C08BEF3 | 8B01                     | mov eax,dword ptr ds:[ecx]              |
7C08BEF5 | 83EC 0C                  | sub esp,C                               |
7C08BEF8 | 56                       | push esi                                |
7C08BEF9 | FF75 10                  | push dword ptr ss:[ebp+10]              |
7C08BEFC | FF50 04                  | call dword ptr ds:[eax+4]               |
7C08BEFF | 8BF0                     | mov esi,eax                             |
7C08BF01 | 85F6                     | test esi,esi                            |
7C08BF03 | 75 14                    | jne server.7C08BF19                     |
7C08BF05 | E8 E616E7FF              | call server.7BEFD5F0                    |
7C08BF0A | 8BF0                     | mov esi,eax                             |
7C08BF0C | 85F6                     | test esi,esi                            |
7C08BF0E | 75 09                    | jne server.7C08BF19                     |
7C08BF10 | 32C0                     | xor al,al                               |
7C08BF12 | 5E                       | pop esi                                 |
7C08BF13 | 8BE5                     | mov esp,ebp                             |
7C08BF15 | 5D                       | pop ebp                                 |
7C08BF16 | C2 0C00                  | ret C                                   |
7C08BF19 | 8B06                     | mov eax,dword ptr ds:[esi]              |
7C08BF1B | 8D4D F4                  | lea ecx,dword ptr ss:[ebp-C]            |
7C08BF1E | 51                       | push ecx                                |
7C08BF1F | 8BCE                     | mov ecx,esi                             |
7C08BF21 | FF90 08020000            | call dword ptr ds:[eax+208]             |
7C08BF27 | 8B4D 08                  | mov ecx,dword ptr ss:[ebp+8]            |
7C08BF2A | D900                     | fld st(0),dword ptr ds:[eax]            |
7C08BF2C | D919                     | fstp dword ptr ds:[ecx],st(0)           |
7C08BF2E | D940 04                  | fld st(0),dword ptr ds:[eax+4]          |
7C08BF31 | D959 04                  | fstp dword ptr ds:[ecx+4],st(0)         |
7C08BF34 | D940 08                  | fld st(0),dword ptr ds:[eax+8]          |
7C08BF37 | 8B06                     | mov eax,dword ptr ds:[esi]              |
7C08BF39 | D959 08                  | fstp dword ptr ds:[ecx+8],st(0)         |
7C08BF3C | 8BCE                     | mov ecx,esi                             |
7C08BF3E | FF90 0C020000            | call dword ptr ds:[eax+20C]             |
7C08BF44 | 8B4D 0C                  | mov ecx,dword ptr ss:[ebp+C]            |
7C08BF47 | 5E                       | pop esi                                 |
7C08BF48 | D900                     | fld st(0),dword ptr ds:[eax]            |
7C08BF4A | D919                     | fstp dword ptr ds:[ecx],st(0)           |
7C08BF4C | D940 04                  | fld st(0),dword ptr ds:[eax+4]          |
7C08BF4F | D959 04                  | fstp dword ptr ds:[ecx+4],st(0)         |
7C08BF52 | D940 08                  | fld st(0),dword ptr ds:[eax+8]          |
7C08BF55 | B0 01                    | mov al,1                                |
7C08BF57 | D959 08                  | fstp dword ptr ds:[ecx+8],st(0)         |
7C08BF5A | 8BE5                     | mov esp,ebp                             |
7C08BF5C | 5D                       | pop ebp                                 |
7C08BF5D | C2 0C00                  | ret C                                   |

Разбираться в этом довольно долго, однако в виде графа потока управления всё выглядит довольно просто:

Если построчно сопоставить дизассемблированную программу с кодом, то мы достаточно быстро найдём необходимое. Код вызывает функцию UTIL_GetLocalPlayer только тогда, когда переданный параметр pClientEntity равен null. Эта логика проверяется в блоке первой функции графа. Если существует действительная клиентская сущность, код переходит к получению позиции и углов глаза для неё, а в противном случае получает сущность локального игрока. Этот вызов происходит при исполнении команды call server.7BEFD5F0 по адресу server.7C08BF05. Как и ранее, мы можем создать указатель функции на UTIL_GetLocalPlayer и вызывать её напрямую.

CBasePlayer* GetLocalPlayer() {

    constexpr auto globalGetLocalPlayerOffset{ 0x26D5F0 };
    static GetLocalPlayerFnc getLocalPlayerFnc{ GetFunctionPointer<GetLocalPlayerFnc>(
        "server.dll", globalGetLocalPlayerOffset) };

    return getLocalPlayerFnc();
}

Следующими в дизассемблированном коде идут вызовы функций EyePosition и EyeAngles. Нас интересует только получение позиций глаза, поэтому важен только первый вызов. Для получения адреса функции мы можем пошагово пройти по вызову, пока не вызовем адрес, находящийся в [EAX+0x208]. После исполнения этой команды мы перейдём на server.dll+0x119D00, таким образом узнав, где находится функция.

Vector GetEyePosition(CBaseEntity* entity) {
    
    constexpr auto globalGetEyePositionOffset{ 0x119D00 };
    static GetEyePositionFnc getEyePositionFnc{ GetFunctionPointer<GetEyePositionFnc>(
        "server.dll", globalGetEyePositionOffset) };

    return getEyePositionFnc(entity);
}

И это всё, что нам нужно от GetPlayerPosition; теперь у нас есть возможность получения указателя локальной сущности игрока и получения позиции глаза сущности. Последнее, что нам потребуется — возможность задания угла обзора игрока. Как говорилось выше, это можно сделать, вызвав функцию SnapPlayerToPosition и посмотрев, где находится функция SnapEyeAngles. Дизассемблированный код SnapEyeAngles выглядит следующим образом:

7C08C360 | 55                       | push ebp                                |
7C08C361 | 8BEC                     | mov ebp,esp                             |
7C08C363 | 8B01                     | mov eax,dword ptr ds:[ecx]              |
7C08C365 | 83EC 0C                  | sub esp,C                               |
7C08C368 | 56                       | push esi                                |
7C08C369 | FF75 10                  | push dword ptr ss:[ebp+10]              |
7C08C36C | FF50 04                  | call dword ptr ds:[eax+4]               |
7C08C36F | 8BF0                     | mov esi,eax                             |
7C08C371 | 85F6                     | test esi,esi                            |
7C08C373 | 75 14                    | jne server.7C08C389                     |
7C08C375 | E8 7612E7FF              | call server.7BEFD5F0                    |
7C08C37A | 8BF0                     | mov esi,eax                             |
7C08C37C | 85F6                     | test esi,esi                            |
7C08C37E | 75 09                    | jne server.7C08C389                     |
7C08C380 | 32C0                     | xor al,al                               |
7C08C382 | 5E                       | pop esi                                 |
7C08C383 | 8BE5                     | mov esp,ebp                             |
7C08C385 | 5D                       | pop ebp                                 |
7C08C386 | C2 0C00                  | ret C                                   |
7C08C389 | 8B06                     | mov eax,dword ptr ds:[esi]              |
7C08C38B | 8BCE                     | mov ecx,esi                             |
7C08C38D | FF90 24020000            | call dword ptr ds:[eax+224]             |
7C08C393 | 8B4D 08                  | mov ecx,dword ptr ss:[ebp+8]            |
7C08C396 | F3:0F1001                | movss xmm0,dword ptr ds:[ecx]           |
7C08C39A | F3:0F5C00                | subss xmm0,dword ptr ds:[eax]           |
7C08C39E | F3:0F1145 F4             | movss dword ptr ss:[ebp-C],xmm0         |
7C08C3A3 | F3:0F1041 04             | movss xmm0,dword ptr ds:[ecx+4]         |
7C08C3A8 | F3:0F5C40 04             | subss xmm0,dword ptr ds:[eax+4]         |
7C08C3AD | F3:0F1145 F8             | movss dword ptr ss:[ebp-8],xmm0         |
7C08C3B2 | F3:0F1041 08             | movss xmm0,dword ptr ds:[ecx+8]         |
7C08C3B7 | 8BCE                     | mov ecx,esi                             |
7C08C3B9 | F3:0F5C40 08             | subss xmm0,dword ptr ds:[eax+8]         |
7C08C3BE | 8D45 F4                  | lea eax,dword ptr ss:[ebp-C]            |
7C08C3C1 | 50                       | push eax                                |
7C08C3C2 | F3:0F1145 FC             | movss dword ptr ss:[ebp-4],xmm0         |
7C08C3C7 | E8 14CFD0FF              | call server.7BD992E0                    |
7C08C3CC | FF75 0C                  | push dword ptr ss:[ebp+C]               |
7C08C3CF | 8BCE                     | mov ecx,esi                             |
7C08C3D1 | E8 4A0FE0FF              | call server.7BE8D320                    |
7C08C3D6 | 8B06                     | mov eax,dword ptr ds:[esi]              |
7C08C3D8 | 8BCE                     | mov ecx,esi                             |
7C08C3DA | 6A FF                    | push FFFFFFFF                           |
7C08C3DC | 6A 00                    | push 0                                  |
7C08C3DE | FF90 88000000            | call dword ptr ds:[eax+88]              |
7C08C3E4 | B0 01                    | mov al,1                                |
7C08C3E6 | 5E                       | pop esi                                 |
7C08C3E7 | 8BE5                     | mov esp,ebp                             |
7C08C3E9 | 5D                       | pop ebp                                 |
7C08C3EA | C2 0C00                  | ret C                                   |

Повторив уже описанный выше процесс, мы выясним, что команда call server.7BE8D320 является вызовом SnapEyeAngles. Мы можем определить следующую функцию:

void SnapEyeAngles(CBasePlayer* player, const QAngle& angles)
{
    constexpr auto globalSnapEyeAnglesOffset{ 0x1FD320 };
    static SnapEyeAnglesFnc snapEyeAnglesFnc{ GetFunctionPointer<SnapEyeAnglesFnc>(
        "server.dll", globalSnapEyeAnglesOffset) };

    return snapEyeAnglesFnc(player, angles);
}

Итак, теперь у нас есть всё необходимое.

▍ Создание aimbot

Для создания aimbot нам нужно следующее:

  • Итеративно обойти сущности
    • Если сущность является врагом, то найти расстояние между сущностью и игроком
    • Отслеживать ближайшую сущность
  • Вычислить вектор «глаз-глаз» между игроком и ближайшим врагом
  • Корректировать углы глаза игрока так, чтобы он следовал за этим вектором

Ранее мы узнали, что для получения позиции игрока можно вызвать GetPlayerPosition. Для циклического обхода списка сущностей можно вызвать FirstEntity и NextEntity, которые возвращают указатель на экземпляр CBaseEntity. Чтобы понять, является ли сущность врагом, можно сравнить имя сущности со множеством имён враждебных NPC-сущностей. Если мы получили сущность врага, то вычисляем расстояние между игроком и сущностью и сохраняем позицию сущности, если она ближайшая из всех, пока найденных нами.

После итеративного обхода всего списка сущностей мы получаем ближайшего врага, вычисляем вектор «глаз-глаз» и корректируем углы глаза игрока при помощи функции VectorAngles.

В виде кода получим следующее:

auto* serverEntity{ reinterpret_cast<IServerEntity*>(
    GetServerTools()->FirstEntity()) };

if (serverEntity != nullptr) {
    do {
        if (serverEntity == GetServerTools()->FirstEntity()) {

            SetPlayerEyeAnglesToPosition(closestEnemyVector);
            closestEnemyDistance = std::numeric_limits<float>::max();
            closestEnemyVector = GetFurthestVector();
        }

        auto* modelName{ serverEntity->GetModelName().ToCStr() };
        if (modelName != nullptr) {
            auto entityName{ std::string{GetEntityName(serverEntity)} };

            if (IsEntityEnemy(entityName)) {
                Vector eyePosition{};
                QAngle eyeAngles{};

                GetServerTools()->GetPlayerPosition(eyePosition, eyeAngles);

                auto enemyEyePosition{ GetEyePosition(serverEntity) };

                auto distance{ VectorDistance(enemyEyePosition, eyePosition) };
                if (distance <= closestEnemyDistance) {
                    closestEnemyDistance = distance;
                    closestEnemyVector = enemyEyePosition;
                }
            }
        }

        serverEntity = reinterpret_cast<IServerEntity*>(
            GetServerTools()->NextEntity(serverEntity));

    } while (serverEntity != nullptr);
}

В коде есть несколько вспомогательных функций, которые мы ранее не рассматривали: функция GetFurthestVector возвращает вектор с максимальными значениями float в полях x, y и z; GetEntityName возвращает имя сущности в виде строки, получая член m_iName экземпляра CBaseEntity; а IsEntityEnemy просто сверяет имя сущности со множеством враждебных NPC.

Векторные вычисления и расчёт нового угла обзора происходят в показанной ниже SetPlayerEyeAnglesToPosition:

void SetPlayerEyeAnglesToPosition(const Vector& enemyEyePosition) {

    Vector eyePosition{};
    QAngle eyeAngles{};
    GetServerTools()->GetPlayerPosition(eyePosition, eyeAngles);

    Vector forwardVector{ enemyEyePosition.x - eyePosition.x,
        enemyEyePosition.y - eyePosition.y,
        enemyEyePosition.z - eyePosition.z
    };

    VectorNormalize(forwardVector);

    QAngle newEyeAngles{};
    VectorAngles(forwardVector, newEyeAngles);

    SnapEyeAngles(GetLocalPlayer(), newEyeAngles);
}

Эта функция вычисляет вектор «глаз-глаз», вычитая из вектора позиции глаза врага вектор позиции глаза игрока. Затем этот новый вектор нормализуется и передаётся функции VectorAngles для вычисления новых углов обзора. Затем углы глаза игрока корректируются под эти новые углы, что должно создавать эффект слежения за сущностью.

Как это выглядит в действии?

Вы видите почти прозрачный прицел, следующий за головой идущего по комнате NPC. Когда NPC отходит достаточно далеко, код выполняет привязку к более близкому NPC. Всё работает!

▍ Заключение

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

Реверс-инжиниринг Half-Life 2 был сильно упрощён открытостью Source SDK. Возможность сопоставления кода и структур данных с ассемблерным кодом существенно упростила отладку, однако обычно такого везения не бывает! Надеюсь, эта статья помогла вам понять, как работают aimbot и показала, что создавать их не очень сложно.

Полный исходный код aimbot выложен на GitHub, можете свободно с ним экспериментировать.

Понравилась статья? Поделить с друзьями:
  • Как написать аим на python
  • Как написать администрации варфейс
  • Как написать администрации вайлдберриз
  • Как написать администрации авито сообщение
  • Как написать администратору яндекса