Skip to content
Beiren Xie edited this page Oct 3, 2013 · 10 revisions

This will be the place to store relevant examples for newcomers to learn about Skadi. All of these scripts require a demo file path, and some require some common scripts/variables that were developed as well. Current versions of these scripts, and the common items can be found at https://github.com/garth5689/skadi/tree/explore/explore. If you have questions, ask in IRC. Enjoy!

Scripts

Total Gold Earned
Pudge Hooks
Print Neutrals
Buyback Cooldown
Total Distance Traveled
Midas Efficiency
Hero Attributes
Team Convex Hulls

##Total Gold Earned back to top
The following example shows you how to plot each player's total gold earned vs. game time

from matplotlib import pyplot as plt
from common import DEMO_FILE_PATH, PLAYER_COLORS
from skadi import demo as d


def main():
    # first, construct the demo file so Skadi can access the information
    game = d.construct(DEMO_FILE_PATH)

    total_gold_earned = {}
    game_time = []
    player_names = []

    # This loop gets all the player names from the file info.  This information
    # could also be obtained through the DT_DOTA_PlayerResource.
    # This will also create dict keys using the player names.
    for player in game.file_info.game_info.dota.player_info:
        name = player.player_name.encode('UTF-8')
        player_names.append(name)
        total_gold_earned[name] = []

    # the tick specifies a certain point in the replay.  In this loop, a stream
    # is created to loop through the replay and grab the gold values throughout.

    for tick, user_messages, game_events, world, modifiers in game.stream(tick=0):

        # Here we access the respective DTs.  A DT is a dict where information
        # is stored.  In the DT_DOTAGamerulesProxy, we can find meta information
        # about the game.  This is how game time is calcluated.  The
        # DT_DOTA_PlayerResource contains the players gold totals.  The ehandle
        # is a replay-wide unique identifier to relate different values.

        players_ehandle, players_state = world.find_by_dt('DT_DOTA_PlayerResource')
        rules_ehandle, rules_state = world.find_by_dt('DT_DOTAGamerulesProxy')

        # Wait until game has started
        if rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')] != 0.0:

            # Calculate game time
            time = rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_fGameTime')] \
                   - rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')]
            game_time.append(time / 60)

            # Append each player's gold value to the dict.  The DT_EndScore...
            # is the key we use to find each player's gold value.
            for number, player in enumerate(player_names):
                player_num = str(number).zfill(4)
                player_gold_DT = ('DT_EndScoreAndSpectatorStats', 'm_iTotalEarnedGold.{ID}'.format(ID=player_num))
                total_gold_earned[player].append(players_state[player_gold_DT])

    # Determine max gold for plot.
    maxgold = 0

    for gold in total_gold_earned.itervalues():
        if max(gold) > maxgold:
            maxgold = max(gold)

    # Plot results
    figure, axis = plt.subplots()
    figure.patch.set_facecolor('white')
    axis.set_ylabel('m_iTotalEarnedGold [gold]')
    axis.set_xlabel('Game Time [min]')
    for i in range(10):
        axis.plot(game_time, total_gold_earned[player_names[i]], \
                  color=PLAYER_COLORS[i], linewidth=3.0, label=player_names[i])
    axis.axis((0, game_time[-1], 0, maxgold))
    axis.legend(loc='upper left', labelspacing=0.5, handletextpad=2)
    axis.set_title('Total Gold Earned [gold] vs. Time [min]')
    plt.show()


if __name__ == '__main__':
    main()

##Pudge Hooks back to top
This script will save an image showing all the pudge hooks throughout a game, there are some things to be done yet, but it is functional.

import os
import matplotlib.pyplot as plt
from matplotlib.pyplot import savefig
import matplotlib.offsetbox as ob
from common import HERO_ICONS_PATH, MINIMAP_PATH, HEROID, DEMO_FILE_PATH, worldcoordfromcell, imagecoordfromworld
from skadi import demo as d

'''
This script should plot all the hooks that occured in a game with pudge.  It uses the modifer placed on a hooked
target as the indicator for a hook.  You will need the folder of Hero Icons to plot the targets.  Hooks that 
do not hit a hero, but do hit a creep are red lines.  Hooks that miss completely, or kill a hero on impact are 
not implemented yet.  These are features for a future version.
'''

def main():
    game = d.construct(DEMO_FILE_PATH)
    game_time = []
    hooks = []

    for tick, user_messages, game_events, world, modifiers in game.stream(tick=0):

        rules_ehandle, rules_state = world.find_by_dt('DT_DOTAGamerulesProxy')

        if rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')] != 0.0:

            time = rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_fGameTime')] - \
                   rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')]
            game_time.append(time / 60)

            # Here we want to start going through the modifiers.
            # We are looking for 'modifier_pudge_meat_hook'. 
            # Once this modifier is encountered, we find the location of pudge and the target
            # and the target's model index.  We will use this for plotting later.
            for parent, parent_modifiers in modifiers.by_parent.iteritems():
                for mod_num, mod_dict in parent_modifiers.iteritems():
                    if mod_dict['name'] == 'modifier_pudge_meat_hook':
                        if not hooks:
                            # Each hook will be a dict with the following information to make plotting easy.
                            hooks.append({'tick': tick, 'target': parent,
                                          'target_index': world.find(parent)[('DT_DOTA_BaseNPC', 'm_iUnitNameIndex')],
                                          'time': time / 60.0,
                                          'pudge_pos': worldcoordfromcell(world.find(mod_dict['caster'])),
                                          'target_pos': worldcoordfromcell(world.find(parent))})
                        elif tick - hooks[-1]['tick'] > 100:
                            hooks.append({'tick': tick, 'target': parent,
                                          'target_index': world.find(parent)[('DT_DOTA_BaseNPC', 'm_iUnitNameIndex')],
                                          'time': time / 60.0,
                                          'pudge_pos': worldcoordfromcell(world.find(mod_dict['caster'])),
                                          'target_pos': worldcoordfromcell(world.find(parent))})
                        else:
                            pass
    
    # In my replay, there was an error with me hooking an allied hero.  This hook kept showing up in each tick,
    # so I removed it manually.
    # hooks = [hooks[i] for i in range(len(hooks)) if hooks[i]['target'] != 832840]
    return hooks


def hook_plotting(hooks):

    # Add our map image, scale & color axes appropriately, and make the axes take up the whole figure
    map_img = plt.imread(MINIMAP_PATH)
    fig, ax = plt.subplots(figsize=(10.25, 10.25))
    ax.set_position([0, 0, 1, 1])
    plt.imshow(map_img)
    fig.patch.set_facecolor('black')
    ax.patch.set_facecolor('black')
    ax.axis((0, 1024, 1024, 0))
    
    # Each hero icon is plotted as an OffsetImage.  Basically the image is an OffsetImage, which is
    # then added to the plot as an OffsetBox.
    pudge_img = plt.imread(os.path.abspath(os.path.join(HERO_ICONS_PATH, 'npc_dota_hero_pudge.png')))
    pudge_oi = ob.OffsetImage(pudge_img, zoom=0.75)

    for hook in hooks:

        px, py = imagecoordfromworld(hook['pudge_pos'][0], hook['pudge_pos'][1])
        tx, ty = imagecoordfromworld(hook['target_pos'][0], hook['target_pos'][1])

        ax.plot([px, tx], [py, ty], color='r', zorder=3, linewidth=5)
        pudge_ab = ob.AnnotationBbox(pudge_oi, (px, py))
        pudge_ab.patch.set_alpha(0)
        pudge_art = ax.add_artist(pudge_ab)
        pudge_art.set(zorder=4)

        #Can't remember right now why I had a KeyError exception here.
        try:
            hero_img_name = HEROID[hook['target_index']]
            target_img = plt.imread(
                os.path.abspath(os.path.join(HERO_ICONS_PATH, '{hero}.png'.format(hero=hero_img_name))))
            target_oi = ob.OffsetImage(target_img, zoom=0.75)
            target_ab = ob.AnnotationBbox(target_oi, (tx, ty))
            target_ab.patch.set_alpha(0)
            target_art = ax.add_artist(target_ab)
            target_art.set(zorder=5)
        except KeyError:
            pass
    
    #Replace this with a suitable save location if you want to save
    savefig('/Users/Andrew/Desktop/hooks.png', dpi=100)


if __name__ == '__main__':
    hooks = main()
    hook_plotting(hooks)

##Print Neutrals back to top
This script will print out each neutral from the game, with their model index

import re
from sets import Set
from common import DEMO_FILE_PATH
from skadi import demo as d


def main():
    # Construct a demo and make a set to store neutral model indices
    game = d.construct(DEMO_FILE_PATH)
    creep_set = Set()

    # Iterate through each tick of the replay, finding all instances of
    # 'DT_DOTA_BaseNPC_Creep_Neutral'.  These are the DTs that specify neutral
    # creep units.

    for tick, user_messages, game_events, world, modifiers in game.stream(tick=0):
        neutral = world.find_all_by_dt('DT_DOTA_BaseNPC_Creep_Neutral')

        # neutral at this point is a list of ehandles.  ehandles are unique
        # identifiers, so we want to use world.find(ehandle) to access the data
        #  Once we find them, world.find returns a dict.  
        # ('DT_BaseEntity', 'm_nModelIndex') is the key for that dict that returns
        # the model index.  All of the data stored in DTs is access this way,
        # using ehandles and keys.

        for creep in neutral:
            creep_set.add(world.find(creep)[('DT_BaseEntity', 'm_nModelIndex')])

    # Skip to the end of the replay and grab the table that allows us to convert
    # model index into a useful name.
    model_table = game.stream(tick=game.file_info.playback_ticks - 5).string_tables['modelprecache']
    creep_list = sorted(creep_set)

    for creep in creep_list:
        # This regex will take the name of the model, and strip the .mdl extension
        # this gives us a better idea of which creep is which.
        creep_name = re.findall('(?<=/)[a-z\_]+(?=\.mdl)', model_table.by_index[creep][0])[0]

        print '{0: <3}'.format(creep), ':', creep_name.encode('UTF-8')


if __name__ == '__main__':
    main()

##Buyback Cooldown back to top
This script plots buyback cooldown for a specific player vs. game time

import matplotlib.pyplot as plt

from common import DEMO_FILE_PATH
from skadi import demo as d


def bbcd():
    game = d.construct(DEMO_FILE_PATH)

    bbcd = []
    game_time = []

    for tick, user_messages, game_events, world, modifiers in game.stream(tick=0):

        players_ehandle, players_state = world.find_by_dt('DT_DOTA_PlayerResource')
        rules_ehandle, rules_state = world.find_by_dt('DT_DOTAGamerulesProxy')

        if rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')] != 0.0:
            time = rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_fGameTime')] \
                   - rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')]
            game_time.append(time / 60)

            bbcd.append(players_state[('DT_DOTA_PlayerResource', 'm_flBuybackCooldownTime.0001')])

    return game_time, bbcd


def bbcd_plotting(game_time, bbcd):
    fig, ax = plt.subplots()
    fig.patch.set_facecolor('white')
    ax.set_ylabel('m_flBuybackCooldownTime')
    ax.set_xlabel('Game Time [min]')
    ax.axis((0, 40, 0, 3500))
    ax.plot(game_time, bbcd, 'k', linewidth=3.0)
    plt.show()


if __name__ == '__main__':
    game_time, bbcd = bbcd()
    bbcd_plotting(game_time, bbcd)

##Total Distance Traveled back to top
This script will print out each player's name and the total distance that they traveled in game units during the game

from itertools import islice
from math import sqrt
from common import DEMO_FILE_PATH, worldcoordfromcell
from skadi import demo as d


def main():
    game = d.construct(DEMO_FILE_PATH)

    player_names = []
    player_coords = {}
    dist = {}

    for player in game.file_info.game_info.dota.player_info:
        name = player.player_name.encode('UTF-8')
        player_names.append(name)
        player_coords[name] = []
        dist[name] = 0

    for tick, user_messages, game_events, world, modifiers in islice(game.stream(tick=0), 0, None, 30):

        players_ehandle, players_states = world.find_by_dt('DT_DOTA_PlayerResource')
        rules_ehandle, rules_state = world.find_by_dt('DT_DOTAGamerulesProxy')

        if rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')] != 0.0:
            for number, player in enumerate(player_names):
                player_id = str(number).zfill(4)
                hero = world.find(
                    players_states[('DT_DOTA_PlayerResource', 'm_hSelectedHero.{ID}'.format(ID=player_id))])
                player_coords[player].append(worldcoordfromcell(hero))

    for player in player_names:
        for time in range(1, len(player_coords[player])):
            x2, y2 = player_coords[player][time]
            x1, y1 = player_coords[player][time - 1]
            dist[player] += sqrt((y2 - y1) ** 2 + (x2 - x1) ** 2)

        print '{player:''>20} : {dist}'.format(player=player, dist=dist[player])


if __name__ == '__main__':
    main()

##Midas Efficiency back to top
This script calculates an "efficiency" of each player's hand of midas. Unsure how it works with players getting more than one hand of midas...

from common import DEMO_FILE_PATH
from skadi import demo as d

'''
This script will take all players, check if they have a hand of midas, and if so, check how often it is on cooldown.
This is a pseduo-metric to determine how efficient they are at using their midas every time it is available, for maximum
gain.  This does not take into account strategic uses, such as possessed neutral greeps, etc.  If a player did not get
a midas during the game, it will print their name with 'no midas'.
'''

def main():
    game = d.construct(DEMO_FILE_PATH)

    player_names = []
    midas_on_cd = {}

    # Get all the player names
    for player in game.file_info.game_info.dota.player_info:
        name = player.player_name.encode('UTF-8')
        player_names.append(name)
        midas_on_cd[name] = []

    for tick, user_messages, game_events, world, modifiers in game.stream(tick=0):

        players_ehandle, players_states = world.find_by_dt('DT_DOTA_PlayerResource')
        rules_ehandle, rules_state = world.find_by_dt('DT_DOTAGamerulesProxy')

        if rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')] != 0.0:
            # For each player, 
            for player_num in range(10):
                player_id = str(player_num).zfill(4)
                hero = world.find(
                    players_states[('DT_DOTA_PlayerResource', 'm_hSelectedHero.{ID}'.format(ID=player_id))])
                # For each item in that player's inventory
                for item_num in range(6):
                    item_id = str(item_num).zfill(4)
                    
                    # Try querying their hand of midas cooldown.  If they don't have a hand of midas, we'll
                    # except that KeyError and continue to the next player.
                    try:
                        item = world.find(hero[('DT_DOTA_UnitInventory', 'm_hItems.{ID}'.format(ID=item_id))])
                        if item[('DT_BaseEntity', 'm_iName')] == 'item_hand_of_midas':
                            # If they do have a midas, we'll add an item to the boolean
                            # list to determine if it's on cooldown or not.
                            item_cd = item[('DT_DOTABaseAbility', 'm_fCooldown')]
                            game_time = rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_fGameTime')]
                            midas_on_cd[player_names[player_num]].append(item_cd > game_time)
                    except KeyError:
                        pass

    for player in player_names:
        if not midas_on_cd[player]:
            print '{player:''>20} : no midas'.format(player=player)
        else:
            # If midas_on_cd isn't empty, print the efficiency as a percentage
            print '{player:''>20} : {midaseff}'.format(player=player, midaseff=str(
                (float(midas_on_cd[player].count(True)) / len(midas_on_cd[player])) * 100))


if __name__ == '__main__':
    main()

##Hero Attributes back to top
This script will plot each hero's attributes (str, agi, int) over the course of the game vs time.

import matplotlib.pyplot as plt
from common import DEMO_FILE_PATH
from skadi import demo as d

'''
This script plots the attributes of a single hero over the entire game.
'''

def stragiint():
    game = d.construct(DEMO_FILE_PATH)

    # strength, agi, intel are the base stats from levels
    strength = []
    agi = []
    intel = []
    
    # the totals are the base + any attributes added by items or buffs
    str_tot = []
    agi_tot = []
    int_tot = []
    game_time = []

    for tick, user_messages, game_events, world, modifiers in game.stream(tick=0):

        players_ehandle, players_state = world.find_by_dt('DT_DOTA_PlayerResource')
        rules_ehandle, rules_state = world.find_by_dt('DT_DOTAGamerulesProxy')

        if rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')] != 0.0:
            
            # This is the selected hero.  For each tick, grab all the attributes and add them to the appropriate list
            hero = world.find(players_state[('DT_DOTA_PlayerResource', 'm_hSelectedHero.0001')])
            agi.append(hero[('DT_DOTA_BaseNPC_Hero', 'm_flAgility')])
            strength.append(hero[('DT_DOTA_BaseNPC_Hero', 'm_flStrength')])
            intel.append(hero[('DT_DOTA_BaseNPC_Hero', 'm_flIntellect')])
            agi_tot.append(hero[('DT_DOTA_BaseNPC_Hero', 'm_flAgilityTotal')])
            int_tot.append(hero[('DT_DOTA_BaseNPC_Hero', 'm_flIntellectTotal')])
            str_tot.append(hero[('DT_DOTA_BaseNPC_Hero', 'm_flStrengthTotal')])

            time = rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_fGameTime')] \
                   - rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')]
            game_time.append(time / 60)

    return game_time, strength, agi, intel, str_tot, agi_tot, int_tot


