Game Data

The following provides a detailed how-to for use of static game data that is loaded into coc.py.

All game data has been retrieved from https://coc.guide and is freely available.

Why?

While the API provides all data about a player’s current troops and spells, often people want to know “does this player have a maxed Barbarian for their TH level?” or “how much does it cost to upgrade all a player’s spells to max?”.

This then results in an often clumsy and hard-to-maintain dictionary, csv, json or other form of static data being made and utilised. The integration of these game files into coc.py means there’s less need to duplicate this process, and upgrading/updating code should be as easy as upgrading versions of coc.py whenever an update lands.

Other benefits include integration into existing Troop and other data models, meaning the process for getting a troop’s upgrade cost for a player is the exact same as getting the troop’s name, or level. But more on that later.

Additionally, speed and memory consumption has been prioritised and optimised where possible.

Initialising the Client

There are a few options you can choose from as to how coc.py will handle when to inject game data and when not to. Although all efforts have been made to minimise overheads, some may prefer to only use game data when they choose to.

  1. Always

    If you want coc.py to always inject game metadata, regardless of when or where you call Client.get_player(), including in events, use this option.

    Game metadata will be loaded on startup using this option, which means you can use Client.parse_army_link() etc. without issue.

client = coc.login('email', 'password', load_game_data=coc.LoadGameData(always=True))
  1. Default

    In this option, coc.py will always inject game metadata when using Client.get_player(), however will not load game metadata for any events dispatched. Instead see Loading Game Data Manually for how to load it when using events.

    Game metadata will be loaded on startup using this option, which means you can use Client.parse_army_link() etc. without issue.

client = coc.login('email', 'password', load_game_data=coc.LoadGameData(default=True))
  1. Startup Only

    With this option, coc.py will load game metadata on startup, but will never automatically load it into Player objects. This means you must manually call Player.load_game_data() to load game data for a player.

    Using this option means game metadata is loaded on startup, which means Client.parse_army_link() etc. will will work fine.

client = coc.login('email', 'password', load_game_data=coc.LoadGameData(startup_only=True))
  1. Never

    With this option, game metadata is not loaded on startup, and will never be injected into player objects. This means that you cannot use Client.parse_army_link() methods, nor the Player.load_game_data() to manually load the data.

client = coc.login('email', 'password', load_game_data=coc.LoadGameData(never=True))

Loading Game Data Manually

The preferred way of telling coc.py that you want to load game data for player requests is either through setting load_game_metadata to Always or Default, or by passing load_game_data=True into your Client.get_player() call.

player = await client.get_player("#tag", load_game_data=True)

Alternatively, if you initialised the client with load_game_data set to Always or Default and don’t wish to load game data, you can set it to False:

player = await client.get_player("#tag", load_game_data=False)

By default, however, it is set to whatever you initialised at Client startup.

If you’re using the EventsClient and don’t have load_game_metadata set to Always, your player events won’t have metadata loaded. This is easy to fix, with the Player.load_game_data() call:

@client.event
@coc.PlayerEvents.troop_change()
async def player_troop_upgraded(cached_player, player, troop):
    player.load_game_data()

    print("Player {} upgraded {}, which costs {} to upgrade again.", player.name, troop.name, troop.upgrade_cost)

Warning

This is not designed for regular use. Please don’t use it where there’s an alternative. It manually injects troop data into the existing objects which takes longer than creating them at the start of the call. The example given is the only use-case I can think of at present.

Initiated vs Uninitiated Objects

Due to internal design, Troop (and other, but they mimic Troop so we’ll use Troop as an example) classes are stored internally as uninitiated classes, with all the properties from game data stored as class attributes. When you call Client.get_player(), the client will retrieve these uninitiated classes, and create instances of them, which is what you use when accessing Player.troops.

When you use Client.get_troop(), you receive an uninitiated class, which has slightly different ways of dealing with attributes.

Initiated Objects

These are what you receive when you use Player.get_troop() with a player retrieved with Client.get_player(), or a troop retrieved with Client.get_troop() when passing in level or townhall parameters.

The important thing to note is that all attributes found under Troop (and Spell, Hero, Pet) all return their type (int, bool, etc.)

player = await client.get_player("#2pp")
barb = player.get_troop("Barbarian")

print(barb.dps)  # prints 56
print(barb.hitpoints)  # prints 129

If you wanted to get the statistics of the barbarian 1 level up from the current level, you could do

player = await client.get_player("#2pp")
barb = player.get_troop("Barbarian")

barb.level += 1  # set the level to be 1 higher

print(barb.dps)  # prints 64
print(barb.hitpoints)  # prints 132

Note

Be very careful when adjusting the level that you store a copy of what the original troop level it, because coc.py won’t. Changing the level will effect the duration of that player object, and may introduce unintended bugs.

If you wanted to get a dictionary of all the troop level/hitpoints, you could do the following:

player = await client.get_player("#2pp")
barb = player.get_troop("Barbarian")

for level, hitpoints in enumerate(barb.__class__.hitpoints, start=1):
    print("Barbarian has {} hitpoints at Lv{}.".format(hitpoints, level))

Note

__class__ is a descriptor that provides access to the uninitiated root class of the instance object. If that doesn’t make sense, it basically means that barb.__class__ gives you an Uninitiated Objects.

Alternatively, you could use Client.get_troop() and use the unititiated object returned from that. See below.

Uninitiated Objects

These are perhaps the less common variant of Troop and other classes you’ll come across. Internally, all data objects are stored as these, and instances are created on-demand when you request a player.

You can get these with Client.get_troop(), but they function a little differently to Initiated Objects.

barb = client.get_troop("Barbarian")
print(barb.id)  # prints 4000000

print(barb.hitpoints)  # prints [56, 64, 67, 71, ...]

Anything that varies with level will return a list of statistics when using uninitiated objects.

This includes (and note, where applicable, these attributes apply to Spell, Hero and Pet too:

A few more examples:

barb = client.get_troop("Barbarian")

for level, hitpoints in enumerate(barb.hitpoints, start=1):
    print("Barbarian has {} hitpoints at Lv{}.".format(hitpoints, level))


barb_king = client.get_hero("Barbarian King")
for level, regeneration_time in enumerate(barb_king.regeneration_time, start=1):
    print("BK has {}min regeneration time at level {}".format(level, regeneration_time.minutes))

One interesting thing to note it that barb.hitpoints will return a list of [34, 43, 54, 65, 76, ...] which may look like a normal list, but if you index it, i.e. hitpoints[4], you’ll get stats for level 4, rather than the 5th item in the list, or level 5 if it were to be 0-indexed. If you use enumerate, you still need to specify that you’re starting from 1, however: enumerate(barb.hitpoints, start=1).

Put simply, this means barb.hitpoints[1] will return stats for barb level 1, and barb.hitpoints[0] will raise an error.

barb = client.get_troop("Barbarian")

lv_1_stats = barb.hitpoints[1]  # returns 96
lv_14_stats = barb.hitpoints[14]  # returns 234

# or, assigning hitpoints as a variable
hitpoints = barb.hitpoints  # returns [96, 123, 145, 167, ..., 234]
hitpoints_lv_1 = hitpoints[1]  # hitpoints is still 1-indexed
hitpoints_lv_14 = hitpoints[14]

print(hitpoints)  # will print <UnitStatList [96, 123, 145, 167, ..., 234]>, to show it's not a normal list.

Here’s a simple example, if that was all very confusing:

barb_king = client.get_hero("Barbarian King")

print(f"Barb King has an upgrade cost of {barb_king.upgrade_cost[43]} at level 43!")