leonhard-s / auraxium

A high-level Python wrapper for the PlanetSide 2 API.
https://auraxium.readthedocs.io/
MIT License
28 stars 8 forks source link

Example of obtaining a player's overall kills and deaths. #37

Closed dbrennand closed 3 years ago

dbrennand commented 3 years ago

Hello,

I am quite confused on how I would go about obtaining a player's overall kills, deaths and KDR.

I am unsure what method I need to call on Character in order to obtain these stats.

Would I use stat() or stat_by_faction() or stat_history() ?

Appreciate any help or guidance you can give.

leonhard-s commented 3 years ago

Hi,

This is handled quite unintuitively on Daybreak's side, and the answer is generally "all of the above, depending on the context". KDR and accuracy must always be calculated on your end, but getting that data can be... messy.

Here are some examples to illustrate:

If that sounds like a mess... it is. These methods are direct equivalents of the corresponding API tables (characters_stat_by_faction, etc.) and I have unfortunately not yet found a satisfying way to abstract this data to be more friendly.
I had plans for an assortment of built-in reports that would return this data on a per-player basis (#18), but I have not found a syntax that doesn't come with a significant performance hit or other limitations.

Speaking of which - if you have any suggestions on how this syntax could work and what it would look like, feel free to leave it below... I am rather frustrated with the current solution as well.

PS: There is a special API collection that you can use to get all statistics for a given character. It is well indexed and fast, but the returned payloads are also very large and incur a lot of unneeded network traffic. It is called single_character_by_id, here is an example. When you need to find an API stat, your best shot is searching for its name in there, then following the data tree to find where it lives.

dbrennand commented 3 years ago

Hi @leonhard-s

Thank you for your detailed response! 😃

I agree with you that it is indeed "a mess" 😆 . Why on earth there isn't just a single endpoint to query data such as this is like mind blowing :/ Maybe it requires some computation on their side to calculate some of the stats so maybe this might be why? Who knows... 😅

Thanks for sharing this API collection (single_character_by_id). I will have a play around with this. Is there a method in auraxium that allows me to query the single_character_by_id endpoint for a given character ID?

I will post here if I do come up with any ideas for syntax for reports.

Thanks 👍

leonhard-s commented 3 years ago

Is there a method in auraxium that allows me to query the single_character_by_id endpoint for a given character ID?

Indirectly, there is. You can use the URL generation submodule auraxium.census to create the query object and then pass that to the client - here is a snippet using it:

import asyncio
import auraxium
from auraxium import census

async def main():
    # Create a client and grab a character - you likely already have this
    client = auraxium.Client(service_id='s:example')
    char = await client.get_by_name(auraxium.ps2.Character, 'Auroram')
    assert char is not None

    # Create a query object for this character's statistics collection table
    query = census.Query(
        'single_character_by_id', service_id=client.service_id, character_id=char.id)

    # Perform the query through the client's own request handler
    data = await client.request(query)
    # Extract the data we're interested in
    player_stats = data['single_character_by_id_list'][0]['stats']

    # With that, you can process the "stat" dictionary like any other. As an example,
    # here I'll list all the stat_name-s from the "characters_stat" collection.
    print(sorted(
        (s['stat_name'] for s in player_stats['stat'])))

asyncio.run(main())

I hope that works for your application - keep in mind that the values returned in this dictionary are strings, so you'll have to cast them to int before you can compare or print them properly.

dbrennand commented 3 years ago

I have version auraxium 0.1.0a1 of the library. Which I think is the latest version available on PyPi.

I run into the error below when running the code above:

AttributeError: 'Client' object has no attribute 'request'

leonhard-s commented 3 years ago

I did push 0.1.0a2 yesterday, that should include the current master branch.

dbrennand commented 3 years ago
* The total number of deaths is stored in the "weapon_deaths" field of `stat()`, for profile ID 0 (i.e. global)

I was just trying to calculate total number of deaths from calling stat() however, it seems to return a list of:

 {'character_id': '5428041429990226841',
  'last_save': '1409302036',
  'last_save_date': '2014-08-29 08:47:16.071',
  'skill_id': '8966'},

My code:

import aiohttp
import asyncio
import pprint
import auraxium

async def main():
    async with auraxium.Client() as client:
        char = await client.get_by_name(auraxium.ps2.Character, "MyPlayerName")
        stat = await char.stat()
        pprint.pprint(stat)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

I'm not sure "weapon_deaths" is returned via this method? 🤔 If so, I must be missing something crucial here.

Thanks.

leonhard-s commented 3 years ago

This issue is twofold:

In its current state, char.stat() works similar to client.find(), just specific to that particular sub-query. So to get a particular field, you must either set the results keyword to an integer large enough that all results are returned, or you can specify the name of the stat field to retrieve via the generic **kwargs:

import asyncio
import pprint
import auraxium

async def main():
    async with auraxium.Client() as client:
        char = await client.get_by_name(auraxium.ps2.Character, "MyPlayerName")
        stat = await char.stat(stat_name='weapon_deaths')
        pprint.pprint(stat)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

The other issue is a typo in the method itself, update via pip to resolve that one.

dbrennand commented 3 years ago

Ahh I see.

Thank you @leonhard-s for your help.

I think I have managed to obtain player total kills using:

# Obtain character total kills
player_kills = await player.stat_by_faction(stat_name="weapon_kills")
leonhard-s commented 3 years ago

Great to hear!

Best to always double-check the numbers with a character you own to validate; there are funky things going on at times, like the total number of kills/deaths not matching the sum of kills for each class/profile, and so on. For example, a kill scored once your character has died or respawned could cause this kill to count towards an incorrect profile, or no profile at all; only being added to the global total.

Some stat tracking sites do mitigate these inconsistencies, others do not - but as long as you're in the ball park, you likely have the right stat field.