Bloog Bot

Chapter 16
Drew Kestell, 2018
[email protected]

Runtime Reload

As we start to spend more time working with our state machine, it's going to get really annoying to have to close the client, compile our changes, restart the bot, and log back into the game every time we want to test a little change. We're going to fix that problem by reorganizing our solution in such a way that we'll be able to recompile our state machine and reload it into the WoW process while our bot is injected into the WoW client. We'll use the Managed Extensibility Framework (MEF) to accomplish this.

It's been a while since I showed the solution and project hierarchy, so before we start moving stuff around, here's a snapshot of what mine looks like at the beginning of this chapter:

We want to completely decouple our state machine from the core bot engine, so we're going to take everything from the AI folder and move it into a new project. Create a new .NET Framework class library and give it a name (mine is called WarriorBot), move all the files from BloogBot/AI into the new project, and update the namespaces appropriately. You'll also have to add a reference to the BloogBot project from the new WarriorBot project. You can delete all the files that you moved from BloogBot.

Now, we have a bit of a problem. We moved Bot.cs out of BloogBot, so the private variable of type Bot in our MainViewModel will break the project. We're going to solve that problem by creating an IBot interface in the BloogBot project, then have WarriorBot.Bot implement that interface. Then you can update the line in MainViewModel to use type IBot instead of Bot. BloogBot will provide the interface, and WarriorBot will provide the implementation. Here's the IBot interface:

public interface IBot
{
    void Start(ObjectManager objectManager, WoWEventHandler wowEventHandler);

    void Stop();
}

Now, instead of just instantiating a new Bot object from MainViewModel, we're going to have to do something different. This is where MEF comes into play. First we'll need to add a reference to System.ComponentModel.Composition because this isn't added to new .NET projects by default (you can do so by right clicking the project, add, reference, assemblies, framework). Now, in the WarriorBot project, open Bot.cs and add an Export attribute to the class declaration like so:

[Export(typeof(IBot))]
class Bot : IBot
{
  ...
}

Next, open the project properties for WarriorBot, go to the Build tab, and change the output path to ..\Bot\ to make sure the .dll is placed in the same folder as the rest of our output files.

That's it for WarriorBot. Now, create a new BotLoader class in the BloogBot project. It should look like this:

class BotLoader
{
    const string BOT_PATH = @"C:\Users\Drew\Repos\bloog-bot-v2\Bot\WarriorBot.dll";

    [Import(typeof(IBot))]
    IBot bot = null;

    AggregateCatalog catalog;
    CompositionContainer container;

    public BotLoader()
    {
        AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => Assembly.GetExecutingAssembly();
    }

    internal IBot ReloadBot()
    {
        var assembly = Assembly.Load(File.ReadAllBytes(BOT_PATH));
        catalog = new AggregateCatalog();
        catalog.Catalogs.Add(new AssemblyCatalog(assembly));
        container = new CompositionContainer(catalog);
        container.ComposeParts(this);

        return bot;
    }
}

The AggregateCatalog and CompositionContainer classes come from MEF. This is cool, because MEF handles wiring up the import and export types for us, and instantiating the IBot object that we're exporting from the WarriorBot project. If you're curious about the AggregateCatalog and CompositionContainer classes, you can read more about MEF online. I know very little about it beyond what I've learned to make this work. You'll also have to update the BOT_PATH string to point to the correct location on your machine.

The only other tricky piece here is this line:

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => Assembly.GetExecutingAssembly();

Without that line, you'll get an error that BloogBot.dll can't be found. And this makes sense considering the way we've injected BloogBot into an existing WoW process. AppDomain.CurrentDomain gets initialized from the WoW client folder, so it's not going to find any .dlls that we have in the output folder of our bot solution. This line basically says "when you fail to resolve an assembly, try to use the current executing assembly", which in this case is BloogBot. I think it's a bit weird how MEF handles this, but it's the best way I've found to get around the problem.

Next, we need to add a new Reload command (make sure you also add a button to MainWindow.xaml and wire it up with the ReloadBotCommand):

MainViewModel.cs:

ICommand reloadBotCommand;

void ReloadBot() => bot = botLoader.ReloadBot();

public ICommand ReloadBotCommand =>
    reloadBotCommand ?? (reloadBotCommand = new CommandHandler(ReloadBot, true));

And make sure you call ReloadBot from the constructor of MainViewModel so the bot gets properly initialized at startup. To test this out, modify the Bot.Start method in the WarriorBot project to just print some text to the Console.

public void Start(ObjectManager objectManager, WoWEventHandler wowEventHandler)
{
    Console.WriteLine("Bot version 1");
    //Running = true;
    //botStates.Push(new GrindState(botStates, objectManager, wowEventHandler));
    //StartInternal();
}

Fire up the bot, click the Start button, then modify the Start method to print something different, compile, click Reload, and click Start again. You should see the updated Start method.

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

Comments? Leave me a note: