How To Write Plugins For Arena Helper

June 15, 2015 in Articles »C#PluginTutorial

Since I have released the plugin Arena Helper I got a lot of requests if I could add support for website APIs. There are websites that try to help the Hearthstone player to create a deck of cards that has the most value. These sites don’t always integrate with Hearthstone itself, like Arena Helper does. Without integration and automatic card detection, these sites are a pain to use. To solve this problem, I have created a plugin system within the plugin. Anyone can create a plugin that can use the detection algorithms of Arena Helper with the ability to retrieve the value of the cards from other sources than the default tier list.

In this tutorial, we will be creating a fully working plugin for Arena Helper using Visual Studio and C#. If you are a site owner that has an API or if you want to create a website that integrates with Hearthstone to automatically detect the Arena cards and present a value and possible advice to the player, this tutorial will explain how to do this.

If you have any issues, please use the GitHub Issues page. I can only support public APIs that are explicitly allowed to be used by third-party applications.

Getting The Source Code

Go to the Arena Helper project page on GitHub and clone the project to your computer or download the latest source code from the releases page. The project was made using Visual Studio Express 2013. You can use any compatible version of Visual Studio, like the Community edition. After opening the solution file ArenaHelper.sln, you will see two projects: Arena Helper and TestPlugin. Before you can build the TestPlugin project, you need to add some references. The missing references can be seen by expanding the References item in the Solution Explorer. Most likely, you will need to add a reference to the latest versions of ArenaHelper.dll and Hearthstone Deck Tracker.exe. These files can be found in the Hearthstone Deck Tracker installation directory and the Arena Helper plugin directory, if both are installed correctly. How to install the Arena Helper plugin, can be found on the Arena Helper project page on GitHub.

Add Reference

Add Reference

If you have added the references, you can build the TestPlugin, by right-clicking on it in the Solution Explorer and choosing Build. You can also try to build the entire solution, but you will need to add more references and a NuGet package to enable compilation of Arena Helper itself. If compilation succeeded, you can find the compiled plugin in the TestPlugin\bin\Debug or in the TestPlugin\bin\Release directory. It will be named TestPlugin.dll. To use this dll in Arena Helper, place it inside the plugins directory of Arena Helper: Hearthstone Deck Tracker\Plugins\ArenaHelper\plugins\. There can only be one active plugin at a time.

Build TestPlugin

Build TestPlugin

You can see the result of the Test Plugin by starting a new Hearthstone Arena run and opening Arena Helper. The plugin returns modified card values and an advice. The screenshot below shows in the overlay that the plugin added a prefix “p” to the default values. The plugin also shows an advice.

Arena Helper Test Plugin

Implementing The Plugin

A plugin should implement the abstract properties Name, Author and Version. Implementing the virtual functions is optional. If you want a plugin with basic functionality, the only function you have to implement is the GetCardValues function. The code below is the base class for a plugin. Every plugin should derive from this base class and implement the abstract properties.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
namespace AHPlugins
{
    public abstract class AHPlugin
    {
        public abstract string Name { get; }
        public abstract string Author { get; }
        public abstract Version Version { get; }
 
        // All virtual functions are optional
 
        // Called when three new cards are detected
        // arenadata: The previously detected cards, picked cards and heroes
        // newcards: List of 3 detected cards
        // defaultvalues: List of 3 tier values for the detected cards
        // Return a list of 3 card values and an optional 4th advice value
        public virtual async Task<List<string>> GetCardValues(ArenaHelper.Plugin.ArenaData arenadata,
                                                              List<Card> newcards,
                                                              List<string> defaultvalues) { return null; }
 
        // Called when a new arena is started
        // arendata: As before
        public virtual async void NewArena(ArenaHelper.Plugin.ArenaData arenadata) { }
 
        // Called when the heroes are detected
        // arendata: As before
        // heroname0: name of hero 0
        // heroname1: name of hero 1
        // heroname2: name of hero 2
        public virtual async void HeroesDetected(ArenaHelper.Plugin.ArenaData arenadata,
                                                 string heroname0, string heroname1, string heroname2) { }
 
        // Called when a hero is picked
        // arendata: As before
        // heroname: name of the hero
        public virtual async void HeroPicked(ArenaHelper.Plugin.ArenaData arenadata, string heroname) { }
 
        // Called when the cards are detected
        // arendata: As before
        // card0: card 0
        // card1: card 1
        // card2: card 2
        public virtual async void CardsDetected(ArenaHelper.Plugin.ArenaData arenadata,
                                                Card card0, Card card1, Card card2) { }
 
        // Called when a card is picked
        // arendata: As before
        // pickindex: index of the picked card in the range -1 to 2, if -1, no valid pick was detected
        // card: card information, null if invalid card
        public virtual async void CardPicked(ArenaHelper.Plugin.ArenaData arenadata,
                                             int pickindex, Card card) { }
 
        // Called when all cards are picked
        // arendata: As before
        public virtual async void Done(ArenaHelper.Plugin.ArenaData arenadata) { }
 
        // Called when Arena Helper window is opened
        // arendata: As before
        // state: the current state of Arena Helper
        public virtual async void ResumeArena(ArenaHelper.Plugin.ArenaData arenadata,
                                              ArenaHelper.Plugin.PluginState state) { }
 
        // Called when Arena Helper window is closed
        // arendata: As before
        // state: the current state of Arena Helper
        public virtual async void CloseArena(ArenaHelper.Plugin.ArenaData arenadata,
                                             ArenaHelper.Plugin.PluginState state) { }
    }
}

The ArenaData class contains information of the current arena run.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ArenaData
{
    public string deckname;
    public string deckguid;
    public List<string> detectedheroes;
    public string pickedhero;
    public List<Tuple<string, string, string>> detectedcards;
    public List<string> pickedcards;
 
    public ArenaData()
    {
        deckname = "";
        deckguid = "";
        detectedheroes = new List<string>();
        pickedhero = "";
        detectedcards = new List<Tuple<string, string, string>>();
        pickedcards = new List<string>();
    }
}

Lets implement the GetCardValues function to create a basic TestPlugin that returns card values and an advice.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public override async Task<List<string>> GetCardValues(ArenaHelper.Plugin.ArenaData arenadata,
                                                       List<Card> newcards,
                                                       List<string> defaultvalues)
{
    List<string> values = new List<string>();
 
    // Add a test delay to simulate an API call
    await Task.Delay(1000);
 
    // Add the three card values
    for (int i = 0; i < 3; i++)
    {
        // Add the prefix "p" to the default values as a test
        values.Add("p" + defaultvalues[i]);
    }
 
    // Optionally add an advice as a 4th list element
    values.Add("I don't know, pick one!");
 
    return values;
}

Plugins that want to talk to site APIs could use something like WebClient.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System.Net;
 
// (...)
 
private async Task<string> MakeApiCall(string param)
{
    // Create a web client and specify a user agent
    WebClient webclient = new WebClient();
    string useragent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20130401 Firefox/31.0";
    webclient.Headers.Add(HttpRequestHeader.UserAgent, useragent);
 
    // Make the call
    string page = await webclient.DownloadStringTaskAsync("https://example.com/api/" + param);
 
    // Return the results
    return page;
}

Browse the source of the Test Plugin to see what is possible. You can access public functions of Arena Helper by using ArenaHelper.Plugin. The following example shows some of the functionality you can access.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Called when Arena Helper window is opened
// arendata: As before
// state: the current state of Arena Helper
public override async void ResumeArena(ArenaHelper.Plugin.ArenaData arenadata,
                                       ArenaHelper.Plugin.PluginState state)
{
    Logger.WriteLine("Resuming Arena");
    foreach (var cardid in arenadata.pickedcards)
    {
        Card card = ArenaHelper.Plugin.GetCard(cardid);
        string cardname = "-";
        if (card != null)
        {
            cardname = card.Name;
        }
        Logger.WriteLine(cardname);
    }
 
    foreach (var heroname in arenadata.detectedheroes)
    {
        ArenaHelper.Plugin.HeroHashData hero = ArenaHelper.Plugin.GetHero(heroname);
        Logger.WriteLine("Detected hero: " + hero.name);
    }
 
    if (arenadata.pickedhero != "")
    {
        ArenaHelper.Plugin.HeroHashData hero = ArenaHelper.Plugin.GetHero(arenadata.pickedhero);
        Logger.WriteLine("Picked hero: " + hero.name);
    }
 
    Logger.WriteLine("State: " + ArenaHelper.Plugin.GetState().ToString());
}

As stated before, if you encounter issues or problems, please visit the GitHub Issues page and ask for help.