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

  • Tutorial
Это вторая часть поста на тему создания несложного бота для игры 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 в час). Пока он бегает, компьютер занят, а мне надо пост писать. Конь пока не выпал. Но если выпадет, я обрадуюсь не меньше, ведь я получу его хоть и не фармом, но все-таки трудом.

Голосуйте, пишите комментарии, не ругайте за спорную тему. Удачи.
Чего бы вам больше хотелось видеть в моем следующем посте

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

Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 44
  • +3
    С одной стороны хочется шадоуморном Вас поколотить за ботоводство. С другой — снимаю шляпу. Реально интересно и понятно описано.
    • +28
      Однажды я тоже хотел описать процесс создания ботов для одной очень популярной MMORPG, даже было написано 2 поста и 5-ти, но я не решился опубликовать их, так как после таких постов многих игроков могли забанить, что не есть хорошо. Боты, кстати, тоже на AutoIt. Пишу на нем еще со 2-й версии. Идеален для этого дела. Посвятил ботоводству более 2-х лет, но к счастью бросил это дело) Я проводил кучи исследований о том, как палятся боты. Много знаний в этой теме за годы накопилось. Если кому-то интересно — могу опубликовать пару постов, так как половина тем уже потеряла актуальность, но не смысловую наргузку, и больше не стоит бояться, что я кого-то подставлю ;)
      • 0
        Очень интересно, обязательно публикуйте и рассказывайте всё, что знаете.
        • +1
          Тогда будем писать! Надеюсь выкрою время на выходных.
      • 0
        Конечно больше не стоит бояться, что кого-то забанят, пишите)
        • 0
          Если Вы описываете процесс создания взрывчатки, не стоит бояться, что террористов из-за вас посадят. Пишите, конечно, это любопытно.
          • +1
            Каждый бан спасет чью-то жизнь — смотрите на это с такой точки зрения и, конечно, пишите.
            • 0
              Писал ботов еще лет 10-12 назад, в одной из первых MMORPG — Ultima Online. Получил интересный опыт, бот имел кучу подпрограмм для добычи руды, переплавки, складирования, телепортации и снова в шахту. Отслеживались диалоги, так что всегда можно было поприветствовать, если с тобой здороваются.
            • 0
              Нужно читать, а главное, писать в память процесса игры

              Всегда интересовало: может ли программа запущщеная от имени юзера узнать о том то ее память кто-то читал? А если читает процесс с более высокими привилегиями?

              И еще одно, пожет ли программа определить, что клики мыши были не настоящие, а кем-то сэмулированы? Тем-же автоитом, например.
              • 0
                Всегда интересовало: может ли программа запущщеная от имени юзера узнать о том то ее память кто-то читал? А если читает процесс с более высокими привилегиями?

                да. нет. :)
                И еще одно, пожет ли программа определить, что клики мыши были не настоящие, а кем-то сэмулированы? Тем-же автоитом, например.

                по самому клику — зачастую нет (кроме случаев, когда кликер сделан криво), по наличию в запущенных процессах, например, uopilot.exe я дисконнектил клиента сразу же. Переименвание экзешника спасает, а в самом крайнем случае можно и драйвер написать.

                P.S. Варден от 2008го года позволял получать список процессов, загруженных к WoW dll, читать произвольные данные из адресного пространства клиента. При этом варден = dll модуль, загружаемый на компьютер играющего, может обновляться сколько-угодно раз без обновления клиента и перезапуска сервера. При некотором желании они могли бы варденом и переписку скайпа считывать на предмет фраз «я ставлю бота и работать».
                • 0
                  Скоро узнаете из новых постов :)
                • 0
                  Самый лучший бот — это тот, который расположен на шлюзе и перехватывает пакеты между сервером и клиентом.
                  • +2
                    С учетом криптования трафика и смены номеров опкодов в каждом патче, это по силу лишь тем, кто не играет в игры и не водит ботов :)
                    • 0
                      да оно и без смены опкодов было очень сомнительным удовольствием.

                      воссоздать состояние клиента — это очень большой объем работы.
                      в памяти игры обычно тысячи весьма и весьма непростых игровых объектов, плюс много-много collision geometry (которая не передается по сети, а берется из локальных файлов клиента).

                      практического смысла воссоздавать всё это — ноль. гораздо проще и удобнее поручить это официальному клиенту, а ботом — только вызывать нужные функции и читать\писать память.
                      • 0
                        Как раз локальная геометрия обрабатывается относительно просто (допустим, на примере какого-нибудь оупен-сорсного wowmapview), из игровых объектов актуальны только мобы и ресурсы для фарминга, да и вообще это высокоуровневая задача, которая вполне решаема и скриптуется. Вспомнить хотябы Injection и EasyUO для Ultima Online.
                        А вот низкий уровень наворочен настолько, что его реализация потребует больше времени, чем может принести выгоды ботоводство.
                      • 0
                        А никто и не говорил, что это будет легко :)
                    • 0
                      Ухты, неожидал увидеть свою работу, ето я про ED-209. Приятно.
                      За статью спасибо, очень интересно. Жду 3 часть.
                      • 0
                        Спасибо за Ваш труд. Я еще переживал, не нарушу ли я авторских прав. Скриншот из фильма Робокоп был недостаточного качества.
                        • 0
                          Пожалуйста, пережмите картинки под ширину статьи и не ипользуйте для полноцветной графики PNG. Тормоза страшные)
                          • 0
                            Спасибо за замечание. Полагал, что habrastorage сам пережимает и уменьшает размер. Проверил, две картинки по 1Mb, а одна из них не под катом. Ужас. Переделаю.
                      • +5
                        А можно видео геймплея с запущенным ботом?
                      • 0
                        Спасибо, статья очень интересная, всегда хотел узнать, как делаются подобные штуки :) Жду продолжения.

                        P. S. Удивлен, что нашлось 16 человек, у которых поднялась рука отметить последнюю галочку. Даже если Вы такой умный, что ничего нового не узнали, есть еще много не столь просветленных, которым интересно.
                        • 0
                          Хех, 16 — это только за первый час.
                        • 0
                          Чтобы противодействовать Warden'у, можно сделать свои HID-устройства, которые будут определяться, как самые обычные клавиатура с мышкой, которые и будут управлять персонажем. Остается только добавить некое головное устройство, которое будет обеспечивать синхронность действий этих ребят.

                          Но это все мелочи, потому что взаимодействовать с внутриигровым миром на уровне игрока можно лишь внедрившись в процесс клиента игры, перехватывая трафик или вовсе реализовав альтернативный клиент, как это сделали, скажем, в проекте Openkore. Ну, или создавать ИИ и обучать его :)
                          • +1
                            я вас умоляю.

                            чтобы противодействовать вардену — нужно всего лишь:

                            1. не трогать участки памяти, которые он сканирует (для самых ленивых — регулярно обновляемые списки этих участков есть в публичном доступе).

                            2. не светить своего бота (варден сканирует сигнатуры. при этом сам он в открытом виде ничего не передает, максимум — хэши. как сигнатуры попадают в его базу, и кто именно их туда вносит — думаю понятно)

                            собственно политика близардов направлена на недопущение массового ботоводства (и даже с этим у них не очень — см. HonorBuddy). отдельные деятели с нестандартными велосипедами их интересуют чуть менее чем нисколько.
                            • 0
                              Моя идея несколько более надежна (и менее банальна), чем традиционные методы противостояния читеров и вирусописателей с античитами и антивирусами, где если сегодня ничего не определяется, то завтра вполне уже может детектиться уже целый зоопарк штаммов, на что тут же (или вовсе превентивно) реагируют действиями, ведущими к смене сигнатур.
                          • 0
                            Картинка главная зачетно вставлена. Пофиг что это на рабочий стол)
                            • +1
                              Великолепная статья. Вспомнилось, как в свое время писались километровые скрипты для копания и прочих процессов Ultima Online. Судя по статье, проблемы перед ботописателями за прошедшие годы не изменились. Все те же препятствия, мобы, отладка скрипта и прочие радости ботовода.

                              • 0
                                Играл под wine'ом в wow… живой)
                                • 0
                                  Посмотрите в сторону isxwow
                                  • +5
                                    напомнило перл с wowlol
                                    — Здравствуйте меня зовут Дэвид, я геймастер этого сервера, у вас есть время поговорить?
                                    — Да
                                    — Видите ли... некоторые люди пишут мне что вы бот
                                    — Всё ок
                                    — Да, теперь я сам вижу, до чего же порой есть завистливые и лживые люди.
                                    — Да
                                    — Надеюсь они вас больше не побеспокоят
                                    — Всё ок
                                    — вы должны понять, просто политика компании не приемлет ботов, ну вы наверно и так это знаете
                                    — Да
                                    — Спасибо за общение, желаю всего наилучшего
                                    — Всё ок
                                    — Да, и если эти люди ещё вас побеспокоят дайте мне знать, хорошо?
                                    — Да
                                    (спустя время)
                                    — Прошу прощения, это снова Дэвид, проанализировав наш разговор я подумал что возможно вы всё же и правда являетесь ботом.
                                    — Да здравствует великий Мао!!!
                                    — Извините
                                    — всё ок
                                    — больше вас не побеспокою, надеюсь недоразумение исчерпано
                                    — да
                                    • 0
                                      В картинке где подписи к углам, want следует заменить на required %)
                                      • 0
                                        Считаю статью удачной. Она передает ощущение победы над возникшими проблемами в попытках превосходства над «простыми смертными».

                                        Раньше можно было обмануть запрет на 5 входов, не знаю как сейчас. План такой:
                                        1. Добавить в группу своего персонажа. Будем звать его номер 2.
                                        2. Зайти в подземелье персонажем номер 1. Поделать свои дела. Затем выйти.
                                        3. Отключить персонажа номер 1. Например alt+f4.
                                        4. Из окна персонажа номер 2 обновить подземелье.
                                        5. В окне персонажа номер 1 войти в игру. Затем с шага №2.
                                        6. Profit

                                        Вот простой пример работы аж с тремя персонажами сразу через Autoit
                                        #include <WinAPI.au3>
                                        
                                        AutoItSetOption("SendKeyDownDelay", 50)
                                        
                                        $WoWAppName = "World of Warcraft"
                                        $Title1 = 'honor VICTIM 1'
                                        $Title2 = 'honor VICTIM 2'
                                        $Title3 = 'honor VICTIM 3'
                                        
                                        WinSetTitle($Title1,"",$WoWAppName)
                                        WinSetTitle($Title2,"",$WoWAppName)
                                        WinSetTitle($Title3,"",$WoWAppName)
                                        WinWaitActive($WoWAppName)
                                        WinSetTitle($WoWAppName,"",$Title1)
                                        WinWaitActive($WoWAppName)
                                        WinSetTitle($WoWAppName,"",$Title2)
                                        WinWaitActive($WoWAppName)
                                        WinSetTitle($WoWAppName,"",$Title3)
                                        
                                        
                                        While 1
                                        
                                           ControlSend($Title1, "", "", "1")
                                           ControlSend($Title2, "", "", "1")
                                           ControlSend($Title3, "", "", "1")  
                                           sleep(500)
                                           
                                        WEnd
                                        
                                        • 0
                                          У меня работал рыболовный бот почти год — не забанили. Соответственно бота нигде не публиковал, в людных местах не ловил. Выше про Warden'а написали — не так страшен чёрт.
                                          • –1
                                            Люди, далекие от MMORPG игр, спросят: «А зачем вообще противодействовать ботам? Ведь автоматизация везде приветствуется.» А вот и нет. Если все платят одинаково, то и возможности должны быть равными. Иначе ущемленная часть обижается и перестает играть. Если в игре доступны, например, макросы, то они должны быть понятны и не программистам.

                                            Не пишите ерунды. Не программисты нынче знают lua? /script Зная lua скрипт, можно игровые деньги зарабатывать. Написал скрипт на закупку необходимых товаров (еда, ингредиенты, реагенты), прожал кнопку и закупился. Глава гильдии прожал кнопку и исключил всех из гильдии ниже 50го уровня, отсутствующих более месяца например. Это не автоматизация?
                                            Поэтому у Blizzard есть Warden. ПО по сути напоминает облачный антивирус:
                                            фоновые программы анализируются
                                            их метаданные отправляются в облако
                                            чит-аналитики принимают решение, что такой-то процесс нечестно взаимодействует с игрой
                                            к пользователям, использовавшим эти программы, применяются санкции

                                            Warden не анализирует фоновые программы. Никто не имеет права, получать список процессов с вашей машины без вашего согласия — это Неправомерный доступ к компьютерной информации Статья 272 УК РФ. С сервера приходят сигнатуры на определенных проверку участков памяти процесса игры и затем их значения отправляются на сервер. Так же анализируются библиотеки подключенные к игре. Но запомните, никто не имеет права, получать список процессов без вашего согласия.
                                            Я нередко встречал возгласы, что Blizzard (это разработчик WoW) плохо следит, и боты повсюду. Во-первых, сам я так не считаю, думаю, их доля преувеличена. А во-вторых, давайте обсудим, что же есть в арсенале разработчика. Как не только распознать бота, но еще и иметь подтверждающие факты. Ведь на основе подозрений невежливо банить игрока.

                                            Да, боты по всюду, зайди на БГ или полетай — покачай профессию и посмотри на их поведение. Человек не будет так двигаться, тупить, стандартный алгоритм выхода из застреваний. Банально, я наблюдал 5 минут, как бот за Альянс, пытался куда-то пройти мимо лагеря Орды в Когтистых горах, что тут сказать на 4ом спавне, я побежал дальше делать квесты, честно заработав 10 очков чести =)
                                            И да, я получил свой первый бан за Злоупотребление особенностями игровой механики в World of Warcraft
                                            • 0
                                              Зная lua скрипт, можно игровые деньги зарабатывать.

                                              Тут мне стало интересно.
                                              • 0
                                                На данный момент, заработал 5к золота, продавая скрипты. Как-то так.
                                                • 0
                                                  А скрипты какие? Для ботинга или чего?
                                                  • 0
                                                    Под заказ. Просят разное, от каста спеллов, до управления гильдиями
                                              • –1
                                                Согласно источнику, который я привел в статье
                                                Используя Warden Client, Blizzard столкнулась с обвинениями некоторых сторонников секретности. Так как Warden просматривает запущенные процессы, он может обнаружить и некоторые приватные данные, такие как адреса электронной почты, названия учётных записей в программах мгновенного общения и другую конфиденциальную информацию.

                                                В том же самом источнике Вы можете найти техническое описание Warden'а.

                                                Кроме того, Вы лично подтверждали следующее
                                                WHEN RUNNING, THE GAME MAY MONITOR YOUR COMPUTER'S RANDOM ACCESS MEMORY (RAM) AND/OR CPU PROCESSES FOR UNAUTHORIZED THIRD PARTY PROGRAMS RUNNING CONCURRENTLY WITH WORLD OF WARCRAFT


                                                Не пишите ерунды

                                                В настоящий момент, зная язык Lua, Вам не удастся «прожать кнопку и закупиться», потому что игра требует отдельного клика на каждую покупку.

                                                И я согласен с Вами, зная Lua, вы можете написать аддон и повысить удобство игры, а этим аддоном можете ни с кем не делиться.
                                                • –1
                                                  Ну во-первых, технического описания Warden — не существует в принципе, во-вторых, вы правильно заметили, что написано
                                                  PROGRAMS RUNNING CONCURRENTLY WITH WORLD OF WARCRAFT

                                                  Не просто процесс в памяти, а работающий совместно, т.е. речь идет о инъекции. Так что назови я отдельный процесс «Mega WoW Hack Cheat» — ничего не будет.
                                                  В настоящий момент, зная язык Lua, Вам не удастся «прожать кнопку и закупиться», потому что игра требует отдельного клика на каждую покупку.

                                                  Скажите это, вот этому скрипту:
                                                  /script local function buy (n,q) for i=1,200 do if n==GetMerchantItemInfo(i) then BuyMerchantItem(i,q) end end end buy("Формула: рунический адамантитовый жезл",1)
                                                  В свое время формула стоила больших денег и респавн был длинным… Ставишь рогу в инвизе у вендора на автоклик, и к утру у тебя штук 6 рецов… эх было время

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