Bloog Bot

Chapter 9
Drew Kestell, 2018
[email protected]

Position

The next thing we're going to do is implement the Player object type. A Player is a specific kind of Unit, so create a WoWPlayer class that inherits from the WoWUnit class:

class WoWPlayer : WoWUnit
{
    readonly MemoryReader memoryReader;

    internal WoWPlayer(MemoryReader memoryReader, IntPtr pointer, ulong guid, ObjectType objectType)
        : base(memoryReader, pointer, guid, objectType)
    {
        this.memoryReader = memoryReader;
    }
}

We're also going to have to modify the Callback of EnumerateVisibleObjects in the ObjectManager class to handle this new object type. Recall that EnumerateVisibleObjects will call Callback internally, passing in the filter parameter, and the guid of the object. Thankfully, the guid for the player will always come back as 1, so we can look for that to easily find our player. First add a property on the ObjectManager class to store a reference to the local player:

internal WoWPlayer Player { get; private set; }

Then modify the Callback method to look for an object with a guid equal to 1, and set the Player parameter:

int Callback(int filter, ulong guid)
{
    var pointer = functions.GetObjectPtr(guid);
    var objectType = (ObjectType)memoryReader.ReadByte(
        IntPtr.Add(pointer, 
        OBJECT_TYPE_OFFSET)
    );
    
    switch (objectType)
    {
        case ObjectType.Player:
            var player = new WoWPlayer(memoryReader, pointer, guid, objectType);
            if (guid == 1)
                Player = player;
            Objects.Add(player);
            break;
        case ObjectType.Unit:
            Objects.Add(new WoWUnit(memoryReader, pointer, guid, objectType));
            break;
        default:
            Objects.Add(new WoWObject(pointer, guid, objectType));
            break;
    }
    
    return 1;
}

There's one quirk that we need to fix related to the Name property on the WoWUnit class. We access a Player's name in a different way. So we're going to modify the Name property on the WoWUnit class to make it virtual, then we'll override that method in the WoWPlayer class. I haven't looked too closely at this section of the decompiled code from the WoW client, but I assume all player names are stored in a specific area of memory, and non-player units' names are stored elsewhere. We have the base address of that player name collection. We also know the offset between names. So we start at the base address, then walk the collection until we find the guid of the Player, then we read a string from memory at that location. Here's the code for the WoWPlayer class:

class WoWPlayer : WoWUnit
{
    // NAME
    const int NAME_BASE_OFFSET = 0xC0E230;
    const int NEXT_NAME_OFFSET = 0xC;
    const int PLAYER_NAME_OFFSET = 0x14;

    readonly MemoryReader memoryReader;

    internal WoWPlayer(MemoryReader memoryReader, IntPtr pointer, ulong guid, ObjectType objectType)
        : base(memoryReader, pointer, guid, objectType)
    {
        this.memoryReader = memoryReader;
    }

    internal override string Name
    {
        get
        {
            var namePtr = memoryReader.ReadIntPtr((IntPtr)NAME_BASE_OFFSET);
            while (true)
            {
                var nextGuid = memoryReader.ReadUlong(IntPtr.Add(namePtr, NEXT_NAME_OFFSET));

                if (nextGuid != Guid)
                    namePtr = memoryReader.ReadIntPtr(namePtr);
                else
                    break;
            }
            return memoryReader.ReadString(IntPtr.Add(namePtr, PLAYER_NAME_OFFSET));
        }
    }
}

Next we're going to start talking about positions in 3D space. It's very common in 3D games to use a 3D coordinate system to store the relative position of objects in the world. A basic understanding of geometry is necessary before jumping into the operations we'll be making in 3D coordinate space, so if this is new to you, take a look at this article.

Our bot needs to be able to move around, so eventually we're going to need to know the player's position in 3D space, as well as the position of all units around the player. Let's do that now. Unlike the Name property, both WoWUnit and WoWPlayer find their positions in the same relative place in memory, so we should add the new code to the base WoWUnit class. We'll most often be interested in all three dimensions at the same time (x, y, and z), so instead of implementing separate properties for each, let's create a Position class to encapsulate all three dimensions. Let's also have the Position class override the ToString() method so we can easily print a unit's position for debugging purposes. Here's the code::

