PDA

Просмотр полной версии : [Статья] Как написать бота. Часть 2.[Borland C++ Builder 6]


dwa83
05.05.2012, 12:09
Пишу продолжение в новой теме, так как форум не поддерживает посты такого обьёма, чтобы вместить все части. Тема, содержащая первые 3 части находится тут Как написать бота. Часть первая. ([Ссылки могут видеть только зарегистрированные и активированные пользователи])

В разделе 1 мы разбирались в том, как считывать различные значения параметров из клиента игры, а так же написали структуру READER, с помощью которой мы эти значения считывали. Раздел 2 был посвящён теме инжектов, где я попытался объяснить что это и как это использовать в своём боте. В разделе 3 мы от инжектов перешли к использованию пакетов. На данном этапе мы уже можем получать различные значения из игры а так же программно выполнять какие-либо действия в игре(с помощью инжектов или отправки пакетов). Настало время уделить внимание основной нашей структуре. Это структура BOT. В ней мы будем писать всю логику поведения бота. Давайте определим сразу, что будет делать наш бот. Мы не будем делать суперкрутого бота, сделаем простенького. Итак действия нашего бота: выделять моба, бить его пока не убьём, собирать лут и повторять эти действия.
Все эти действия можно заключить в какой-нибудь бесконечный цыкл, но тогда мы не сможем управлять окном нашего бота, так как бесконечный цыкл не будет давать нашей программе обрабатывать различные события(мы даже не сможем закрыть программу). Можно конечно в этом цикле использовать функцию ProcessMessages(), входящую в класс приложения Applocation, и с помощью неё принудительно заставлять нашу программу обработать произошедшие события, но наткнёмся на ещё одну проблему. ProcessMessages() в свою очередь не будет давать продолжаться циклу в случае, если произойдёт какое-нибудь длительное событие (например перетаскивание окна нашей программы). Можно всё что связанно с работой бота поместить в отдельный поток, который будет работать параллельно с основным потоком нашей программы.

Мы не будем применять ни один из вышеописанных способов, а сделаем по другому. Для каждого действия бота мы напишем отдельную функцию. Так как и инжекты и считывания происходят давольно быстро, то и эти функции будут выполняться тоже быстро. Затем мы создадим функцию-переключатель, которая будет переключать бота между этими функциями. Мы создадим отдельную переменную, которая будет содержать числовое значение, и в зависимости от того, что это за значение, в программе будет выполняться соответствующая этому действию функция. В событии таймера мы напишем ВСЕ наши функции, но при каждом срабатывании таймера будет проверяться значение переменной и выполняться только одна из функций, а другие просто будут пропускаться.
Давайте уже приступим. Перейдём к редактированию модуля Bot и в структуре BOT объявим функции, соответствующие всем действиям бота, которые нам понадобятся.

void RUNACTION(); // один цикл обработки текущего режима(именно это и будет внутри таймера)
void Stop(); // режим остановки бота
void Switcher(); // режим переключения режимов по приоритету
void ScanLoot(); // режим сканирования на наличие нашего лута
void GetLoot(); // режим подбора кучки лута
void KillMob(); // режим атаки на моба
void TargetMob(); // режим выделения моба для битья

Позже напишем их реализацию. А пока что давайте создадим ещё одну вспомогательную структуру, с помощью которой бот будет работать с действиями. Назовём её ACTION_STACK и обьявим перед структурой BOT. Это будет структура, содержащая значение текущего режима а так же стек для сохранения режимов, чтобы можно было из одного режима переходить к другому, а после его окончания возвращаться к предыдущему.

// Стек действий
struct ACTION_STACK
{
int Action; // Параметр текущего действия бота
int arr[1000]; // можно запомнить в стек 1000 режимов
int current; // номер верхушки стека
void Push(){current+=1; arr[current]=Action;} // запомнить режим в стек
void Pop(){Action=arr[current];current--;} // восстановим из стека последний запомненный режим режим
};

Так же в труктуре BOT объявим переменную типа нашей новой структуры.

ACTION_STACK st; //стек режимов бота

Ещё нам понадобятся некоторые вспомогательные переменные-флаги и счётчики. Опишем их внутри структуры BOT. Они будут использоваться в функциях действий.

// Флаги и счётчики
bool lootgotow; // Собран ли лут
int count; // Счётчик

Давайте перед всеми структурами определим для каждого режима отдельное числовое значение и соответствующее этому значению имя