def stragiintplotting(game_time, strength, agi, intel, str_tot, agi_tot, int_tot):
    fig, ax = plt.subplots()
    fig.patch.set_facecolor('white')
    ax.set_ylabel('Attributes')
    ax.set_xlabel('Game Time [min]')
    ax.axis((0, 40, 0, 100))
    ax.plot(game_time, agi, 'g', label='agi base', linewidth=3.0)
    ax.plot(game_time, agi_tot, 'g--', label='agi total', linewidth=3.0)
    ax.plot(game_time, intel, 'b', label='int base', linewidth=3.0)
    ax.plot(game_time, int_tot, 'b--', label='int total', linewidth=3.0)
    ax.plot(game_time, strength, 'r', label='str base', linewidth=3.0)
    ax.plot(game_time, str_tot, 'r--', label='str total', linewidth=3.0)
    ax.legend(loc='upper left')
    plt.show()


if __name__ == '__main__':
    game_time, strength, agi, intel, str_tot, agi_tot, int_tot = stragiint()
    stragiintplotting(game_time, strength, agi, intel, str_tot, agi_tot, int_tot)

##Team Hulls back to top
This script takes each team, makes a convex hull around them, and then plots it. I am unable to get the animation working (unsure if it's a computer/python installation issue or script issue). If you find anything, please let garth5689 know in IRC.

import itertools
import os
import matplotlib.pyplot as plt
import matplotlib.offsetbox as ob
import numpy as np
import scipy.spatial as spatial
import matplotlib.patches as patches
from common import MINIMAP_PATH, HERO_ICONS_PATH, HEROID, DEMO_FILE_PATH, worldcoordfromcell, imagecoordfromworld
from skadi import demo as d

'''
This script creates a convex hull between all of the alive heros on the radiant/dire sides and plots it.  As it is
currently written, this script will calculate all the data, and then when the plotting method is called, it will draw
and show each frame one-by-one.  I could not find a suitable way to animate the frames, including saving them and then
animating them.  If you can find a way, please let me know and submit a pull request to 
https://github.com/garth5689/skadi/tree/explore  I use the scipy.spatial module, and several extra hull functions
from http://tomswitzer.net/2010/03/graham-scan/
'''


TURN_LEFT, TURN_RIGHT, TURN_NONE = (1, -1, 0)


# This code allows us to take the simplex points from the convex hull and order them to draw the polygon
# Code from http://tomswitzer.net/2010/03/graham-scan/
def turn(p, q, r):
    return cmp((q[0] - p[0]) * (r[1] - p[1]) - (r[0] - p[0]) * (q[1] - p[1]), 0)


def _keep_left(hull, r):
    while len(hull) > 1 and turn(hull[-2], hull[-1], r) != TURN_LEFT:
        hull.pop()
    if not len(hull) or hull[-1] != r:
        hull.append(r)
    return hull


def convex_hull(points):
    """Returns points on convex hull of an array of points in CCW order."""
    points = sorted(points)
    l = reduce(_keep_left, points, [])
    u = reduce(_keep_left, reversed(points), [])
    return l.extend(u[i] for i in xrange(1, len(u) - 1)) or l


def main():
    game = d.construct(DEMO_FILE_PATH)

    hero_handles = []
    player_nums = [str(i).zfill(4) for i in range(10)]
    
    # These will be lists of lists.  the length of each will be the number of frame generated, and each
    # will refer to a tick.
    unit_ids = []
    rad_pos = []
    dire_pos = []

    for tick, user_messages, game_events, world, modifiers in itertools.islice(game.stream(tick=0), 0, None, 60):

        players_ehandle, players_state = world.find_by_dt('DT_DOTA_PlayerResource')
        rules_ehandle, rules_state = world.find_by_dt('DT_DOTAGamerulesProxy')

        if rules_state[('DT_DOTAGamerulesProxy', 'DT_DOTAGamerules.m_flGameStartTime')] != 0.0:
            
            # The first time through this loop, generate a list of the hero handles present in the game.
            if not hero_handles:
                for player_num in player_nums:
                    hero_handles.append(
                        players_state[('DT_DOTA_PlayerResource', 'm_hSelectedHero.{ID}'.format(ID=player_num))])

            temp_unit_ids = []
            temp_rad_pos = []
            temp_dire_pos = []

            for num, hero_handle in enumerate(hero_handles):
                hero = world.find(hero_handle)
                if hero[('DT_DOTA_BaseNPC', 'm_lifeState')] == 0:
                    # Add each unit ID to the temp ID list, and each coords to the correct team's coords.  This 
                    # keeps the radiant and dire separate so we can draw two polygons.
                    if num <= 4:
                        dx, dy = worldcoordfromcell(hero)
                        x, y = imagecoordfromworld(dx, dy)
                        temp_rad_pos.append([x, y])
                        temp_unit_ids.append(hero[('DT_DOTA_BaseNPC', 'm_iUnitNameIndex')])
                    elif num >= 4:
                        dx, dy = worldcoordfromcell(hero)
                        x, y = imagecoordfromworld(dx, dy)
                        temp_dire_pos.append([x, y])
                        temp_unit_ids.append(hero[('DT_DOTA_BaseNPC', 'm_iUnitNameIndex')])

            if not (temp_unit_ids == [] and temp_rad_pos == [] and temp_dire_pos == []):
                unit_ids.append(temp_unit_ids)
                rad_pos.append(np.array(temp_rad_pos))
                dire_pos.append(np.array(temp_dire_pos))

    return unit_ids, rad_pos, dire_pos


def hull_plotting(unit_ids, rad_pos, dire_pos):
    for index in range(len(rad_pos)):
        
        # set up the initial plot, add the map, maximize axes, etc.
        fig, ax = plt.subplots(figsize=(10.25, 10.25))
        map_img = plt.imread(MINIMAP_PATH)
        ax.set_position([0, 0, 1, 1])
        plt.imshow(map_img)
        fig.patch.set_facecolor('black')
        ax.patch.set_facecolor('black')
        ax.axis((0, 1024, 1024, 0))
        
        # Plot all the hero icons in their coordinates
        for num, hero in enumerate(unit_ids[index]):
            hero_img_name = HEROID[hero]
            hero_img = plt.imread(
                os.path.abspath(os.path.join(HERO_ICONS_PATH, '{hero}.png'.format(hero=hero_img_name))))
            hero_oi = ob.OffsetImage(hero_img, zoom=0.75)
            if num < len(rad_pos[index]):
                hero_ab = ob.AnnotationBbox(hero_oi, (rad_pos[index][num, 0], rad_pos[index][num, 1]))
            else:
                hero_ab = ob.AnnotationBbox(hero_oi, (
                    dire_pos[index][num - len(rad_pos[index]), 0], dire_pos[index][num - len(rad_pos[index]), 1]))
            hero_ab.patch.set_alpha(0)
            hero_art = ax.add_artist(hero_ab)
            hero_art.set(zorder=5)
        
        # For each of the teams, if there are more than 2 heros, plot the polygon for the convex hull.
        # If there are only two heros, draw a line between them.  else, don't draw any lines, etc.
        if len(rad_pos[index]) >= 3:
            rad_hull = spatial.ConvexHull(rad_pos[index])
            rad_points = []
            for simplex in rad_hull.simplices:
                p1 = [rad_pos[index][simplex, 0][0], rad_pos[index][simplex, 1][0]]
                p2 = [rad_pos[index][simplex, 0][1], rad_pos[index][simplex, 1][1]]
                if p1 not in rad_points:
                    rad_points.append(p1)
                if p2 not in rad_points:
                    rad_points.append(p2)
            rad_points = convex_hull(rad_points)
            hull_poly = patches.Polygon(rad_points, fc='green', ec='green', alpha=0.4, lw=3)
            ax.add_artist(hull_poly)

        elif len(rad_pos[index]) == 2:
            plt.plot(rad_pos[index][:, 0], rad_pos[index][:, 1], 'g-', linewidth=3, alpha=0.4, zorder=3)

        if len(dire_pos[index]) >= 3:
            dire_hull = spatial.ConvexHull(dire_pos[index])
            dire_points = []
            for simplex in dire_hull.simplices:
                p1 = [dire_pos[index][simplex, 0][0], dire_pos[index][simplex, 1][0]]
                p2 = [dire_pos[index][simplex, 0][1], dire_pos[index][simplex, 1][1]]
                if p1 not in dire_points:
                    dire_points.append(p1)
                if p2 not in dire_points:
                    dire_points.append(p2)
            dire_points = convex_hull(dire_points)
            hull_poly = patches.Polygon(dire_points, fc='red', ec='red', alpha=0.4, lw=3)
            ax.add_artist(hull_poly)

        elif len(dire_pos[index]) == 2:
            plt.plot(dire_pos[index][:, 0], dire_pos[index][:, 1], 'g-', linewidth=3, alpha=0.4, zorder=3)

        plt.show()


if __name__ == '__main__':
    unit_ids, rad_pos, dire_pos = main()
    hull_plotting(unit_ids, rad_pos, dire_pos)