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

в 5:17, , рубрики: autoit, bot, diy или сделай сам, game development, tutorial, world of warcraft, wow, вектор, перехват нажатия, робототехника, метки: , , , , , , , ,

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

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

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

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 поставляется AutoIt3ExtrasAu3RecordAu3Record.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. Направление персонажа смещается налево или направо. Причем, чем быстрее вы двигаете курсор, тем быстрее вертится персонаж

Как нам попроще определить, в какую сторону выгоднее поворачиваться, если у нас есть два угла: текущий и требуемый?
Создание несложного бота для WoW, программирование маршрутов (продолжение)
Можно посмотреть на разницу углов, если она положительна — направо. Но рассмотрим случай: текущий угол — 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) и смотрит под углом Создание несложного бота для WoW, программирование маршрутов (продолжение) и ему надо попасть в (bx, by), ему бежать вперед или назад? Для начала слегка перефразируем задачу: пусть персонаж стоит в (0, 0), смотрит в Создание несложного бота для WoW, программирование маршрутов (продолжение), а надо ему в (dx, dy).
Создание несложного бота для WoW, программирование маршрутов (продолжение)
Если решать задачу в лоб, то надо бы вычислить косинус угла между векторами. Если он положительный, то угол острый и бежать вперед. Если отрицательный — тупой, надо пятиться. Косинус можно вычислять по теореме косинусов
Создание несложного бота для WoW, программирование маршрутов (продолжение)
Но для этого нам придется вычислять длины каждого из векторов, извлекать корни, очень громоздко.

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

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

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

Реализация

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

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() возвращает массив из двух значений: сколько идти вперед/назад, сколько идти вбок.

Это поворот!

Некоторые читатели могут воскликнуть: «Да ведь эти умножения на синусы-косинусы — ничто иное, как обыкновенный поворот системы координат,
Создание несложного бота для WoW, программирование маршрутов (продолжение)
описываемый
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

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

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

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

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

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

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

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

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

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

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

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

Автор: AlexeyVanilov

Источник

Поделиться

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