// действия бота
#define A_STOP 0 // остановки
#define A_SWITCHER 1 // переключатель режимов
#define A_TARGET_MOB 2 // режим выделения моба
#define A_KILL_MOB 3 // режим убийства моба
#define A_SCAN_LOOT 4 // режим сканирования лута
#define A_GET_LOOT 5 // режим подбора кучки

Теперь эти имена мы будем использовать вместо того, чтобы писать цыфры, определяющие нужный режим.
Теперь можно приступить к написанию тел объявленных нами функций. Перейдём к редактированию Bot.cpp и напишем эти функции.
Функция RUNACTION будет отвечать за выполнение по срабатыванию таймера того действия, которое в данный момент является текущим. В ней будет происходить выбор функции в зависимости от режима. Тут будет всё просто. Проверяется какой режим является текущим, и выполнение соответствующей этому режиму функции.

void BOT::RUNACTION()
{
if (st.Action==A_STOP) Stop();
if (st.Action==A_SWITCHER) Switcher();
if (st.Action==A_SCAN_LOOT) ScanLoot();
if (st.Action==A_GET_LOOT) GetLoot();
if (st.Action==A_KILL_MOB) KillMob();
if (st.Action==A_TARGET_MOB) TargetMob();
}

Вот так. Проще некуда. А теперь реализация отдельных действий.

// функция режима остановки бота
void BOT::Stop()
{
// в режиме остановки мы обнулим наши флаги и счётчики
lootgotow=false;
count=0;
}

// режим переключателя других режимов
void BOT::Switcher()
{
st.Push(); // запомним этот режим, а дальше включим нужный


if (client.get.myTargetWID()!=0) // если в таргете кто-то есть
st.Action=A_KILL_MOB; // режим убийства моба ставим активным
else // если в таргете нет никого
{
if (lootgotow) // если лут уже собран
{
st.Action=A_TARGET_MOB; // режим выделения моба
lootgotow=false; // установим в несобранный для следующей проверки
}
else // если лут ещё не собирали
{
st.Action=A_SCAN_LOOT; // режим подбора лута
}
}

}

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

// сканирование лута
void BOT::ScanLoot()
{

if (count==0) Sleep(1000); // подождём секунду пока лут упадёт(данные о луте придут с сервера)

while ((client.get.lootWID(count)==0)&&(count<0x300)) // пока не найдём кучку в массиве либо не конец массива
count++;

if (count<0x300) //если не конец массива
{
if (client.get.lootType(count)!=2) // если это не рес
{
st.Push(); // запомним этот режим
st.Action=A_GET_LOOT; // режим подбора кучки
}
else count++; //переходим к следующему луту в массиве
}
else // всё проверили и собрали
{
count=0; // обнулим счётчик
lootgotow=true; // лут собран
st.Pop(); // вернёмся в предыдущий режим
}
}

// подбор отдельной кучки
void BOT::GetLoot()
{
WORD type=client.get.lootType(count);
DWORD wid=client.get.lootWID(count);
if (wid!=0) // если лут ещё существует
{
client.inject.GetLoot(wid,type); // инжектим подбор
}
else // либо собрали, либо пропал
{
count++; // следующая куча в массиве
st.Pop(); // вернёмся в предыдущий режим(предыдущим был режим сканирования лута)
}
}

// убийство моба
void BOT::KillMob()
{
client.inject.Skill(299); // используем скилл
st.Pop(); // поставим предыдущий режим как текущий
}

// выделение моба
void BOT::TargetMob()
{
DWORD wid=0;
float dist=100000;
for (int i=0; i<0x300; i++)
if (client.get.mobWID(i)!=0)
{

if (client.get.mobDist(i)<=dist) // ближе предыдущего моба из списка
if (client.get.mobType(i)==6) // точно моб
if (client.get.mobAction(i)!=4) // не труп моба
{
wid=client.get.mobWID(i);
dist=client.get.mobDist(i);
}
}

if (wid!=0) // Если мы отсеяли не пустое место в списке мобов
{
client.inject.TargetMob(wid); // инжектим таргет моба
st.Pop(); // выделили и возвращаемся в предыдущий режим
}
}

Некоторые функции, такие как:
DWORD myTargetWID(); - считывает WID того, кто в таргете
DWORD lootWID(int nom); - считывает из массива лута WID кучки с номером nom
WORD lootType(int nom); - для лута с номером nom возвращает его тип

DWORD mobAction(int i); - возвращает текущее действие моба с номером i в массиве мобов

void GetLoot(wid,type); - функция, которую нужно добавить в INJECTOR, инжектит подбор лута по его WID
void Skill(DWORD id); - инжект использования скилла по ID

вы должны добавить самостоятельно в структуры READER и INJECTOR, по аналогии с теми что мы добавляли в разделе 1 и 2.
В примере client.inject.Skill(299) используется скилл с ID=299. Это скилл друида "Жалящий рой". Проверять работу бота будем на друиде.
Вообще все функции действий, которые я здесь указал, по максимуму упрощены и не оптимизированны. Доработкой вы займётесь сами.

Итак, все функции мы реализовали.
Давайте заставим нашего бота работать. На форму кинем две кнопки с надписями Старт и Стоп. В уже размещённый нами таймер(по умолчанию должен быть включен, всегда) вставим функцию RUNACTION. При срабатывании таймера она выполнит один раз функцию соответствующую текущему режиму. Время срабатывания поставим например 200;

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
bot.RUNACTION();
}

Теперь перепишем код в событии создания формы

void __fastcall TForm1::FormCreate(TObject *Sender)
{
bot.client.Init();
bot.st.Action=A_STOP; // начальный режим - бот остановлен
}

В событии кнопки Стоп напишем

bot.st.Action=A_STOP; // остановим бота

В событии кнопки Старт включаем нашего бота.

bot.st.current=0; // обнулим стек действий
bot.st.Push(); // запомним режим остановки
bot.st.Action=A_SWITCHER; // режим свитчера других режимов

Теперь войдём в игру друлей(или другим персом, но тогда вместо ID=299 ставьте соответствующий ему скилл, а вообще лучше сделать считывание в боте скиллов, которые есть у персонажа и использовать эти значения), прийдём на место фарма, запустим нашего бота и нажмём кнопку Старт.
Наш персонаж начнёт бить мобов и собирать лут. Вот и всё. Всю работу по усложнению логики поведения оставляю вам. Вот, к примеру, что из себя представляют функции Switcher() и RUNACTION() в моём боте:

void BOT::RUNACTION()
{
if (st.Action==A_STOP) Stop();
if (st.Action==A_SWITCHER) Switcher();
if (st.Action==A_SCAN_LOOT) ScanLoot();
if (st.Action==A_GET_LOOT) GetLoot();
if (st.Action==A_KILL_MOB) KillMob();
if (st.Action==A_AGR_SELECT) AgrSelect();
if (st.Action==A_TARGET_MOB) TargetMob();
if (st.Action==A_CALL_PET) CallPet();
if (st.Action==A_HILL_PET) HillPet();
if (st.Action==A_RES_PET) ResPet();
if (st.Action==A_USE_HP) UseHP();
if (st.Action==A_USE_MP) UseMP();
if (st.Action==A_TORG) Torg();
if (st.Action==A_GOTO_TORG) GoToTorg();
if (st.Action==A_GOTO_FARM) GoToFarm();
if (st.Action==A_FALL_DOWN) FallDown();
if (st.Action==A_FLY_H) FlyH();
if (st.Action==A_WAIT_STOP) WaitStop();
if (st.Action==A_MOVE_STOP) MoveStop();
if (st.Action==A_MOVE) Mov();
if (st.Action==A_FLOAT_UP) FloatUp();
if (st.Action==A_FLY_TOGGLE) FlyToggle();
if (st.Action==A_FLY_OFF) FlyOff();
if (st.Action==A_FLY_ON) FlyOn();
if (st.Action==A_RES_PERS) ResPers();
if (st.Action==A_VER_PERS) VerPers();
}


void BOT::Switcher()
{
st.Push(); // запомним режим цикла
int mhp=client.get.myHP();
int mmp=client.get.myMP();

if (client.get.myTargetWID()!=0)
{
Target=client.get.mobCoords(MobNom(client.get.myTa rgetWID()));
st.Action=A_KILL_MOB; // режим убийства моба
}

if (client.get.petNomActive()!=0) st.Action=A_CALL_PET; // режим вызова пета
else if (client.get.petHPproc(0)<0.8) st.Action=A_HILL_PET; // режим лечения пета

if (client.get.myTargetWID()==0)
{
if (lootgotow)
{
st.Action=A_TARGET_MOB; // режим выделения моба
lootgotow=false;
}
else
{
st.Action=A_SCAN_LOOT; // режим подбора лута
client.inject.PetGuard();
}
}

if (!farmpoint) st.Action=A_GOTO_FARM;
if ((InvFull())||
(KolItems(IDHPPots())==0)||
(KolItems(IDMPPots())==0)) st.Action=A_TORG; // режим продажи

if ((client.get.myOtkatBMP()==0)&&(mmp<values.MyMP)) st.Action=A_USE_MP;
if ((client.get.myOtkatBHP()==0)&&(mhp<values.MyHP)) st.Action=A_USE_HP;
if (mhp==0) st.Action=A_VER_PERS; // включим режим проверки перса

while (!client.get.Online()) {Sleep(10000); ReLogin();} // подождём пока переподключится интернет и заходим в игру
}

Ниже приложу проект примера.

vogel
05.05.2012, 13:59
Статья годная...однако :

void BOT::RUNACTION()
{
if (st.Action==A_STOP) Stop();
....
}

switch выглядит красивее, а реализация действий шаблоном "Command" ([Ссылки могут видеть только зарегистрированные и активированные пользователи]) с нормальным полиморфизмом - правильнее.

dwa83
05.05.2012, 15:13
switch выглядит красивее
Да, не спорю, но эти примеры я не оптимизировал, а писал "наскоро". Потому вы можете делать по своему.

Добавлено через 42 минуты
Почитал тему по ссылке. Но мой способ основан на другом принципе, а именно "конечного автомата", то есть система состояний, и переходов между ними. Смена состояний происходит в зависимости от входящих параметров, коими являются полученные из клиента значения. А структура стека здесь скорее вспомогательная, служащая для того, чтобы мы могли вернуться в предыдущее состояние даже не зная что это за состояние было. Вобщем, для большей гибкости. Так же на этом же форуме нашёл вот такую темку Конечные автоматы ([Ссылки могут видеть только зарегистрированные и активированные пользователи]).

vogel
05.05.2012, 18:18
Я знаю, что такое конечные автоматы, но есть мнение (и не только моё), что делая их без объектов и полиморфизма - вы неправильно их готовите.

dwa83
05.05.2012, 18:33
вы неправильно их готовите
Возможно и так, но ведь мы не создаём какой то серьёзный коммерческий проект, это просто программа бот для собственных нужд. Потому я думаю, что не критично то, что мы делаем его не по всем правилам программирования) Тем более я сам в этом деле любитель, потому написал так, как мне показалось проще. Здесь просто показано как можно сделать и не настаивается на том что так нужно.

AntiQuarEugene
30.07.2012, 18:10
Перезалейте плиз заново все проекты(в том числе и в первых частях) . Заранее спасибо=)

Блин1
09.12.2012, 07:22
а почему бот написан на дельфи, а тема по с++?

qwer7074
09.12.2012, 12:29
На каком делфи?Это C++Builder

Zevs86
21.03.2013, 04:59
dwa83, Перезалейте плиз заново все проекты

dwa83
11.05.2013, 02:30
Всех у меня увы уже нету, но там есть ссылка на уже готовый проект(по крайней мере должна быть :))

Xantrax2150198
20.05.2013, 21:10
Что за функция такая : client.get.myTargetWID() в тексте программы автора? Если я не ошибаюсь, то это FullTarget 00693D60 из оффсетов?
Или если я не прав, то как определить, есть ли у перса в таргете сейчас кто-то?

Xantrax2150198
20.05.2013, 23:31
Тоже хотел спросить на подобе такого вопроса что такое client.get.myTargetWID
Это WID моба в таргете персонажа.
Читается из памяти как:
DWORD CNewBotDlg::PlayerTarget(DWORD pid)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
DWORD value;
ReadProcessMemory(hProcess,(void*)BA,&value,sizeof(DWORD),NULL);
ReadProcessMemory(hProcess,(void*)(value + M_D1),&value,sizeof(DWORD),NULL);
ReadProcessMemory(hProcess,(void*)(value + PersStruct),&value,sizeof(DWORD),NULL);
ReadProcessMemory(hProcess,(void*)(value + PersTarget),&value,sizeof(DWORD),NULL);
return value;
}
Где BA 0x00B8FBCC, M_D1 0x1C, PersStruct 0x34, PersTarget 0x0BF8.

doc doc
06.01.2014, 03:30
Автор, подскажи пожалуйста, что за MY_ACTION_ARRAY в функции инжекта движения? В Offsets.h нету такого(

dwa83
30.01.2014, 18:22
версия 1.4.4

+1050 ActionArray /Массив действий/ +...

+C Stand Action
+14 Current Action
+14 +10 P.Target ID
+30 +4 MovePoint Action
+30 +4 + 20 Destination LocX
+30 +4 + 24 Destination LocZ
+30 +4 + 28 Destination LocY
+30 +8 MoveTarget Action
+30 +8 +20 Destination ID
+30 +10 +30 M.Target ID
+30 +2C Mining Action
+30 +30 PetEvocative Action

пс: исходник ([Ссылки могут видеть только зарегистрированные и активированные пользователи])