class Position
{
    internal Position(float x, float y, float z)
    {
        X = x;
        Y = y;
        Z = z;
    }

    internal float X { get; }
    
    internal float Y { get; }

    internal float Z { get; }

    public override string ToString() => $"X: {X}, Y: {Y}, Z: {Z}";
}

This is our first time reading a float from memory, so we'll need a new method on the MemoryReader class:

internal float ReadFloat(IntPtr address) => *(float*)address;

Finding the x, y, and z coordinates of a unit is actually quite simple. Each coordinate is simply stored at an offset relative to the base address of the unit:

class WoWUnit : WoWObject
{
    ...

    const int POS_X_OFFSET = 0x9B8;
    const int POS_Y_OFFSET = 0x9BC;
    const int POS_Z_OFFSET = 0x9C0;

    readonly MemoryReader memoryReader;

    internal WoWUnit(MemoryReader memoryReader, IntPtr pointer, ulong guid, ObjectType objectType)
        : base(pointer, guid, objectType)
    {
        this.memoryReader = memoryReader;
    }

    ...

    internal Position Position
    {
        get
        {
            var x = memoryReader.ReadFloat(IntPtr.Add(Pointer, POS_X_OFFSET));
            var y = memoryReader.ReadFloat(IntPtr.Add(Pointer, POS_Y_OFFSET));
            var z = memoryReader.ReadFloat(IntPtr.Add(Pointer, POS_Z_OFFSET));

            return new Position(x, y, z);
        }
    }
}

Like usual, let's test this by modifying our Test method in the UI to print the position of all units. You'll see something like this:

Now that we know the player's position, and the positions of all units around the player, we can perform all sorts of useful calculations. A simple example would be finding the unit closest to the player. First, let's make a quick fix to the ObjectManager class. Consider the following code:

internal IEnumerable<WoWUnit> Units => Objects.OfType<WoWUnit>();

The Units property is actually going to return units AND players, because a WoWPlayer is actually a specific type of WoWUnit. It would be more useful if we could get ONLY units, excluding the players. Let's change this property to be more specific (notice we need to to call ToList() here - the way LINQ does deferred execution here will screw up the cast from WoWObject to WoWUnit without it):

internal IEnumerable<WoWUnit> Units => Objects.OfType<WoWUnit>().Where(o => o.ObjectType == ObjectType.Unit).ToList();

So, in order to find the closest NPC to the player, we first need the ability to determine the distance between two positions. Let's make that it's own method on the Position class. Most people are probably familiar with using the pythagorean theorem to find the length of the hypotenuse of a triangle in 2D space. The technique is pretty much the same for 3D space. First we're going to find the deltas of all three coordinates, then we'll apply the pythagorean theorem.

internal float DistanceTo(Position position)
{
    var deltaX = X - position.X;
    var deltaY = Y - position.Y;
    var deltaZ = Z - position.Z;

    return (float)Math.Sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ);
}

Now that we can find the distance between two positions, in order to determine the closest unit to the player, we can simply order the units by distance to the player in ascending order, then take the first element. Let's test it:

void Test()
{
    objectManager.EnumerateVisibleObjects();

    var player = objectManager.Player;

    var units = objectManager.Units;
    var closestUnit = units.OrderBy(u => u.Position.DistanceTo(player.Position)).First();

    Log($"Closest unit to player: {closestUnit.Name}");
    Log($"Distance: {closestUnit.Position.DistanceTo(player.Position)}");
}

Fire up your client and give it a go. You should see something like:

Considering a bot spends most of its time running between enemies and laying waste to them, being able to find the nearest enemy is a fundamental piece of behavior. In the next chapter we'll see how to make the bot move.

Back to Top
...
Subscribe to the RSS feed to keep up with new chapters as they're released

Comments? Leave me a note: