Creating a simple bot for WoW, programming routes

Introduction


I recently read a post in which the author talked about his bot for trading at auction in the game World of Warcraft. His character shuttled for short distances and performed specific actions strictly specified in the control program. Using his ideas, I decided to go a little further: let the character be able to run along the route lasting at least five minutes, while it should be possible to record the route and the actions performed from the outside, without resorting to changing the control program itself. What I described is a development of the original post , however, I tried to ensure that its reading was not compulsory.
In a post I will describe
  1. The process of creating a trivial addon for the Lua language WoW user interface
    • Serialization of data for transmission between parts of the system
    • Pixel color rendering
  2. AutoIt character management process
    • Program for recording actions
    • Program to play
    • Mat.calculation of the direction of movement
    • Why you should not resort to reading / writing process memory
  3. Management program (ro) bot with commands that are understandable to the non-programmer


A warning


I understand that the use of bots is prohibited by the Blizzard license agreement. This can lead to a permanent ban of the account. I would be upset to learn that someone, using the materials of this post, began to cash in, worsening the economy of the game and causing disapproval of other players. And yes, I understand that what I do is dishonest.

The purpose of the post is to tell about my experience and describe the difficulties that I encountered, to find out from you what could be done better.

Background


Collecting

People far from the gaming industry can skip this section without prejudice. In the game WoW there is such an aspect - collecting. As in numismatics / philately, the more you have, for example, mounted animals, the more pleasant you are. The process of obtaining is just as important. Some dragons require a dozen tasks to complete (“achievements”), some require entering the top 2% of players in the world in terms of character control skills (arena battles), you can simply buy something for the game currency, which then - for real money in the store. Details can be read in another source., this is not about that. So, some of the collectible items fall into the dungeon with a chance of about 0.5%. If the chance of falling is given to the player only once a day (sometimes a week), you will need time comparable with the year for the races. And all for the sake of that moment of receiving the treasured suffered object. The more time and effort you spent on " farming " an item, the more pleasant you will get it in the end. In my experience, joy is very fleeting.

Dependence of the chance of falling on the number of attempts

A small digression. What is the probability that “6” will be dropped in six dice rolls? Obviously, that . That is, our event is the opposite event to the failure of “6” for all six attempts.
Using the second remarkable limit , it is easy to show that .

And the probability of getting a horse with a 1% drop chance in 100 attempts is approximately 63%.

Some players, having made three hundred attempts, believe that they are about to be lucky - because the probability of a break-in for such a large number of attempts is small. They will be disappointed, because over the next 100 they will be lucky again only 63%. And the old works give nothing.

Task

There is a dungeon. You need to run along it with turns, perform a couple of simple tasks along the way. Kill the final boss and raise the loot. In order not to run to the exit on foot, teleport to the nearest village and fly to the entrance. Repeat 5 times.

Dungeon map and route:


The maximum challenge. Launched and left for half an hour. The process is fully automatic.

Minimum task. Run and sit next to, read or play the guitar, looking at the monitor. If necessary, occasionally help the character get out of an unforeseen situation.

It is important to be able to optionally describe the route of another dungeon without getting into the code.

An approach


Movement and positioning

Simply recording keystrokes and release keys, position and mouse clicks will not work. More precisely, it will turn out to record, only when playing it will not happen at all what you expected, for the following reasons:
  1. You need to reproduce the initial position of the character and the direction of his gaze. If you turn slightly in the opposite direction before an automatic race, you will run crookedly and sooner or later run into a wall
  2. If you wrote down the exact coordinates and angle, then you will not succeed in putting the character there either. It responds to buttons like a bulldozer and rotates in the same way. And even an error of 1 degree after 100 meters will result in meters (width of the doorway)
  3. The time to press and release the buttons is also far from ideal. It is like walking around an apartment with your eyes closed. It seems to be six steps forward and strictly to the right, but in fact, instead of the toilet, we find ourselves in the bathroom. Try also to open the door with your eyes closed without feeling the handle.

For these reasons, we need to position ourselves in the world using coordinates. At the same time, it is necessary to check the position and make adjustments regularly, every 100 milliseconds.

move 40.644 20.417 1.537
move 40.526 20.411 1.537
move 40.246 20.408 1.592
move 39.974 20.414 1.592
move 39.691 20.411 1.537
move 39.417 20.388 1.510

This is an excerpt from the list of robot movement commands. The x , y coordinates and the rotation angle in space in radians are indicated .

Keys and Commands

On the way, you need to press buttons and click with the mouse, open the doors, somewhere you need to wait. I would not want to write it all out manually. And to look among the teams with coordinates (in the example above) where to enter what is not very pleasant. Therefore, one-time clicks on buttons and clicks, we will write in parallel with the recording of coordinates.

pitch -0.89
mouse right 942 498
pause 10000
move 39.417 20.388 1.510
key `
pause 1000
key {SPACE}
move 39.142 20.364 1.510
key {TAB}
key 3
key 3
mouse right 963 622
move 38.864 20.340 1.510
move 38.653 20.321 1.510

I’ll separately mention the “pitch” (slope). If the character is looking at the horizon, it is zero. If under the legs, the slope is negative. And in the sky - positive. Measured in radians. He had to be added as soon as the character wanted to fly.

Modules

Thus, parts of the bot hardware and software complex are looming :
  1. We will write addon for the WoW interface , which will determine the character's position, azimuth, inclination. The rules of the game add-ons are not prohibited, the specified information is available through the game API inside the add-on. He will draw it all on the screen. The addon itself is written in the Lua language, because the developers of the game so decided.
  2. In the addon you can define something, you can draw, but you can’t make the character move. Even in the add-on, you can’t write to a file, ask the game where the walls are, where the enemies are, and a lot of things still can’t. Therefore, we need to write an external control application . I did not become original and used AutoIt . It is really fast and convenient, albeit a little wild for me, who are used to programming in strongly typed languages .
    • The application should be able to record my actions. Call this functionality Recorder
    • The application should be able to play the recording. Call Player
  3. The recorded actions and coordinates will be stored in a text file line by line. They can and should be edited manually. Add crutches, remove debris. I will say right away that this is the most difficult.


Addon for WoW on Lua


Addon for WoW is a script in the Lua language . The language is full, flexible, effective. You can’t do anything with the system. You can do everything with the game, but only within the framework of the API that the game provides you. Since Blizzard does not want, or rather, prohibits players and entrepreneurs from writing bots, the API, in addition to the coordinates mentioned above, provides almost nothing useful. Of course, useful for our needs. A good guide to writing add-ons immediately with a description of Lua can be found here . Well, I will describe my option.

Manifest and GUI

Details and source . In the World of Warcraft \ Interface \ AddOns \ folder, create the HelloWorld folder . Put HelloWorld.toc in it with the contents

## Interface: 50001
## Title: Hello, World!
## Notes: My first AddOn
HelloWorld.lua

If I wanted to draw shapes and buttons (and addons are usually needed for this), I would write HelloWorld.xml with the description of these buttons as the last line . AddOn Studio helps with the design and writing of such a GUI addon . Powerful tool based on MS Visual Studio.

But since I am minimalist, we will not draw this time. In addition, this will give me the opportunity to show that you can create forms dynamically from the script itself without using a designer. Consider HelloWorld.lua . I edited it in Notepad ++. In order for the game to incorporate changes in HelloWorld.toc , you must restart it entirely. But the changes in HelloWorld.lua can be caught by writing a command in the console/ reload . Therefore, the programming and debugging process is not so painful.

Slash commands

By the way, about debugging. I advise you to immediately add a slash command handler :

SLASH_HELLO_WORLD1 = '/helloworld';
functionSlashCmdList.HELLO_WORLD(msg, editbox)local facing = GetPlayerFacing();
	local pitch = GetUnitPitch("player");
	local x, y = GetPlayerMapPosition("player");
	print(format("HelloWorld %.2f %.2f %.2f %.2f", x*100, y*100, facing, pitch)); 
end

To do this, define a variable of the form SLASH_IMЯn . Where the NAME is unique for all add-ons, and n is either empty or an ordinal number from 1. And we add a function with the name NAME to some object. It may seem strange to a C ++ programmer that we are not explicitly registering this handler function anywhere. Yes, and we associate with a string variable purely by the name of the variable. But here it is, the power and magic of Lua.

Now the team in the WoW console / helloworld will perform the actions you need: it will allow you to display debugging information, and for the simplest add-ons, in fact, it will do everything for which you wrote them.

Well, then I showed the WoW API, which pulls out the required information.

Event handler

The system for working with the GUI in the addon is similar to the work of Windows with its messages and their processing.

local EventFrame = CreateFrame("Frame")
functionEventFrame:OnEvent(event, ...)print("HelloWorld:", event)
	self[event](self, ...) 
end
EventFrame:SetScript("OnEvent", EventFrame.OnEvent)
EventFrame:RegisterEvent("PLAYER_LOGIN")
functionEventFrame:PLAYER_LOGIN()-- Инициализация тутendfunctionEventFrame:OnUpdate()-- Полезные действия тутend
EventFrame:SetScript("OnUpdate", EventFrame.OnUpdate)

We create a frame of type " Frame " and connect to the actions " OnEvent " and " OnUpdate " with commands

EventFrame:SetScript("OnEvent", EventFrame.OnEvent)
EventFrame:SetScript("OnUpdate", EventFrame.OnUpdate)

The OnUpdate handler will be called after each frame - what we need to update the coordinates. And OnEvent will be called at other desired events. From it we will pull the corresponding functions:

self[event](self, ...)

What’s even more convenient in Lua is that with this design you can call unique handlers of the form

functionEventFrame:PLAYER_LOGIN()

Here PLAYER_LOGIN is an event that is sent to all frames when they enter the world and restart the user interface. Other events: PLAYER_LEAVE_COMBAT , QUEST_FINISHED , PLAYER_EQUIPMENT_CHANGED , PLAYER_DEAD , and many more. The full list can be found here . Register the event that we want to handle with the command

EventFrame:RegisterEvent("PLAYER_LOGIN")


Information transfer

Since you can’t do anything with the system in the addon, we will transmit information from WoW to another part of the bot by changing the color of the pixels. As rednaxi did in his post . But only we will transmit more than one bit of information in color, but we will serialize the data and transmit more of it.

Drawing

Since frames are also needed for drawing, create them

local HelloWorld1 = CreateFrame("Frame", nil, UIParent) 
local HelloWorld2 = CreateFrame("Frame", nil, UIParent)
functionEventFrame:PLAYER_LOGIN() 
	HelloWorld1:SetFrameStrata("BACKGROUND")
	HelloWorld1:SetWidth(10) 
	HelloWorld1:SetHeight(10) 
	HelloWorld1.texture = HelloWorld1:CreateTexture(nil,"BACKGROUND")
	HelloWorld1.texture:SetAllPoints(HelloWorld1)
	HelloWorld1:SetPoint("TOPLEFT",0,0)
	HelloWorld1:Show()
	HelloWorld2:SetFrameStrata("BACKGROUND")
	HelloWorld2:SetWidth(10) 
	HelloWorld2:SetHeight(10) 
	HelloWorld2.texture = HelloWorld2:CreateTexture(nil,"BACKGROUND")
	HelloWorld2.texture:SetAllPoints(HelloWorld2)
	HelloWorld2:SetPoint("TOPLEFT",10,0)
	HelloWorld2:Show()
end

I think everything is clear here and without comment. Look for details for each method yourself.

Serialization

We have two coordinates and two angles. All are floating point numbers. The components of the colors of pixels in the addon are also floating point numbers, but from 0.0 to 1.0 . In addition, it is known that the color component will remain in one byte. At first, I saved each coordinate in one color component. As a result, the positioning accuracy in the location was 1/255 of the size of the map. It turned out like in GPS : there seems to be a coordinate, but driving a car on a machine (controlling a computer without a person) on the road through the navigator will not work. So I had to give two bytes. How is it better to keep one fractional number in two bytes? I did so

local x1, x2 = math.modf(x*255)

The modf function returns the integer and fractional parts of a number, separated by commas. Parallel assignment is used here - another useful Lua feature.

With this approach, I use the full power of each of the two bytes into which the coordinate is stored. Well, the azimuth and slope are not so exacting to accuracy, if only they fit into the interval 0.0-1.0 . It turns out like this:

localmath = getfenv(0).mathfunctionEventFrame:OnUpdate()local facing = GetPlayerFacing();
	local pitch = GetUnitPitch("player");
	local x, y = GetPlayerMapPosition("player");
	local x1, x2 = math.modf(x*255)
	local y1, y2 = math.modf(y*255)
	HelloWorld1.texture:SetTexture(x1/255, x2, facing/7)
	HelloWorld2.texture:SetTexture(y1/255, y2, pitch/4+0.5)
end

Now, when moving around the world in the upper left corner, two 10 by 10 squares will randomly change their color.
Full text HelloWorld.lua
localmath = getfenv(0).math
SLASH_HELLO_WORLD1 = '/helloworld';
local EventFrame = CreateFrame("Frame") 
local HelloWorld1 = CreateFrame("Frame", nil, UIParent) 
local HelloWorld2 = CreateFrame("Frame", nil, UIParent) 
functionEventFrame:OnEvent(event, ...)print("HelloWorld:", event)
	self[event](self, ...) 
end
EventFrame:SetScript("OnEvent", EventFrame.OnEvent)
EventFrame:RegisterEvent("PLAYER_LOGIN")
functionEventFrame:PLAYER_LOGIN() 
	HelloWorld1:SetFrameStrata("BACKGROUND")
	HelloWorld1:SetWidth(10) 
	HelloWorld1:SetHeight(10) 
	HelloWorld1.texture = HelloWorld1:CreateTexture(nil,"BACKGROUND")
	HelloWorld1.texture:SetAllPoints(HelloWorld1)
	HelloWorld1:SetPoint("TOPLEFT",0,0)
	HelloWorld1:Show()
	HelloWorld2:SetFrameStrata("BACKGROUND")
	HelloWorld2:SetWidth(10) 
	HelloWorld2:SetHeight(10) 
	HelloWorld2.texture = HelloWorld2:CreateTexture(nil,"BACKGROUND")
	HelloWorld2.texture:SetAllPoints(HelloWorld2)
	HelloWorld2:SetPoint("TOPLEFT",10,0)
	HelloWorld2:Show()
endfunctionEventFrame:OnUpdate()local facing = GetPlayerFacing();
	local pitch = GetUnitPitch("player");
	local x, y = GetPlayerMapPosition("player");
	local x1, x2 = math.modf(x*255)
	local y1, y2 = math.modf(y*255)
	HelloWorld1.texture:SetTexture(x1/255, x2, facing/7)
	HelloWorld2.texture:SetTexture(y1/255, y2, pitch/4+0.5)
end
EventFrame:SetScript("OnUpdate", EventFrame.OnUpdate)
functionSlashCmdList.HELLO_WORLD(msg, editbox)local facing = GetPlayerFacing();
	local pitch = GetUnitPitch("player");
	local x, y = GetPlayerMapPosition("player");
	print(format("HelloWorld %.2f %.2f %.2f %.2f", x*100, y*100, facing, pitch)); 
end



Conclusion of the first part


Repeat what was discussed
  1. We calculated the probability of obtaining an item for a large number of attempts
  2. Outlined the problem, developed an approach to the solution, divided into modules
  3. We reviewed the simplest WoW add-on code in Lua
    • learned to transmit coordinates using color
    • learned to draw, handle events
    • learned to process slash commands

    Now you are ready to write your addon.

And if you are developing something and want to enable yourself or users to participate, flexibly customize the software for yourself using your API (for example, write artificial intelligence of opponents, allow brokers to place positions based on quotes, and allow administrators to perform their actions according to the results inventory of user systems, etc.), you know, Lua is very flexible and very easy to integrate. Consider such an opportunity.

Well, in the next part we will talk about
  1. writing a Recorder of keys and coordinates in AutoIt
  2. writing Player'a instructions for the bot
  3. math 2D, how to navigate the Cartesian coordinate system without the cosine theorem
  4. robot control with insufficient sensors
  5. counter measures to bots

Modification from 08/14
Added a link to the second part.

Also popular now: