Your info daily

Creating a simple bot for WoW, programming routes (continued)

This is the second part of a post on creating a simple bot for the game World of Warcraft. The first part can be found here . Today we talk about
  1. writing the Recorder 's keys and coordinates in AutoIt
  2. Writing Player 's instructions for the bot
  3. math 2D how to navigate in the Cartesian coordinate system without the cosine theorem
  4. control of the robot with an insufficient number of sensors
  5. bots countermeasures



Recorder


Our task: reading colors of pixels, determining keystrokes, background work with the ability to pause, sending clicks to the application. Here, the AutoIt language is the best fit. To write the same thing on C, you would have to bother with handles, devices, hooks, Windows events ... And, for each of these actions, you need to read a certificate, figure out a way, decide on structures, types, apishkami.

For editing the script and quick launch, I used SciTE-Lite , which includes Highlighting, CodeFolding, Autocomplete, Intellisense, and also SyntaxCheck. Built-in help for the language as a gift. Standard fishhechki.

Hotkeys

In every script that works with your desktop and is able to take control over it , I advise you to use a couple of shortcut handlers.

$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 

Otherwise, you risk falling into an observer state without the ability to control your system. Judgment Day.

I note that the F12 and some other buttons handler can not be hung. For some time I could not understand why he was not called.

Getting the coordinates

Let me remind you that in the addon we put floating-point numbers into the color components of the pixels, and they come to us in the form of whole bytes:

 #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 

By counting the data, we perform inverse transformations with them. Functions return arrays and arrays are written to variables completely transparent to the programmer.

Key logging

I read somewhere that the author of the AutoIt language did not want such a simple and powerful language to be used by attackers to write malware. Therefore, he removed the ability to create a click handler for all keys at once, so that at least Keylogger's would not rivet.

What can I say. AutoIt is very actively used to write malware, and we will do the following to intercept key presses:

 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 

Thus, we don’t need to invent our own key names to save them to a file, it’s enough to use AutoIT

If we need to intercept something else (and during the game we know in advance that we will crush and what spells to use), we simply add them to the long list at the beginning.

Having intercepted a keystroke, we need to send it further to the application, for this we temporarily remove our handler

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

This approach is recommended in the help, but in practice, when you click on a combination with Alt and others, it is noticeably buggy. I did not understand, but simply avoided the use of such combinations.

Mouse logging

In AutoIt there is no regular way to intercept mouse clicks. There is a third-party module with an example of use . Or you can hang your SetWindowsHookEx (WH_MOUSE_LL) . Cognitive example of using WinAPI Callback- functions on AutoIt here . But I did not use this approach for two reasons:
  1. The movement of the character is associated with a large number of mouse clicks that do not need to be logged. Plus, random clicks are possible. I would have to write logic on the separation of flies from cutlets.
  2. Using Hooks increases the chances of attracting the attention of "special services" to you. More on this in the section "Counter-bots".

Therefore, as you have already noticed, I just used the " [ " and " ] " buttons and pressed them as needed. The main thing is not to forget to press them.

Coordinate recording

And, of course, our Recorder should record the movement of the character in the background.

 $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 

You may ask, why write coordinates so often (10 times per second)? The fact is that on our route there are piled up obstacles: boxes, corners, doorways, lampposts, rakes . And the character just “magnet” to them. If somewhere, running past, he can get stuck, he will definitely do it. Even if you are running in a straight line for a long time, remember that the azimuth of motion was not perfectly set, therefore, perhaps you have been running for a long time, resting your forehead against the wall.
Full source code for the 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

Along with SciTE4AutoIt3 comes AutoIt3 \ Extras \ Au3Record \ Au3Record.exe , which allows you to record your actions with the desktop and executes it immediately in the form of an AutoIt script. If you have to perform a series of automatic actions many times without having to think (for example, when installing software or patches on a large fleet of computers), take a closer look at this tool.

Player


Let me remind you that the result of the work of Recorder 'a, described in the previous chapter, is a set of commands

  mouse left 1892 1021
 pause 10,000
 pitch -0.89
 mouse right 942 498
 pause 10,000
 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 

At first glance, it seems that “losing” them is not difficult, but let's take a closer look at this process.

Rotate and tilt

Turning and tilting by pressing the buttons gives an accuracy of about 30 degrees, which is unacceptable for our task. Therefore, we will use the second possibility for this: mouse movement with the right button held down. The algorithm is as follows:
  1. Hold the right mouse button
  2. The game itself will place the cursor on the center of the screen and will hold it there. This will allow the user not to end up with the cursor on the edge of the screen when turning.
  3. Shift the cursor to the left or right by a certain number of pixels.
  4. The direction of the character is shifted to the left or right. Moreover, the faster you move the cursor, the faster the character turns

How is it easier for us to determine in which direction it is more profitable to turn if we have two angles: the current and the required?

You can look at the difference in angles, if it is positive - to the right. But consider the case: the current angle is 30 degrees, the required angle is 330 degrees. The difference is negative, but we still right. And the angles can be negative. To avoid having to write out all these conditions, just use the sine of the difference of angles and look only at its sign.

The variables $ want and $ current contain (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 

Sine also allows us to adjust the speed of rotation. The smaller the angle, the more accurately our cursor will move and the more accurately we will turn. Conversely, at large angles, we want to turn quickly.

The function of changing the slope to the horizon is completely analogous, only the cursor must be shifted vertically.

In the example code, there is a logical error that can lead to incorrect behavior of the bot. I suggest that the curious reader find it himself.

Motion

The following requirements are imposed on the movement
  1. Positioning accuracy is low. You can not stand exactly where you want
  2. It is necessary to move without turning around. Of the available movements, there is only running forward, steps backward and strafe (moving sideways without turning)
  3. The movement should be continuous, without stopping and jerking
  4. As few as possible returns and changes of direction. It is unpleasant to look at the monitor, if the character ran a little, then backed away, then ran again, and so on


Forward or backward?

If you don’t like math or finished school a long time ago, you can skip this section without compromising understanding. But there is nothing particularly difficult here. Let the character stand on (a x , a y ) and look at an angle and he needs to get into (b x , b y ) , should he run forward or backward? To begin with, let's rephrase the task: let the character stand at (0, 0) , look at , and he needs in (d x , d y ) .

If you solve the problem in the forehead, then it would be necessary to calculate the cosine of the angle between the vectors. If it is positive, then the angle is sharp and run forward. If negative - stupid, you have to back up. Cosine can be calculated by the cosine theorem

But for this we have to calculate the length of each of the vectors, extract the roots, very cumbersome.

There is also a scalar product formula

Which, as is known , in the Cartesian coordinate system can be calculated by the formula

Thus, by calculating the scalar product and looking at its sign, we can determine whether we run forward or backward. And in our case, we will calculate it by the formula . Moreover, this same scalar product shows us exactly how much it is necessary to run forward or backward in its geometrical definition (the projection of one vector onto another multiplied by the length of the second).
image

Left or right?

Similarly, the sine of the angle between the gaze vectors and the direction to the target (d x , d y ) determines which side to go. If positive, target on the right hand, and vice versa. Here the vector product will help us, and to be more precise, the pseudoscalar product , calculated in Cartesian coordinates by the formula

Again, the value of this number determines how many steps to take sideways.

Implementation

Let's program our reasoning.

 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 

Do not be confused by the fact that in the program the sine and cosine are swapped and taken with the opposite sign. Just in WoW the azimuth is counted from the north counterclockwise. These details can simply be “tried” in a real program. And, if it happened the other way around, play around with the signs.

The GetDirection () function returns an array of two values: how much to go forward / backward, how much to go sideways.

This is a twist!

Some readers may exclaim: “Why, these multiplications by sine-cosines are nothing more than an ordinary rotation of the coordinate system ,

described
image
Why fool us heads. ”

True, but then it would be more difficult for me to explain why this is the matrix.

Stop

Now we are ready to push the running buttons

 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 

Pay attention, we go in the direction in which to go the furthest, and finish the way when we are almost at the goal. This allows the character not to flicker. The forward button does not release when the destination is reached. Therefore, we need to turn to the required angle relatively quickly.

The code responsible for playing the command files is not anything interesting, and if you wish, you can familiarize yourself with it.
Full source code for 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 


Control (ro) bot



That seems to be all. Run along the route, record the coordinates, keystrokes and clicks, and start playback. But it was not there. Here begins the most difficult.

In the dungeon live mobs , which, although they do not pose a threat to the life of our character, but strive to stun, throw "silent", run aside just when you would like to kill them, and rush to the embrasure at the moment you thought take on a completely different mob. Sometimes your goal runs behind your back, sometimes it dies in one stroke, although you have to live three. In general, their chaotic behavior introduces a noticeable element of chance into our route, and as a result, a character may be at the locked door at the very end of the route simply because he could not kill someone at the very beginning.

Yes, it was possible to write in-game macros that would take the desired mob as a target, and thus get rid of some problems, but I wanted to leave the bot as universal as possible.

Lattice doors. They need to click to open. And since they are lattices, you can click neatly into the hole by accident. And the character will beat his forehead against the door in an attempt to run further.

Thus, writing down the route, you open it in a notebook and debug it. You watch the character and in case of problems or possible problems “drive crutches” into the route: insert an additional spell to be faithful (suddenly the mob will survive), reach the point where the mob will be clearly visible and reach regardless of his desire to walk. You change the route, so as not to stumble over this unfortunate box. After 10 races, I came to the conclusion that it would be better if I simply killed all the spiders there, than they could then take revenge on me in one case out of 10 with their sudden spell. But, when the character is already running, you no longer want to run on your own legs, you just want to look at it.

I realized what it was like to launch a rover, program a robot vacuum cleaner, walk in complete darkness around the apartment or teach robots to play football. It is necessary to do everything reliably, with a margin. On Mars, the pits will be everywhere, and there will also be overhanging rocks and caves. In the apartment, the robot vacuum cleaner will face the wires and slippers everywhere, and there will be a cat who wants to play. In total darkness there will be corners, legs of chairs, and even knives that have fallen to the floor. Only by touch!

I admit, I completed only the minimum program stated in the last part. The bot still may not complete the task with a probability of 1%. So it is impossible to leave it without control for the night, it will get stuck.

Counter bots



People far from MMORPG games will be asked: “Why bother to resist bots at all? After all, automation is welcome everywhere. ”But not at all. If everyone pays the same , then the possibilities should be equal. Otherwise, the disadvantaged part is offended and stops playing. If macros are available in the game, for example, then they should be understandable and not for programmers. Therefore, the game developer prohibits the use of automation at the level of the license agreement and monitors.

I often met exclamations that Blizzard (this is a WoW developer) is not following well, and bots are everywhere. Firstly, I myself do not think so, I think their share is exaggerated. And secondly, let's discuss what is in the arsenal of the developer. How not only to recognize the bot, but also to have supporting facts. After all, on the basis of suspicion it is impolite to ban a player.

, - ( ) . , «» , : , , ( ), , — . , , , , «».

Blizzard Warden . :
  1. - , -
  2. , ,

, - Wine . Blizzard .

, , :
  1. , ,
  2. -

ban-wave .
AutoIt , , «» . Blizzard , , .

Conclusion


:
  1. ,

, , . 20 ( 5 ). , , . . , , , - .

, , . Good luck.

More posts:


All Posts