Создание несложного бота для WoW, программирование маршрутов (продолжение)

от автора

Это вторая часть поста на тему создания несложного бота для игры World of Warcraft. С первой частью можно ознакомиться здесь. Сегодня мы поговорим о

  1. написании Recorder‘а клавиш и координат на языке AutoIt
  2. написании Player‘a инструкций для бота
  3. математике 2D, как ориентироваться в декартовой системе координат без теоремы косинусов
  4. управлении роботом при недостаточном количестве датчиков
  5. мерах противодействия ботам

Recorder

Наша задача: чтение цветов пикселей, определение нажатий клавиш, фоновая работа с возможностью приостановки, посылка кликов в приложение. Здесь как нельзя лучше подойдет язык AutoIt. Чтобы написать то же самое на Си, пришлось бы заморачиваться с хендлами, девайсами, хуками, событиями Windows… Причем, для каждого из этих действий надо почитать справку, выяснить способ, определиться со структурами, типами, апишками.

Для редактирования скрипта и быстрого запуска я использовал SciTE-Lite, который включает Highlighting, CodeFolding, Autocomplete, Intellisense, ну и SyntaxCheck. Встроенная справка по языку в подарок. Стандартные фишечки.

Горячие клавиши

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

$paused = false HotKeySet("{F11}", "Pause") HotKeySet("{F10}", "Kill") Func Pause() 	$Paused = NOT $Paused 	While $Paused 		Sleep(100) 	WEnd EndFunc Func Kill()    FileClose($hfile)    Exit EndFunc 

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

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

Получение координат

Напомню, что в аддоне мы клали в цветовые компоненты пикселей числа с плавающей запятой, а приезжают они к нам уже в виде целых байтов:

#include <Color.au3> Opt("PixelCoordMode", 2) ;Отсчет координат пикселей от левого верхнего угла клиентской части окна Opt("MouseCoordMode", 2) ;Отсчет координат мыши от левого верхнего угла клиентской части окна $WinName = "World of Warcraft" $hwnd = WinGetHandle($WinName)	 Func GetPitch()    $pixel2 = _ColorGetRGB(PixelGetColor(10, 0, $hwnd));    Return ($pixel2[2]/255.0-0.5)*4 EndFunc Func GetPos()    $pixel1 = _ColorGetRGB(PixelGetColor(0, 0, $hwnd));    $pixel2 = _ColorGetRGB(PixelGetColor(10, 0, $hwnd));    $result[3] = [ ($pixel1[0]+$pixel1[1]/255.0)/255*100, ($pixel2[0]+$pixel2[1]/255.0)/255*100, $pixel1[2]*7.0/255 ]    return $result EndFunc    

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

Логирование клавиш

Читал я где-то, что автор языка AutoIt не хотел, чтобы такой простой и мощный язык использовался злоумышленниками для написания вредоносных программ. Поэтому он убрал возможность создать обработчик нажатия для всех клавиш сразу, чтобы хотя бы Keylogger’ы не клепали.

Что я могу сказать. AutoIt весьма активно используется для написания вредоносов, а перехват нажатия нужных клавиш мы будем делать так:

local $keys = StringSplit("` [ ] 1 2 3 4 5 6 7 8 9 0 !1 !2 !3 !4 !5 !6 !7 !8 !9 !0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +0 +f {SPACE} {TAB} {ESC} {PAUSE} {DELETE} {BACKSPACE}", " ", 2) for $i = 0 to UBound($keys)-1    HotKeySet($keys[$i], "OnHotKey") Next Func OnHotKey()    ;ToolTip(@HotKeyPressed)    HotKeySet(@HotKeyPressed)    Send(@HotKeyPressed)    HotKeySet(@HotKeyPressed, "OnHotKey")    Switch @HotKeyPressed    Case "[" 	  FileWriteLine($hfile, "mouse left " & MouseGetPos(0) & " " & MouseGetPos(1))    Case "]" 	  FileWriteLine($hfile, "mouse right " & MouseGetPos(0) & " " & MouseGetPos(1))    Case "{PAUSE}" 	  FileWriteLine($hfile, "pause 1000")    Case "{BACKSPACE}" 	  FileWriteLine($hfile, StringFormat("pitch %.2f", GetPitch()))    Case Else 	  FileWriteLine($hfile, "key " & @HotKeyPressed)    EndSwitch EndFunc 

Таким образом, нам не нужно изобретать свою систему наименования клавиш для сохранения их в файл, достаточно использовать принятую в AutoIT

Если нам понадобится перехватить что-то еще (а при игре мы заранее знаем, что мы будем давить и какими заклинаниями пользоваться), мы просто добавим их в длинный список в начале.

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

HotKeySet(@HotKeyPressed) Send(@HotKeyPressed) HotKeySet(@HotKeyPressed, "OnHotKey") 

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

Логирование мыши

В AutoIt нет штатного способа перехвата нажатий кнопок мыши. Есть сторонний модуль с примером использования. Или вы можете повесить свой SetWindowsHookEx (WH_MOUSE_LL). Познавательный пример использования WinAPI Callback-функций на AutoIt тут. Но я не использовал такой подход по двум причинам:

  1. Передвижение персонажа сопряжено с большим количеством нажатий мыши, которые логировать не надо. Плюс, возможны случайные нажатия. Пришлось бы писать логику по отделению мух от котлет.
  2. Использование Hook’ов повышает шансы привлечения к вам внимания «спецслужб». Об этом подробнее в разделе «Противодействие ботам».

Поэтому, как вы уже заметили, я всего лишь использовал кнопки "[" и "]" и давил их по мере необходимости. Главное, не забывать нажимать их.

Запись координат

Ну и, конечно же, наш Recorder должен в фоне записывать передвижение персонажа

$hfile = FileOpen("output.txt", 1) $prev = ""  While true    WinWaitActive($hwnd)    local $pos = GetPos()    $command = StringFormat("move %.3f %.3f %.3f", $pos[0], $pos[1], $pos[2]);    if $pos[0] + $pos[1] > 0 And $command <> $prev Then 	  FileWriteLine($hfile, $command)    EndIf    $prev = $command    Sleep(100) WEnd  

Вы можете спросить, зачем так часто (10 раз в секунду) записывать координаты? Дело в том, что на нашем маршруте навалено препятствий: ящиков, углов, дверных проемов, фонарных столбов, граблей. А персонаж просто «магнитится» к ним. Если где-то, пробегая мимо, он может застрять, он обязательно сделает это. Даже если вы длительное время бежите по прямой, вспомните, азимут движения был задан не идеально, поэтому, возможно, вы давно бежите, упираясь лбом в стенку.

Полный исходный код Recorder'а

#include <Color.au3>  Global $WinName = "World of Warcraft"  Opt("PixelCoordMode", 2) ;Отсчет координат пикселей от левого верхнего угла клиентской части окна Opt("MouseCoordMode", 2) ;Отсчет координат мыши от левого верхнего угла клиентской части окна $paused = false HotKeySet("{F11}", "Pause") HotKeySet("{F10}", "Kill") local $keys = StringSplit("` [ ] 1 2 3 4 5 6 7 8 9 0 !1 !2 !3 !4 !5 !6 !7 !8 !9 !0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +0 +f {SPACE} {TAB} {ESC} {PAUSE} {DELETE} {BACKSPACE}", " ", 2) for $i = 0 to UBound($keys)-1    HotKeySet($keys[$i], "OnHotKey") Next  $hwnd = WinGetHandle($WinName)	 $hfile = FileOpen("output.txt", 1) $prev = ""  While true    WinWaitActive($hwnd)    local $pos = GetPos()    $command = StringFormat("move %.3f %.3f %.3f", $pos[0], $pos[1], $pos[2]);    if $pos[0] + $pos[1] > 0 And $command <> $prev Then 	  FileWriteLine($hfile, $command)    EndIf    $prev = $command    Sleep(100) WEnd Func Pause() 	$Paused = NOT $Paused 	While $Paused 		Sleep(100) 	WEnd EndFunc Func Kill()    FileClose($hfile)    Exit EndFunc Func GetPitch()    $pixel2 = _ColorGetRGB(PixelGetColor(10, 0, $hwnd));    Return ($pixel2[2]/255.0-0.5)*4 EndFunc Func GetPos()    $pixel1 = _ColorGetRGB(PixelGetColor(0, 0, $hwnd));    $pixel2 = _ColorGetRGB(PixelGetColor(10, 0, $hwnd));    local $result[3] = [ ($pixel1[0]+$pixel1[1]/255.0)/255*100, ($pixel2[0]+$pixel2[1]/255.0)/255*100, $pixel1[2]*7.0/255 ]    return $result EndFunc    Func OnHotKey()    ;ToolTip(@HotKeyPressed)    HotKeySet(@HotKeyPressed)    Send(@HotKeyPressed)    HotKeySet(@HotKeyPressed, "OnHotKey")    Switch @HotKeyPressed    Case "[" 	  FileWriteLine($hfile, "mouse left " & MouseGetPos(0) & " " & MouseGetPos(1))    Case "]" 	  FileWriteLine($hfile, "mouse right " & MouseGetPos(0) & " " & MouseGetPos(1))    Case "{PAUSE}" 	  FileWriteLine($hfile, "pause 1000")    Case "{BACKSPACE}" 	  FileWriteLine($hfile, StringFormat("pitch %.2f", GetPitch()))    Case Else 	  FileWriteLine($hfile, "key " & @HotKeyPressed)    EndSwitch EndFunc 

Au3Record

Вместе с SciTE4AutoIt3 поставляется AutoIt3\Extras\Au3Record\Au3Record.exe, который позволяет записывать ваши действия с рабочим столом и оформляет это сразу в виде AutoIt-скрипта. Если вам предстоит много раз совершать последовательность машинальных действий без необходимости думать (например, при установке ПО или патчей на большой парк компьютеров), присмотритесь к этому инструменту.

Player

Напомню, что результатом работы Recorder‘а, описанного в предыдущей главе, является набор команд вида

mouse left 1892 1021 pause 10000 pitch -0.89 mouse right 942 498 pause 10000 move 83.72 50.03 0.604 key ` pause 1000 key {SPACE} pitch -0.1 move 83.777 50.207 1.235 move 83.777 50.207 2.114 move 83.777 50.207 2.827 move 83.777 50.207 2.855 move 83.754 50.327 2.855

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

Поворот и наклон

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

  1. Зажмем правую кнопку мыши
  2. Игра сама поместит курсор на центр экрана и будет удерживать его там. Это позволит пользователю не упереться в итоге курсором в край экрана при повороте
  3. Сдвигаем курсор налево или направо на определенное число пикселей
  4. Направление персонажа смещается налево или направо. Причем, чем быстрее вы двигаете курсор, тем быстрее вертится персонаж

Как нам попроще определить, в какую сторону выгоднее поворачиваться, если у нас есть два угла: текущий и требуемый?

Можно посмотреть на разницу углов, если она положительна — направо. Но рассмотрим случай: текущий угол — 30 градусов, требуемый — 330 градусов. Разница отрицательна, но нам все равно направо. А еще углы могут быть отрицательными. Чтобы не приходилось выписывать все эти условия, просто воспользуемся синусом разницы углов и будем смотреть только на его знак.

Переменные $want и $current содержат (x, y, azimuth)

Func Turn($want) 	while true 		$current = GetPos() 		$sin = sin($current[2] - $want[2]) 		;ToolTip(StringFormat("(%.2f,%.2f,%.2f) to (%.2f,%.2f,%.2f)", $current[0], $current[1], $sin, $want[0], $want[1], $want[2])) 		if abs($sin) < 0.05 then return 		MouseMove(MouseGetPos(0)+50*$sin, MouseGetPos(1), 1)    wend EndFunc 

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

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

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

Движение

На движение накладываются следующие требования

  1. Точность позиционирования невысока. Нельзя встать ровно туда, куда вы желаете
  2. Двигаться надо, не поворачиваясь. Из доступных движений есть только бег вперед, шаги назад и strafe (движение вбок без поворота)
  3. Движение должно быть непрерывным, без остановок и рывков
  4. Как можно меньше возвратов и смен направлений движения. Неприятно смотреть на монитор, если персонаж пробежал чуть-чуть, потом попятился, потом снова побежал и так далее
Вперед или назад?

Если вы не любите математику или закончили школу очень давно, вы можете пропустить этот раздел без ущерба для понимания. Но ничего особо сложного тут нет. Пусть персонаж стоит на (ax, ay) и смотрит под углом и ему надо попасть в (bx, by), ему бежать вперед или назад? Для начала слегка перефразируем задачу: пусть персонаж стоит в (0, 0), смотрит в , а надо ему в (dx, dy).

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

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

Есть так же формула скалярного произведения

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

Таким образом, вычислив скалярное произведение и посмотрев на его знак, мы сможем определить, бежать нам вперед или назад. А в нашем случае вычислять мы его будем по формуле . Более того, это же самое скалярное произведение показывает нам, сколько именно надо бежать вперед или назад по своему геометрическому определению (проекция одного вектора на другой, помноженная на длину второго).
image

Влево или вправо?

Аналогичным образом, синус угла между векторами взгляда и направления до цели (dx, dy) определяет, в какой бок идти. Если положительный — цель по правую руку, и наоборот. Тут нам поможет векторное произведение, а если быть более точным, то псевдоскалярное произведение, вычисляемое в декартовых координатах по формуле

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

Реализация

Запрограммируем наши рассуждения

Func ScalarMult($a, $b, $x, $y) 	return $a*$x + $b*$y EndFunc Func VectorMult($a, $b, $x, $y) 	return $a*$y - $b*$x EndFunc Func GetDirection($x, $y, $wx, $wy, $angle)    $dx = $x - $wx    $dy = $y - $wy    local $result[2] = [ ScalarMult(-sin($angle), -cos($angle), $dx, $dy), VectorMult(-sin($angle), -cos($angle), $dx, $dy) ]    Return $result EndFunc 

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

Функция GetDirection() возвращает массив из двух значений: сколько идти вперед/назад, сколько идти вбок.

Это поворот!

Некоторые читатели могут воскликнуть: «Да ведь эти умножения на синусы-косинусы — ничто иное, как обыкновенный поворот системы координат,

описываемый
image
Зачем морочить нам головы.»

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

Остановка

Теперь мы готовы давить кнопки бега

Func Move($want)    while true 	  StartMoving() 	  local $pos = GetPos() 	  local $dir = GetDirection($pos[0], $pos[1], $want[0], $want[1], $pos[2]) 	  ;ToolTip(StringFormat("(%.2f,%.2f,%.2f) to (%.2f,%.2f, %.2f): (%.2f,%.2f)", $pos[0], $pos[1], $pos[2], $want[0], $want[1], $want[2], $dir[0], $dir[1])) 	  if abs($dir[0]) < 0.1 And abs($dir[1]) < 0.1 then ExitLoop 	   	  if abs($dir[0]) >= abs($dir[1]) Then 		 Send("{a up}{d up}") 		 if $dir[0] <=0 Then 			Send("{w down}{s up}") ; forward 		 Else 			Send("{s down}{w up}") ; backward 		 EndIf 	  Else 		 Send("{s up}") 		 if $dir[1] < 0 Then 			Send("{d down}{a up}") ; right 		 Else 			Send("{a down}{d up}") ; left 		 EndIf 	  EndIf    wend    Send("{s up}{a up}{d up}") EndFunc 

Обратите внимание, мы идем в том направлении, в котором идти дальше всего, и заканчиваем путь, когда мы почти у цели. Это позволяет персонажу «не мельтешить». Кнопка движения вперед не отпускается по достижении точки назначения. Поэтому поворачиваться на требуемый угол нам надо сравнительно быстро.

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

Полный исходный код Player'а

#include <Color.au3>  Global $WinName = "World of Warcraft"  Opt("PixelCoordMode", 2) ;Отсчет координат пикселей от левого верхнего угла клиентской части окна Opt("MouseCoordMode", 2) ;Отсчет координат мыши от левого верхнего угла клиентской части окна $paused = false $moving = false HotKeySet("{F11}", "Pause") HotKeySet("{F10}", "Kill")  ;WinActivate($WinName) $hwnd = WinGetHandle($WinName)	 WinWaitActive($hwnd) PlayFile("outdoor.txt", 0) PlayFile("indoor.txt", 0) PlayFile("outdoor.txt", 0) PlayFile("indoor.txt", 0) PlayFile("outdoor.txt", 0) PlayFile("indoor.txt", 0) PlayFile("outdoor.txt", 0) PlayFile("indoor.txt", 0) PlayFile("outdoor.txt", 0) PlayFile("indoor.txt", 0)  Func PlayLine($line)    local $command = StringSplit($line, " ", 2)    ;ToolTip(StringFormat("%s %s %s", $command[0], $command[1], $command[2]))    Switch $command[0] 	  Case "move" 		 local $want[3] = [ number($command[1]), number($command[2]), number($command[3]) ] 		 Move($want) 		 Turn($want) 	  Case "pause" 		 StopMoving() 		 Sleep($command[1]) 	  Case "key" 		 If $command[1] = "{SPACE}" Then 			Send("{SPACE down}") 			Sleep(300) 			Send("{SPACE up}") 		 Else 			StopMoving() 			if UBound($command) > 2 Then 			   Send($command[1] & " " & $command[2]) 			Else 			   Send($command[1]) 			EndIf 			Sleep(1500) 		 EndIf 	  Case "mouse" 		 StopMoving() 		 MouseClick($command[1], $command[2], $command[3]) 		 Sleep(500) 	  Case "pitch" 		 SetPitch($command[1])     EndSwitch EndFunc Func PlayFile($filename, $skip = 0)    $hfile = FileOpen($filename, 0)    For $i = 1 to $skip 	  $line = FileReadLine($hfile)    Next    while True 	  $line = FileReadLine($hfile) 	  if @error = -1 Then ExitLoop 	  PlayLine($line)    wend    FileClose($hfile)    StopMoving() EndFunc Func Sign($x) 	if ($x < 0) then 		return -1 	else 		return 1 	EndIf EndFunc Func ScalarMult($a, $b, $x, $y) 	return $a*$x + $b*$y EndFunc Func VectorMult($a, $b, $x, $y) 	return $a*$y - $b*$x EndFunc Func StartMoving() 	if $moving then return 	$moving = true; 	WinWaitActive($hwnd) 	MouseMove(@DesktopWidth/2, @DesktopHeight/2, 0) 	MouseDown("right") 	Sleep(300) EndFunc Func StopMoving() 	$moving = false 	Send("{w up}{s up}{a up}{d up}") 	MouseUp("right") 	Sleep(300) EndFunc Func GetPitch()    StartMoving()    $pixel2 = _ColorGetRGB(PixelGetColor(10, 0, $hwnd));    Return ($pixel2[2]/255.0-0.5)*4 EndFunc Func GetPos()    StartMoving()    $pixel1 = _ColorGetRGB(PixelGetColor(0, 0, $hwnd));    $pixel2 = _ColorGetRGB(PixelGetColor(10, 0, $hwnd));    local $result[3] = [ ($pixel1[0]+$pixel1[1]/255.0)/255*100, ($pixel2[0]+$pixel2[1]/255.0)/255*100, $pixel1[2]*7.0/255 ]    return $result EndFunc Func GetDirection($x, $y, $wx, $wy, $angle)    $dx = $x - $wx    $dy = $y - $wy    local $result[2] = [ ScalarMult(-sin($angle), -cos($angle), $dx, $dy), VectorMult(-sin($angle), -cos($angle), $dx, $dy) ]    Return $result EndFunc Func Move($want)    while true 	  local $pos = GetPos() 	  local $dir = GetDirection($pos[0], $pos[1], $want[0], $want[1], $pos[2]) 	  ToolTip(StringFormat("(%.2f,%.2f,%.2f) to (%.2f,%.2f, %.2f): (%.2f,%.2f)", $pos[0], $pos[1], $pos[2], $want[0], $want[1], $want[2], $dir[0], $dir[1])) 	  if abs($dir[0]) < 0.1 And abs($dir[1]) < 0.1 then ExitLoop 	   	  if abs($dir[0]) >= abs($dir[1]) Then 		 Send("{a up}{d up}") 		 if $dir[0] <=0 Then 			Send("{w down}{s up}") ; forward 		 Else 			Send("{s down}{w up}") ; backward 		 EndIf 	  Else 		 Send("{s up}") 		 if $dir[1] < 0 Then 			Send("{d down}{a up}") ; right 		 Else 			Send("{a down}{d up}") ; left 		 EndIf 	  EndIf    wend    Send("{s up}{a up}{d up}") EndFunc Func Turn($want) 	while true 		local $current = GetPos() 		$sin = sin($current[2] - $want[2]) 		;ToolTip(StringFormat("(%.2f,%.2f,%.2f) to (%.2f,%.2f,%.2f)", $current[0], $current[1], $sin, $want[0], $want[1], $want[2])) 		if abs($sin) < 0.05 then return 		MouseMove(MouseGetPos(0)+50*$sin, MouseGetPos(1), 1)    wend EndFunc Func SetPitch($want) 	while true 		$current = GetPitch() 		$sin = sin($current - $want) 		;ToolTip(StringFormat("pitch %.2f to %.2f: %.2f", $current, $want, $sin)) 		if abs($sin) < 0.05 then return 		MouseMove(MouseGetPos(0), MouseGetPos(1)+50*$sin, 1) 	wend EndFunc    Func Pause() 	$paused = not $paused 	if $paused then StopMoving() 	While $Paused 		Sleep(1000) 	WEnd EndFunc Func Kill() 	StopMoving() 	Exit EndFunc 

Управление (ро)ботом


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

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

Да, можно было написать внутриигровые макросы, которые бы брали в цель нужного моба, и тем самым избавиться от некоторых проблем, но мне хотелось оставить бота как можно более универсальным.

Двери-решетки. На них нужно кликнуть, чтобы открыть. А так как они решетки, то кликнуть вы можете случайно аккуратно в дырку. И персонаж будет биться лбом об дверь в попытке пробежать дальше.

Таким образом, записав маршрут, вы открываете его в блокноте и отлаживаете. Вы наблюдаете за персонажем и при проблемах или возможных проблемах «вбиваете костыли» в маршрут: вставляете дополнительное заклинание для верности (вдруг моб выживет), добегаете до той точки, в которой моб будет точно виден и досягаем независимо от его желания прогуляться. Вы изменяете маршрут, чтобы уж точно не споткнуться об этот злосчастный ящик. После 10 забегов я пришел к выводу, что лучше бы я просто переубивал там всех пауков, чем они потом смогли бы отомстить мне в одном случае из 10 своим внезапным заклинанием. Но, когда персонаж уже бегает сам, вам уже не хочется бежать самому ножками, вам хочется просто смотреть на это.

Я понял, каково это, запускать марсоход, программировать робот-пылесос, идти в полной темноте по квартире или учить роботов играть в футбол. Надо все делать надежно, с запасом. На марсе ямы будут везде, а еще будут нависающие скалы и пещеры. В квартире роботу-пылесосу будут противостоять провода и тапочки повсюду, а еще будет кот, который захочет поиграть. В полной темноте будут углы, ножки стульев, и даже упавшие на пол ножи. Только на ощупь!

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

Противодействие ботам


Люди, далекие от MMORPG игр, спросят: «А зачем вообще противодействовать ботам? Ведь автоматизация везде приветствуется.» А вот и нет. Если все платят одинаково, то и возможности должны быть равными. Иначе ущемленная часть обижается и перестает играть. Если в игре доступны, например, макросы, то они должны быть понятны и не программистам. Поэтому разработчик игры запрещает использование средств автоматизации на уровне лицензионного соглашения и следит.

Я нередко встречал возгласы, что Blizzard (это разработчик WoW) плохо следит, и боты повсюду. Во-первых, сам я так не считаю, думаю, их доля преувеличена. А во-вторых, давайте обсудим, что же есть в арсенале разработчика. Как не только распознать бота, но еще и иметь подтверждающие факты. Ведь на основе подозрений невежливо банить игрока.

Например, гейм-мастер (внутриигровая поддержка) способен наблюдать за игроком и видеть его глазами. Но даже, увидев явно «нечеловеческое» поведение, как то: длительное вращение на месте, попытки пройти сквозь стену, беспрерывная игра в течение суток (обычно сбор ресурсов), игнорирование попыток общения других игроков, — все это не дает гарантий использования ботов. Быть может, игрок просто заснул на клавиатуре, очень целеустремлен, или просто не хочет общаться, когда «работает».

Поэтому у Blizzard есть Warden. ПО по сути напоминает облачный антивирус:

  1. фоновые программы анализируются
  2. их метаданные отправляются в облако
  3. чит-аналитики принимают решение, что такой-то процесс нечестно взаимодействует с игрой
  4. к пользователям, использовавшим эти программы, применяются санкции

Есть данные, что игроков банили за запуск игры из-под Wine. Blizzard не исключает возможность бана за использование программируемых клавиатур и мышек.

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

  1. Нужно читать, а главное, писать в память процесса игры
  2. Нужно инжектить туда свои модули
  3. Ваше ПО для получения преимуществ в игре должно быть как можно более популярным
  4. Вам надо использовать руткит-технологии и полиморфизм для обхода систем защиты

И вот тогда рано или поздно придет ban-wave для всех пользователей этого ПО.
Что же касается посылки нажатия клавиш и языка AutoIt, то по всей видимости, его запущенный интерпретатор слишком распространен среди игроков и используется также для «мирных» целей. В итоге отличить ботоводов от офисных автоматизаторов труда Blizzard не могут, ну, или не хотят.

Заключение

Сегодня мы описали:

  1. Программу для перехвата клавиш и записи в файл
  2. Программу для воспроизведения команд из файла и перемещения в мире
  3. Процесс управления персонажем в условиях малой информации о мире
  4. Математику для определения направления движения
  5. Порассуждали на тему противодействия ботам, так ли это легко

Больше всего времени при написании бота заняло, конечно же, написание самого поста. Бот сделал забегов 20 (игра запрещает делать больше 5 в час). Пока он бегает, компьютер занят, а мне надо пост писать. Конь пока не выпал. Но если выпадет, я обрадуюсь не меньше, ведь я получу его хоть и не фармом, но все-таки трудом.

Голосуйте, пишите комментарии, не ругайте за спорную тему. Удачи.

Чего бы вам больше хотелось видеть в моем следующем посте

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

Никто ещё не голосовал. Воздержавшихся нет.

ссылка на оригинал статьи http://habrahabr.ru/post/189874/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *