2018-10-08 00:14:56 +08:00
#!/usr/bin/python3.7
# Script Analyze WoT Blitz replays
2019-12-24 02:52:56 +02:00
import sys , argparse , json , os , concurrent , inspect , aiohttp , asyncio , aiofiles , aioconsole
import motor.motor_asyncio , configparser , ssl , re , logging , time , xmltodict , collections
2019-12-18 00:19:21 +02:00
from datetime import datetime , timedelta , date
2018-10-08 00:14:56 +08:00
import blitzutils as bu
from blitzutils import WG
2018-11-05 14:48:07 +02:00
from blitzutils import WoTinspector
2018-10-08 00:14:56 +08:00
logging . getLogger ( "asyncio" ) . setLevel ( logging . DEBUG )
2019-12-24 02:52:56 +02:00
WG_APP_ID = '81381d3f45fa4aa75b78a7198eb216ad'
FILE_CONFIG = 'blitzstats.ini'
DB_C_TANK_STATS = 'WG_TankStats'
DB_C_PLAYER_STATS = 'WG_PlayerStats'
2019-12-18 00:19:21 +02:00
2018-10-08 00:14:56 +08:00
wg = None
2018-11-05 14:48:07 +02:00
wi = None
2018-10-08 00:14:56 +08:00
REPLAY_N = 0
2019-08-17 00:50:48 +03:00
REPLAY_I = 0
2018-10-08 00:14:56 +08:00
STAT_TANK_BATTLE_MIN = 100
2019-12-24 02:52:56 +02:00
## For different player stat functions (global stats, tank tier stats, etc)
# 1st function = for forming stat_id, 2nd for DB stats query, 3rd for WG API stats query
STAT_FUNC = {
'tank_tier' : [ 'get_stat_id_tank_tier' , 'get_db_tank_tier_stats' , 'get_wg_tank_tier_stats' ],
'player' : [ 'get_stat_id_player' , 'get_db_player_stats' , 'get_wg_player_stats' ]
}
2018-10-08 00:14:56 +08:00
replay_summary_flds = [
'battle_result' ,
'battle_type' ,
'map_name' ,
'battle_duration' ,
2018-10-08 15:14:59 +08:00
'protagonist' ,
'title'
2018-10-08 00:14:56 +08:00
]
replay_details_flds = [
'damage_made' ,
'damage_assisted_track' ,
'damage_assisted' ,
'damage_received' ,
'damage_blocked' ,
'enemies_damaged' ,
'enemies_destroyed' ,
'hitpoints_left' ,
'time_alive' ,
'distance_travelled' ,
'base_capture_points' ,
'base_defend_points' ,
'wp_points_earned' ,
'wp_points_stolen' ,
'hits_received' ,
'hits_bounced' ,
'hits_splash' ,
'hits_pen' ,
'shots_made' ,
'shots_hit' ,
'shots_pen' ,
'shots_splash' ,
'enemies_spotted'
]
result_ratios = {
'KDR' : [ 'enemies_destroyed' , 'destroyed' ],
'DR' : [ 'damage_made' , 'damage_received' ],
'hit_rate' : [ 'shots_hit' , 'shots_made' ],
'pen_rate' : [ 'shots_pen' , 'shots_hit' ],
'dmg_block_rate' : [ 'damage_blocked' , 'damage_received' ]
}
result_categories = {
2018-12-14 23:33:38 +02:00
'total' : [ 'TOTAL' , 'total' ],
2018-10-08 00:44:39 +08:00
'battle_result' : [ 'Result' , [ 'Loss' , 'Win' , 'Draw' ]],
2018-10-08 00:14:56 +08:00
'battle_type' : [ 'Battle Type' ,[ 'Encounter' , 'Supremacy' ]],
2018-12-14 23:33:38 +02:00
'tank_tier' : [ 'Tank Tier' , 'number' ],
2018-10-08 00:14:56 +08:00
'top_tier' : [ 'Tier' , [ 'Bottom tier' , 'Top tier' ]],
2018-12-14 23:33:38 +02:00
'tank_name' : [ 'Tank' , 'string' ],
2019-08-17 00:50:48 +03:00
'map_name' : [ 'Map' , 'string' ],
'battle_i' : [ 'Battle #' , 'number' ]
2018-10-08 00:14:56 +08:00
}
2019-08-17 00:50:48 +03:00
result_categories_default = [
'total' ,
'battle_result' ,
2020-02-08 12:07:26 +02:00
'tank_tier' ,
'top_tier' ,
'tank_name' ,
'map_name'
2019-08-17 00:50:48 +03:00
]
2019-12-24 02:52:56 +02:00
RESULT_CAT_HEADER_FRMT = ' {:_<17s} '
RESULT_CAT_FRMT = ' {:>17s} '
2018-10-08 00:14:56 +08:00
2019-12-18 00:19:21 +02:00
## Syntax: Check how the replay JSON files look. The algorithm is counting/recording fields
2018-10-08 00:14:56 +08:00
result_fields = {
'battles' : [ 'Battles' , 'Number of battles' , 8 , ' {:^8.0f} ' ],
2018-12-15 14:12:12 +02:00
'win' : [ 'WR' , 'Win rate' , 6 , ' {:6.1%} ' ],
'damage_made' : [ 'DPB' , 'Average Damage' , 5 , ' {:5.0f} ' ],
'DR' : [ 'DR' , 'Damage Ratio' , 5 , ' {:5.1f} ' ],
'KDR' : [ 'KDR' , 'Kills / Death' , 4 , ' {:4.1f} ' ],
2018-10-08 00:14:56 +08:00
'enemies_spotted' : [ 'Spot' , 'Enemies spotted per battle' , 4 , ' {:4.1f} ' ],
'hit_rate' : [ 'Hit rate' , 'Shots hit / all shots made' , 8 , ' {:8.1%} ' ],
'pen_rate' : [ 'Pen rate' , 'Shots pen / shots hit' , 8 , ' {:8.1%} ' ],
2018-12-15 14:12:12 +02:00
'survived' : [ 'Surv%' , 'Survival rate' , 6 , ' {:6.1%} ' ],
2018-10-08 00:14:56 +08:00
'time_alive%' : [ 'T alive%' , 'Percentage of time being alive in a battle' , 8 , ' {:8.0%} ' ],
2018-12-15 14:12:12 +02:00
'top_tier' : [ 'Top tier' , 'Share of games as top tier' , 8 , ' {:8.0%} ' ],
2019-12-18 00:19:21 +02:00
'allies_wins' : [ 'Allies WR' , 'Average WR of allies at the tier of their tank' , 9 , ' {:9.2%} ' ],
'enemies_wins' : [ 'Enemies WR' , 'Average WR of enemies at the tier of their tank' , 10 , ' {:10.2%} ' ],
2018-10-08 00:14:56 +08:00
'allies_battles' : [ 'Allies Btls' , 'Average number battles of the allies' , 11 , ' {:11.0f} ' ],
2019-12-18 00:19:21 +02:00
'enemies_battles' : [ 'Enemies Btls' , 'Average number battles of the enemies' , 12 , ' {:12.0f} ' ]
2018-10-08 00:14:56 +08:00
}
2019-12-18 00:19:21 +02:00
## Syntax: key == stat field in https://api.wotblitz.eu/wotb/tanks/stats/ (all.KEY)
## Value array [ 'Stat Title', [ 0, data buckets ....], scaling_factor_for_bucket_values, 'print_format' ]
histogram_fields = {
'wins' : [ 'Win rate' , [ 0 , .35 , .40 , .45 , .5 , .55 , .60 , .65 , 1 ], 100 , ' {:.0f} %' ],
'damage_dealt' : [ 'Avg. Dmg.' , [ 0 , .25e3 , .5e3 , 1e3 , 1.5e3 , 2e3 , 2.5e3 , 3e3 , 100e3 ], 1 , ' {:.0f} ' ],
'battles' : [ 'Battles' , [ 0 , 1e3 , 3e3 , 5e3 , 10e3 , 15e3 , 25e3 , 50e3 , 5e7 ], .001 , ' {:.0f} k' ] # battles is a mandatory stat to include
2018-10-08 00:14:56 +08:00
}
2019-09-09 23:31:52 +03:00
2019-12-24 02:52:56 +02:00
def def_value_zero ():
2018-10-08 00:14:56 +08:00
return 0
2019-12-24 02:52:56 +02:00
def def_value_BattleRecord ():
2018-10-08 00:14:56 +08:00
return BattleRecord ()
2019-12-18 00:19:21 +02:00
class PlayerHistogram ():
def __init__ ( self , field : str , name : str , fields : list , factor : float , format : str ):
self . field = field
self . name = name
self . fields = fields
2019-12-24 02:52:56 +02:00
self . cat_factor = factor
self . cat_format = format
2019-12-18 00:19:21 +02:00
self . ncat = len ( self . fields ) - 1
self . allies = [ 0 ] * self . ncat
self . enemies = [ 0 ] * self . ncat
self . total = [ 0 ] * self . ncat
2019-12-24 02:52:56 +02:00
def record_ally ( self , stat : float ):
2019-12-18 00:19:21 +02:00
for cat in range ( 0 , self . ncat ):
if self . fields [ cat ] <= stat <= self . fields [ cat + 1 ]:
self . allies [ cat ] += 1
self . total [ cat ] += 1
return True
return False
2019-12-24 02:52:56 +02:00
def record_enemy ( self , stat : float ):
2019-12-18 00:19:21 +02:00
for cat in range ( 0 , self . ncat ):
if self . fields [ cat ] <= stat <= self . fields [ cat + 1 ]:
self . enemies [ cat ] += 1
self . total [ cat ] += 1
return True
return False
2019-12-24 02:52:56 +02:00
def get_category_name ( self , cat : int ):
2019-12-18 00:19:21 +02:00
if cat < self . ncat - 1 :
2019-12-24 02:52:56 +02:00
return self . format_category ( cat ) + ' - ' + self . format_category ( cat + 1 )
2019-12-18 00:19:21 +02:00
else :
2019-12-24 02:52:56 +02:00
return self . format_category ( cat ) + ' -'
2019-12-18 00:19:21 +02:00
2019-12-24 02:52:56 +02:00
def format_category ( self , cat : int ):
2019-12-18 00:19:21 +02:00
val = self . fields [ cat ]
2019-12-24 02:52:56 +02:00
return self . cat_format . format ( float ( val * self . cat_factor ))
2019-12-18 00:19:21 +02:00
def print ( self ):
N_enemies = 0
N_allies = 0
for cat in range ( 0 , self . ncat ):
N_enemies += self . enemies [ cat ]
N_allies += self . allies [ cat ]
N_total = N_allies + N_enemies
print ( " \n {:12s} | {:13s} | {:13s} | {:13s} " . format ( self . name , "Allies" , "Enemies" , "TOTAL" ))
for cat in range ( 0 , self . ncat ):
2019-12-24 02:52:56 +02:00
print ( " {:12s} | {:5d} ( {:4.1f} %) | {:5d} ( {:4.1f} %) | {:5d} ( {:4.1f} %)" . format ( self . get_category_name ( cat ), self . allies [ cat ], self . allies [ cat ] / N_allies * 100 , self . enemies [ cat ], self . enemies [ cat ] / N_enemies * 100 , self . allies [ cat ] + self . enemies [ cat ], ( self . allies [ cat ] + self . enemies [ cat ]) / N_total * 100 ))
2019-12-18 00:19:21 +02:00
return None
2018-10-08 00:14:56 +08:00
class BattleRecord ():
def __init__ ( self ):
try :
self . battles = 0
self . ratios_ready = False
self . results_ready = False
2019-12-24 02:52:56 +02:00
self . results = collections . defaultdict ( def_value_zero )
2018-10-08 00:14:56 +08:00
self . avg_fields = set ( result_fields . keys ())
self . avg_fields . remove ( 'battles' )
self . avg_fields . difference_update ( result_ratios . keys ())
self . ratio_fields = set ()
for ratio in result_ratios . keys ():
self . ratio_fields . add ( result_ratios [ ratio ][ 0 ])
self . ratio_fields . add ( result_ratios [ ratio ][ 1 ])
except KeyError as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'Key not found' , err )
2018-10-08 00:14:56 +08:00
2019-12-24 02:52:56 +02:00
def record_result ( self , result : dict ) -> bool :
2018-10-08 00:14:56 +08:00
try :
for field in self . avg_fields | self . ratio_fields :
if result [ field ] != None :
self . results [ field ] += result [ field ]
self . battles += 1
return True
except KeyError as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'Key not found' , err )
2018-10-08 00:14:56 +08:00
return False
2019-12-24 02:52:56 +02:00
def calc_ratios ( self ) -> bool :
2018-10-08 00:14:56 +08:00
if self . ratios_ready :
return True
try :
ratio_result_flds = set ( result_ratios . keys ()) & set ( result_fields . keys ())
for field in ratio_result_flds :
if self . results [ result_ratios [ field ][ 1 ]] != 0 :
self . results [ field ] = self . results [ result_ratios [ field ][ 0 ]] / self . results [ result_ratios [ field ][ 1 ]]
else :
self . results [ field ] = float ( 'Inf' )
self . ratios_ready = True
return True
except KeyError as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'Key not found' , err )
2018-10-08 00:14:56 +08:00
return False
2019-12-24 02:52:56 +02:00
def calc_results ( self ):
2018-10-08 00:14:56 +08:00
if self . results_ready == True :
return True
if not self . ratios_ready :
2019-12-24 02:52:56 +02:00
self . calc_ratios ()
2018-10-08 00:14:56 +08:00
try :
for field in self . avg_fields :
2019-08-17 01:16:58 +03:00
self . results [ field ] = self . results [ field ] / max ( self . battles , 1 )
2018-10-08 00:14:56 +08:00
self . results [ 'battles' ] = self . battles
self . results_ready = True
return True
except KeyError as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'Key not found' , err )
2018-10-08 00:14:56 +08:00
except Exception as err :
2019-12-24 02:52:56 +02:00
bu . error ( exception = err )
2018-10-08 00:14:56 +08:00
return False
2019-12-24 02:52:56 +02:00
def get_results ( self ):
2018-10-08 00:14:56 +08:00
if not self . results_ready :
2019-12-24 02:52:56 +02:00
self . calc_results ()
2018-10-08 00:14:56 +08:00
results = []
try :
for field in result_fields . keys ():
results . append ( result_fields [ field ][ 3 ] . format ( self . results [ field ]) )
return results
except KeyError as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'Key not found' , err )
2018-10-08 00:14:56 +08:00
except Exception as err :
2019-12-24 02:52:56 +02:00
bu . error ( exception = err )
2018-10-08 00:14:56 +08:00
return None
2019-12-24 02:52:56 +02:00
def print_results ( self ):
print ( ' : ' . join ( self . get_results ()))
2018-10-08 00:14:56 +08:00
2019-12-18 00:19:21 +02:00
2018-10-08 00:14:56 +08:00
class BattleRecordCategory ():
def __init__ ( self , cat_name : str ):
self . category_name = cat_name
2019-12-24 02:52:56 +02:00
self . category = collections . defaultdict ( def_value_BattleRecord )
2018-12-14 23:33:38 +02:00
if result_categories [ self . category_name ][ 1 ] == 'string' :
self . type = 'string'
elif result_categories [ self . category_name ][ 1 ] == 'number' :
self . type = 'number'
elif result_categories [ self . category_name ][ 1 ] == 'total' :
self . type = 'total'
else :
self . type = 'category'
2018-10-08 00:14:56 +08:00
2019-12-24 02:52:56 +02:00
def get_sub_categories ( self ):
2018-10-08 00:14:56 +08:00
return self . category . keys ()
2019-12-24 02:52:56 +02:00
def record_result ( self , result : dict ):
2018-10-08 00:14:56 +08:00
try :
2018-12-14 23:33:38 +02:00
if self . type == 'total' :
2018-10-08 00:14:56 +08:00
cat = 'Total'
2018-12-14 23:33:38 +02:00
elif self . type == 'number' :
2018-10-08 00:14:56 +08:00
cat = str ( result [ self . category_name ])
2018-12-14 23:33:38 +02:00
elif self . type == 'string' :
cat = result [ self . category_name ]
2018-10-08 00:14:56 +08:00
else :
cat = result_categories [ self . category_name ][ 1 ][ result [ self . category_name ]]
2019-12-24 02:52:56 +02:00
self . category [ cat ] . record_result ( result )
2018-10-08 00:14:56 +08:00
return True
except KeyError as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'Key not found' , err )
2018-10-08 00:14:56 +08:00
except Exception as err :
bu . error ( str ( err ))
return False
2019-12-24 02:52:56 +02:00
def get_headers ( self ):
2018-10-08 00:14:56 +08:00
try :
2019-12-24 02:52:56 +02:00
headers = [ RESULT_CAT_HEADER_FRMT . format ( result_categories [ self . category_name ][ 0 ]) ]
2018-10-08 00:14:56 +08:00
for field in result_fields . keys ():
print_format = '{:^' + str ( result_fields [ field ][ 2 ]) + 's}'
headers . append ( print_format . format ( result_fields [ field ][ 0 ]))
return headers
except KeyError as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'Key not found' , err )
2018-10-08 00:14:56 +08:00
except Exception as err :
bu . error ( str ( err ))
return None
2019-12-24 02:52:56 +02:00
def print_results ( self ):
2018-10-08 00:14:56 +08:00
try :
2019-12-24 02:52:56 +02:00
print ( ' ' . join ( self . get_headers ()))
for row in self . get_results ():
2018-10-08 00:14:56 +08:00
print ( ' : ' . join ( row ))
except KeyError as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'Key not found' , err )
2018-10-08 00:14:56 +08:00
except Exception as err :
bu . error ( str ( err ))
return None
2019-12-24 02:52:56 +02:00
def get_results ( self ):
2018-10-08 00:14:56 +08:00
try :
results = []
2019-12-24 02:52:56 +02:00
# results.append(self.get_headers())
2018-12-14 23:33:38 +02:00
if self . type == 'number' :
for cat in sorted ( [ int ( s ) for s in self . category . keys () ] ):
cat = str ( cat )
2019-12-24 02:52:56 +02:00
row = [ RESULT_CAT_FRMT . format ( cat ) ]
row . extend ( self . category [ cat ] . get_results ())
2018-12-14 23:33:38 +02:00
results . append ( row )
else :
for cat in sorted ( self . category . keys () , key = str . casefold ):
2019-12-24 02:52:56 +02:00
row = [ RESULT_CAT_FRMT . format ( cat ) ]
row . extend ( self . category [ cat ] . get_results ())
2018-12-14 23:33:38 +02:00
results . append ( row )
2018-10-08 00:14:56 +08:00
return results
except KeyError as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'Key not found' , err )
2018-10-08 00:14:56 +08:00
return None
## main() -------------------------------------------------------------
async def main ( argv ):
2018-11-05 14:48:07 +02:00
global wg , wi
2019-12-18 00:19:21 +02:00
# set the directory for the script
os . chdir ( os . path . dirname ( sys . argv [ 0 ]))
## Read config
config = configparser . ConfigParser ()
config . read ( FILE_CONFIG )
2020-02-08 12:07:26 +02:00
configGeneral = config [ 'GENERAL' ]
# WG account id of the uploader:
# # Find it here: https://developers.wargaming.net/reference/all/wotb/account/list/
WG_ID = configGeneral . getint ( 'wg_id' , None )
USE_DB = configGeneral . getboolean ( 'use_DB' , False )
2019-12-18 00:19:21 +02:00
configDB = config [ 'DATABASE' ]
DB_SERVER = configDB . get ( 'db_server' , 'localhost' )
DB_PORT = configDB . getint ( 'db_port' , 27017 )
DB_SSL = configDB . getboolean ( 'db_ssl' , False )
DB_CERT_REQ = configDB . getint ( 'db_ssl_req' , ssl . CERT_NONE )
DB_AUTH = configDB . get ( 'db_auth' , 'admin' )
DB_NAME = configDB . get ( 'db_name' , 'BlitzStats' )
DB_USER = configDB . get ( 'db_user' , 'mongouser' )
DB_PASSWD = configDB . get ( 'db_password' , "PASSWORD" )
DB_CERT = configDB . get ( 'db_ssl_cert_file' , None )
DB_CA = configDB . get ( 'db_ssl_ca_file' , None )
2018-10-08 00:14:56 +08:00
TASK_N = 7
2018-11-05 14:48:07 +02:00
2019-12-24 02:52:56 +02:00
parser = argparse . ArgumentParser ( description = 'Analyze Blitz replay JSONs from WoTinspector.com' )
2018-10-08 00:14:56 +08:00
parser . add_argument ( '--output' , default = 'plain' , choices = [ 'json' , 'plain' , 'db' ], help = 'Select output mode: JSON, plain text or database' )
2020-02-08 12:07:26 +02:00
parser . add_argument ( '-id' , dest = 'account_id' , type = int , default = WG_ID , help = 'WG account_id to analyze' )
2019-12-25 02:55:47 +02:00
parser . add_argument ( '-a' , '--account' , type = str , default = None , help = 'WG account nameto analyze. Format: ACCOUNT_NAME@SERVER' )
2019-08-17 00:50:48 +03:00
parser . add_argument ( '-x' , '--extended' , action = 'store_true' , default = False , help = 'Print Extended stats' )
2019-12-24 12:30:09 +02:00
parser . add_argument ( '--hist' , action = 'store_true' , default = False , help = 'Print player histograms (WR/battles)' )
2019-12-24 02:52:56 +02:00
parser . add_argument ( '--stat_func' , default = 'tank_tier' , choices = STAT_FUNC . keys (), help = 'Select how to calculate for ally/enemy performance: tank-tier stats, global player stats' )
2019-12-25 02:55:47 +02:00
parser . add_argument ( '-u' , '--url' , action = 'store_true' , default = False , help = 'Print replay URLs' )
2018-10-08 00:20:48 +08:00
parser . add_argument ( '--tankfile' , type = str , default = 'tanks.json' , help = 'JSON file to read Tankopedia from. Default is "tanks.json"' )
parser . add_argument ( '--mapfile' , type = str , default = 'maps.json' , help = 'JSON file to read Blitz map names from. Default is "maps.json"' )
2018-10-08 00:14:56 +08:00
parser . add_argument ( '-o' , '--outfile' , type = str , default = '-' , metavar = "OUTPUT" , help = 'File to write results. Default STDOUT' )
2020-02-08 12:07:26 +02:00
parser . add_argument ( '--db' , action = 'store_true' , default = USE_DB , help = 'Use DB - You are unlikely to have it' )
2018-10-08 00:14:56 +08:00
parser . add_argument ( '-d' , '--debug' , action = 'store_true' , default = False , help = 'Debug mode' )
parser . add_argument ( '-v' , '--verbose' , action = 'store_true' , default = False , help = 'Verbose mode' )
2019-12-24 12:30:09 +02:00
parser . add_argument ( '-s' , '--silent' , action = 'store_true' , default = False , help = 'Silent mode' )
2018-11-05 14:48:07 +02:00
parser . add_argument ( 'files' , metavar = 'FILE1 [FILE2 ...]' , type = str , nargs = '+' , help = 'Files to read. Use \' - \' for STDIN"' )
2018-10-08 00:14:56 +08:00
args = parser . parse_args ()
2019-12-24 12:30:09 +02:00
bu . set_log_level ( args . silent , args . verbose , args . debug )
bu . set_progress_step ( 30 ) # Set the frequency of the progress dots.
2019-12-24 02:52:56 +02:00
wg = await WG . create ( WG_APP_ID , args . tankfile , args . mapfile , True )
2018-11-05 14:48:07 +02:00
wi = WoTinspector ()
2019-12-24 12:30:09 +02:00
2018-10-24 12:31:37 +03:00
if args . account != None :
2019-12-25 02:55:47 +02:00
args . account_id = await wg . get_account_id ( args . account )
bu . debug ( 'WG account_id: ' + str ( args . account_id ))
2018-10-08 00:14:56 +08:00
2019-12-18 00:19:21 +02:00
#### Connect to MongoDB (TBD)
2019-12-24 12:30:09 +02:00
bu . debug ( 'DB_SERVER: ' + DB_SERVER )
bu . debug ( 'DB_PORT: ' + str ( DB_PORT ))
bu . debug ( 'DB_SSL: ' + "True" if DB_SSL else "False" )
bu . debug ( 'DB_AUTH: ' + DB_AUTH )
bu . debug ( 'DB_NAME: ' + DB_NAME )
2019-12-18 00:19:21 +02:00
client = None
db = None
if args . db :
try :
client = motor . motor_asyncio . AsyncIOMotorClient ( DB_SERVER , DB_PORT , authSource = DB_AUTH , username = DB_USER , password = DB_PASSWD , ssl = DB_SSL , ssl_cert_reqs = DB_CERT_REQ , ssl_certfile = DB_CERT , tlsCAFile = DB_CA )
db = client [ DB_NAME ]
bu . debug ( 'Database connection initiated' )
except Exception as err :
2019-12-24 02:52:56 +02:00
bu . error ( "Could no initiate DB connection: Disabling DB" , err )
2019-12-18 00:19:21 +02:00
args . db = False
pass
else :
bu . debug ( 'No DB in use' )
2018-10-08 00:14:56 +08:00
try :
2020-02-08 12:11:02 +02:00
replayQ = asyncio . Queue ( maxsize = 1000 )
2018-10-08 00:14:56 +08:00
reader_tasks = []
# Make replay Queue
2020-02-08 12:11:02 +02:00
2019-12-24 02:52:56 +02:00
scanner_task = asyncio . create_task ( mk_replayQ ( replayQ , args . files ))
2019-08-17 00:50:48 +03:00
bu . debug ( 'Replay scanner started' )
2018-10-08 00:14:56 +08:00
# Start tasks to process the Queue
for i in range ( TASK_N ):
2019-12-24 02:52:56 +02:00
reader_tasks . append ( asyncio . create_task ( replay_reader ( replayQ , i , args )))
2019-08-17 00:50:48 +03:00
bu . debug ( 'ReplayReader ' + str ( i ) + ' started' )
2018-10-08 00:14:56 +08:00
bu . debug ( 'Waiting for the replay scanner to finish' )
await asyncio . wait ([ scanner_task ])
2019-08-17 00:50:48 +03:00
2018-10-08 00:14:56 +08:00
bu . debug ( 'Scanner finished. Waiting for replay readers to finish the queue' )
await replayQ . join ()
2019-08-17 00:50:48 +03:00
await asyncio . sleep ( 0.1 )
2018-10-08 00:14:56 +08:00
bu . debug ( 'Replays read. Cancelling Readers and analyzing results' )
for task in reader_tasks :
2019-08-17 00:50:48 +03:00
task . cancel ()
await asyncio . sleep ( 0.1 )
2018-10-08 00:14:56 +08:00
results = []
2019-12-18 00:19:21 +02:00
players = set ()
2018-10-08 00:14:56 +08:00
for res in await asyncio . gather ( * reader_tasks ):
results . extend ( res [ 0 ])
2019-12-18 00:19:21 +02:00
players . update ( res [ 1 ])
2018-10-08 00:14:56 +08:00
2019-12-24 02:52:56 +02:00
( player_stats , stat_id_map ) = await process_player_stats ( players , TASK_N , args , db )
2018-10-08 00:14:56 +08:00
bu . verbose ( '' )
2019-12-24 02:52:56 +02:00
bu . debug ( 'Number of player stats: ' + str ( len ( player_stats )))
teamresults = calc_team_stats ( results , player_stats , stat_id_map , args )
process_battle_results ( teamresults , args )
2020-02-08 12:07:26 +02:00
if args . hist :
print ( ' \n Player Historgrams______' , end = '' , flush = True )
2019-12-24 02:52:56 +02:00
process_player_dist ( results , player_stats , stat_id_map )
bu . debug ( 'Finished. Cleaning up..................' )
2019-08-17 00:50:48 +03:00
except Exception as err :
2019-12-24 02:52:56 +02:00
bu . error ( exception = err )
2018-10-08 00:14:56 +08:00
finally :
## Need to close the aiohttp.session since Python destructors do not support async methods...
2018-11-05 14:48:07 +02:00
await wg . close ()
await wi . close ()
2018-10-08 00:14:56 +08:00
return None
2019-12-24 02:52:56 +02:00
def process_player_dist ( results : list , player_stats : dict , stat_id_map : dict ):
"""Process player distribution"""
2019-12-18 00:19:21 +02:00
hist_stats = dict ()
2019-12-24 02:52:56 +02:00
try :
for field in histogram_fields :
hist_stats [ field ] = PlayerHistogram ( field , histogram_fields [ field ][ 0 ], histogram_fields [ field ][ 1 ], histogram_fields [ field ][ 2 ], histogram_fields [ field ][ 3 ] )
2019-12-18 00:19:21 +02:00
2019-12-24 02:52:56 +02:00
for result in results :
for player in result [ 'allies' ] | result [ 'enemies' ]: # uniton of Sets
player_remapped = stat_id_map [ player ]
if player_remapped in player_stats :
if player in result [ 'allies' ]:
for stat_field in hist_stats :
hist_stats [ stat_field ] . record_ally ( player_stats [ player_remapped ][ stat_field ])
else :
for stat_field in hist_stats :
hist_stats [ stat_field ] . record_enemy ( player_stats [ player_remapped ][ stat_field ])
for stat_field in hist_stats :
hist_stats [ stat_field ] . print ()
2019-12-18 00:19:21 +02:00
2019-12-24 02:52:56 +02:00
except Exception as err :
bu . error ( exception = err )
2019-09-09 23:31:52 +03:00
return None
2019-12-24 02:52:56 +02:00
def process_battle_results ( results : dict , args : argparse . Namespace ):
"""Process replay battle results"""
2019-12-18 00:19:21 +02:00
url = args . url
urls = collections . OrderedDict ()
2018-10-08 00:14:56 +08:00
categories = {}
2019-08-17 00:50:48 +03:00
cats = result_categories_default
if args . extended :
cats = result_categories . keys ()
for cat in cats :
2018-10-08 00:14:56 +08:00
categories [ cat ] = BattleRecordCategory ( cat )
2018-10-08 15:14:59 +08:00
max_title_len = 0
2018-10-08 00:14:56 +08:00
for result in results :
2019-08-17 00:50:48 +03:00
for cat in cats :
2019-12-24 02:52:56 +02:00
categories [ cat ] . record_result ( result )
2018-10-08 15:14:59 +08:00
if url :
max_title_len = max ( max_title_len , len ( result [ 'title' ]))
urls [ result [ 'title' ]] = result [ 'url' ]
2018-10-08 00:14:56 +08:00
2019-08-17 00:50:48 +03:00
for cat in cats :
2019-12-24 02:52:56 +02:00
for sub_cat in categories [ cat ] . get_sub_categories ():
categories [ cat ] . category [ sub_cat ] . calc_results ()
2018-10-08 00:14:56 +08:00
print ( '' )
2019-12-24 02:52:56 +02:00
categories [ cat ] . print_results ()
2018-10-08 15:14:59 +08:00
if url :
print ( ' \n URLs to Battle replays: \n ' )
for title in urls . keys ():
print (( '{:' + str ( 3 + max_title_len ) + '}' ) . format ( title ) + ' : ' + urls [ title ])
2018-10-08 00:14:56 +08:00
return None
2019-12-24 02:52:56 +02:00
async def process_player_stats ( players , N_workers : int , args : argparse . Namespace , db : motor . motor_asyncio . AsyncIOMotorDatabase ) -> dict :
"""Start stat_workers to retrieve and store players' stats"""
2018-10-08 00:14:56 +08:00
## Create queue of player-tank pairs to find stats for
statsQ = asyncio . Queue ()
2019-12-18 00:19:21 +02:00
bu . debug ( 'Create player stats queue: ' + str ( len ( players )) + ' players' )
2019-12-24 02:52:56 +02:00
stat_id_map = {}
stat_ids = set ()
stat_id_map_func = globals ()[ STAT_FUNC [ args . stat_func ][ 0 ]]
2019-12-18 00:19:21 +02:00
for player in players :
2019-12-24 02:52:56 +02:00
stat_id_map [ player ] = stat_id_map_func ( player )
stat_ids . add ( stat_id_map [ player ])
# create statsQ
for stat_id in stat_ids :
await statsQ . put ( stat_id )
2018-10-08 00:14:56 +08:00
# Process player WR / battle stats
stats_tasks = []
2019-12-18 00:19:21 +02:00
bu . debug ( 'Starting player stats workers' )
2018-10-08 00:14:56 +08:00
for i in range ( N_workers ):
2019-08-17 00:50:48 +03:00
bu . debug ( "Starting worker " + str ( i ))
2019-12-24 02:52:56 +02:00
stats_tasks . append ( asyncio . create_task ( stat_worker ( statsQ , i , args , db )))
2019-12-18 00:19:21 +02:00
bu . debug ( 'Waiting stats workers to finish' )
2018-10-08 00:14:56 +08:00
await statsQ . join ()
2019-08-17 00:50:48 +03:00
bu . debug ( 'Cancelling stats workers' )
2018-10-08 00:14:56 +08:00
for task in stats_tasks :
task . cancel ()
player_stats = {}
2019-12-24 02:52:56 +02:00
stat_id_remap = {}
2018-10-08 00:14:56 +08:00
2019-12-24 02:52:56 +02:00
bu . debug ( 'Gathering stats worker outputs' )
for ( stats , id_remap ) in await asyncio . gather ( * stats_tasks ):
player_stats = { ** player_stats , ** stats }
stat_id_remap = { ** stat_id_remap , ** id_remap }
## DO REMAPPING
stat_id_map = remap_stat_id ( stat_id_map , stat_id_remap )
bu . debug ( 'Returning player_stats' )
return ( player_stats , stat_id_map )
def remap_stat_id ( stat_id_map : dict , stat_id_remap : dict ):
bu . debug ( 'Remapping stat_ids: stat_id_map: ' + str ( len ( stat_id_map )) + ' remap needed: ' + str ( len ( stat_id_remap )))
for stat_id in stat_id_map :
# bu.debug(stat_id)
if stat_id_map [ stat_id ] in stat_id_remap :
stat_id_map [ stat_id ] = stat_id_remap [ stat_id_map [ stat_id ]]
return stat_id_map
def calc_team_stats ( results : list , player_stats : dict , stat_id_map : dict , args : argparse . Namespace ) -> list :
"""Calculate team stats"""
2018-10-08 00:14:56 +08:00
return_list = []
2019-12-24 02:52:56 +02:00
2018-10-08 00:14:56 +08:00
stat_types = player_stats [ list ( player_stats . keys ())[ 0 ]] . keys ()
2019-12-18 00:19:21 +02:00
bu . debug ( 'stat_types: ' + ',' . join ( stat_types ))
2019-12-24 02:52:56 +02:00
for result in results :
2018-10-08 00:14:56 +08:00
try :
2019-12-24 02:52:56 +02:00
n_allies = collections . defaultdict ( def_value_zero )
allies_stats = collections . defaultdict ( def_value_zero )
#bu.debug('Processing Allies')
2018-10-08 00:14:56 +08:00
for ally in result [ 'allies' ]:
2019-12-24 02:52:56 +02:00
# Player itself is not in 'allies': see read_replay_JSON()
ally_mapped = stat_id_map [ ally ]
if ally_mapped not in player_stats :
continue
2018-10-08 00:14:56 +08:00
for stat in stat_types :
2019-12-24 02:52:56 +02:00
if player_stats [ ally_mapped ][ stat ] != None :
allies_stats [ stat ] += player_stats [ ally_mapped ][ stat ]
2018-10-08 21:24:46 +08:00
n_allies [ stat ] += 1
2019-12-24 02:52:56 +02:00
#bu.debug('Processing Allies\' avg stats')
2018-10-08 00:14:56 +08:00
for stat in stat_types :
2019-12-18 00:19:21 +02:00
if n_allies [ stat ] > 0 :
result [ 'allies_' + str ( stat )] = allies_stats [ stat ] / n_allies [ stat ]
2019-12-24 02:52:56 +02:00
#bu.debug('Processing Enemies')
n_enemies = collections . defaultdict ( def_value_zero )
enemies_stats = collections . defaultdict ( def_value_zero )
2018-10-08 00:14:56 +08:00
for enemy in result [ 'enemies' ]:
2019-12-24 02:52:56 +02:00
enemy_mapped = stat_id_map [ enemy ]
if enemy_mapped not in player_stats : continue
2018-10-08 00:14:56 +08:00
for stat in stat_types :
2019-12-24 02:52:56 +02:00
if player_stats [ enemy_mapped ][ stat ] != None :
enemies_stats [ stat ] += player_stats [ enemy_mapped ][ stat ]
2018-10-08 21:24:46 +08:00
n_enemies [ stat ] += 1
2019-12-24 02:52:56 +02:00
#bu.debug('Processing Enemies\' avg stats')
2018-10-08 00:14:56 +08:00
for stat in stat_types :
2019-12-18 00:19:21 +02:00
if n_enemies [ stat ] > 0 :
result [ 'enemies_' + str ( stat )] = enemies_stats [ stat ] / n_enemies [ stat ]
2018-10-08 00:14:56 +08:00
return_list . append ( result )
except KeyError as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'Key not found' , err )
2018-10-08 00:14:56 +08:00
except Exception as err :
2019-12-24 02:52:56 +02:00
bu . error ( exception = err )
2018-10-08 00:14:56 +08:00
return return_list
2019-12-24 02:52:56 +02:00
async def stat_worker ( queue : asyncio . Queue , workerID : int , args : argparse . Namespace , db : motor . motor_asyncio . AsyncIOMotorDatabase ) -> list :
2018-10-08 00:14:56 +08:00
"""Worker thread to find stats for player / tank pairs"""
# global wg
2019-12-24 02:52:56 +02:00
stats = {}
stat_id_remap = {}
stat_db_func = globals ()[ STAT_FUNC [ args . stat_func ][ 1 ]]
stat_wg_func = globals ()[ STAT_FUNC [ args . stat_func ][ 2 ]]
2019-08-17 00:50:48 +03:00
bu . debug ( "workedID: " + str ( workerID ) + ' started' )
2018-10-08 00:14:56 +08:00
try :
while True :
2019-12-24 02:52:56 +02:00
stat_id = await queue . get ()
2018-10-08 00:14:56 +08:00
try :
2019-12-24 02:52:56 +02:00
bu . debug ( 'Stat_id: ' + stat_id )
bu . print_progress ()
# Analysing player performance based on their stats on the tier tanks they are playing
2019-12-18 00:19:21 +02:00
2019-12-24 02:52:56 +02:00
stats [ stat_id ] = await stat_db_func ( db , stat_id )
bu . debug ( 'get_db_' + args . stat_func + '_stats returned: ' + str ( stats [ stat_id ]), workerID )
if ( stats [ stat_id ] == None ):
pruned_stat_id = prune_stat_id ( stat_id )
if ( pruned_stat_id not in stats ):
stats [ pruned_stat_id ] = await stat_wg_func ( pruned_stat_id )
stat_id_remap [ stat_id ] = pruned_stat_id
del stats [ stat_id ]
2018-10-08 00:14:56 +08:00
except KeyError as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'Key not found' , err , workerID )
2018-10-08 00:14:56 +08:00
except Exception as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'Unexpected error' , err , workerID )
2018-10-08 00:14:56 +08:00
queue . task_done ()
2019-08-17 00:50:48 +03:00
except ( asyncio . CancelledError , concurrent . futures . _base . CancelledError ):
2019-12-24 02:52:56 +02:00
bu . debug ( 'Stats queue is empty' , workerID )
2018-10-08 00:14:56 +08:00
except Exception as err :
2019-12-24 02:52:56 +02:00
bu . error ( exception = err )
finally :
# remove empty stats
try :
keys_2_del = []
for key in stats :
if stats [ key ] == None :
keys_2_del . append ( key )
for key in keys_2_del :
del stats [ key ]
except KeyError as err :
bu . error ( 'Error in pruning empty stats' , err )
# bu.debug('Returning stats & exiting')
return ( stats , stat_id_remap )
2018-10-08 00:14:56 +08:00
2019-12-24 02:52:56 +02:00
## player stat functions: tank_tier
async def get_wg_tank_tier_stats ( stat_id_str : str ) -> dict :
2019-12-18 00:19:21 +02:00
"""Get player stats from WG. Returns WR per tier of tank_id"""
try :
2019-12-24 02:52:56 +02:00
( account_id , tier ) = str2ints ( stat_id_str )
tier_tanks = wg . get_tanks_by_tier ( tier )
2019-12-18 00:19:21 +02:00
# 'battles' must always be there
hist_stats = [ 'all.' + x for x in histogram_fields . keys () ]
2019-12-24 02:52:56 +02:00
hist_stats . append ( 'tank_id' )
2019-12-18 00:19:21 +02:00
2020-02-08 12:07:26 +02:00
player_stats = await wg . get_player_tank_stats ( account_id , tier_tanks , hist_stats )
2019-12-24 02:52:56 +02:00
#bu.debug('account_id: ' + str(account_id) + ' ' + str(player_stats))
2019-12-18 00:19:21 +02:00
2019-12-24 02:52:56 +02:00
return await tank_stats_helper ( player_stats )
2019-12-18 00:19:21 +02:00
except KeyError as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'account_id: ' + str ( account_id ) + ' Key not found' , err )
2019-12-18 00:19:21 +02:00
except Exception as err :
2019-12-24 02:52:56 +02:00
bu . error ( exception = err )
2019-12-18 00:19:21 +02:00
return None
2019-12-24 02:52:56 +02:00
async def get_db_tank_tier_stats ( db : motor . motor_asyncio . AsyncIOMotorDatabase , stat_id_str : str ) -> dict :
2019-12-18 00:19:21 +02:00
"""Get player stats from MongoDB (you are very unlikely to have it, unless you have set it up)"""
if db == None :
return None
try :
dbc = db [ DB_C_TANK_STATS ]
2019-12-24 02:52:56 +02:00
( account_id , tier , battletime ) = str2ints ( stat_id_str )
tier_tanks = wg . get_tanks_by_tier ( tier )
time_buffer = 2 * 7 * 24 * 3600
2019-12-18 00:19:21 +02:00
#bu.debug('Tank_ids: ' + ','.join(map(str, tier_tanks)))
2019-12-24 02:52:56 +02:00
# if battletime == None:
# pipeline = [ { '$match': { '$and': [ { 'account_id': account_id }, { 'tank_id' : {'$in': tier_tanks} } ]}},
# { '$sort': { 'last_battle_time': -1 }},
# { '$group': { '_id': '$tank_id', 'doc': { '$first': '$$ROOT' }}},
# { '$replaceRoot': { 'newRoot': '$doc' }},
# { '$project': { '_id': 0 }} ]
# else:
pipeline = [ { '$match' : { '$and' : [ { 'account_id' : account_id }, { 'last_battle_time' : { '$lte' : battletime + time_buffer }}, { 'tank_id' : { '$in' : tier_tanks } } ]}},
{ '$sort' : { 'last_battle_time' : - 1 }},
{ '$group' : { '_id' : '$tank_id' , 'doc' : { '$first' : '$$ROOT' }}},
{ '$replaceRoot' : { 'newRoot' : '$doc' }},
{ '$project' : { '_id' : 0 }} ]
2019-12-18 00:19:21 +02:00
# cursor = dbc.aggregate(pipeline, allowDiskUse=True)
cursor = dbc . aggregate ( pipeline )
2019-12-24 02:52:56 +02:00
return await tank_stats_helper ( await cursor . to_list ( 1000 ))
2019-12-18 00:19:21 +02:00
except Exception as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'account_id: ' + str ( account_id ) + ' Error' , err )
2019-12-18 00:19:21 +02:00
return None
2019-12-24 02:52:56 +02:00
## player stat functions: player
async def get_db_player_stats ( db : motor . motor_asyncio . AsyncIOMotorDatabase , stat_id_str : str ) -> dict :
"""Get player stats from MongoDB (you are very unlikely to have it, unless you have set it up)"""
if db == None :
return None
try :
dbc = db [ DB_C_TANK_STATS ] ## not collecting player stats yet 2019-12-23
( account_id , battletime ) = str2ints ( stat_id_str )
time_buffer = 2 * 7 * 24 * 3600
pipeline = [ { '$match' : { '$and' : [ { 'account_id' : account_id }, { 'last_battle_time' : { '$lte' : battletime + time_buffer }} ]}},
{ '$sort' : { 'last_battle_time' : - 1 }},
{ '$group' : { '_id' : '$tank_id' , 'doc' : { '$first' : '$$ROOT' }}},
{ '$replaceRoot' : { 'newRoot' : '$doc' }},
{ '$project' : { '_id' : 0 }} ]
# cursor = dbc.aggregate(pipeline, allowDiskUse=True)
cursor = dbc . aggregate ( pipeline )
return await tank_stats_helper ( await cursor . to_list ( 1000 ))
except Exception as err :
bu . error ( 'account_id: ' + str ( account_id ) + ' Error' , err )
return None
async def get_wg_player_stats ( stat_id_str : str ) -> dict :
"""Get player stats from WG. Returns WR per tier of tank_id"""
try :
account_id = int ( stat_id_str )
# 'battles' must always be there
hist_stats = [ 'all.' + x for x in histogram_fields . keys () ]
hist_stats . append ( 'tank_id' )
player_stats = await wg . get_player_stats ( account_id , hist_stats )
#bu.debug('account_id: ' + str(account_id) + ' ' + str(player_stats))
return await player_stats_helper ( player_stats )
except KeyError as err :
bu . error ( 'account_id: ' + str ( account_id ) + ' Key not found' , err )
except Exception as err :
bu . error ( exception = err )
return None
async def tank_stats_helper ( stat_list : list ):
"""Helpher func got get_db_tank_tier_stats() and get_wg_tank_tier_stats()"""
2019-12-18 00:19:21 +02:00
try :
if stat_list == None :
return None
stats = {}
hist_fields = histogram_fields . keys ()
if 'battles' not in hist_fields :
bu . error ( ' \' battles \' must be defined in \' histogram_fields \' ' )
sys . exit ( 1 )
2019-12-24 02:52:56 +02:00
2019-12-18 00:19:21 +02:00
for field in hist_fields :
stats [ field ] = 0
2019-12-24 02:52:56 +02:00
for tank_stat in stat_list :
tank_stat = tank_stat [ 'all' ]
2019-12-18 00:19:21 +02:00
for field in hist_fields :
2019-12-24 02:52:56 +02:00
stats [ field ] += max ( 0 , tank_stat [ field ])
2019-12-18 00:19:21 +02:00
if stats [ 'battles' ] == 0 :
return None
for field in hist_fields :
if field == 'battles' : continue
stats [ field ] = stats [ field ] / stats [ 'battles' ]
return stats
except Exception as err :
2019-12-24 02:52:56 +02:00
bu . error ( exception = err )
2019-12-18 00:19:21 +02:00
return None
2019-12-24 02:52:56 +02:00
async def player_stats_helper ( player_stats : dict ):
"""Extract relevant stats for player histogram from raw player stats"""
try :
if player_stats == None :
return None
stats = {}
hist_fields = histogram_fields . keys ()
if 'battles' not in hist_fields :
bu . error ( ' \' battles \' must be defined in \' histogram_fields \' ' )
sys . exit ( 1 )
for field in hist_fields :
stats [ field ] = 0
for field in hist_fields :
stats [ field ] += max ( 0 , player_stats [ 'statistics' ][ 'all' ][ field ])
if stats [ 'battles' ] == 0 :
return None
for field in hist_fields :
if field == 'battles' : continue
stats [ field ] = stats [ field ] / stats [ 'battles' ]
return stats
except Exception as err :
bu . error ( exception = err )
return None
async def mk_replayQ ( queue : asyncio . Queue , files : list ):
2018-10-08 00:14:56 +08:00
"""Create queue of replays to post"""
p_replayfile = re . compile ( '.* \\ .wotbreplay \\ .json$' )
if files [ 0 ] == '-' :
bu . debug ( 'reading replay file list from STDIN' )
stdin , _ = await aioconsole . get_standard_streams ()
while True :
line = ( await stdin . readline ()) . decode ( 'utf-8' ) . rstrip ()
if not line :
break
else :
if ( p_replayfile . match ( line ) != None ):
2019-12-24 02:52:56 +02:00
await queue . put ( await mk_readerQ_item ( line ))
2018-10-08 00:14:56 +08:00
else :
for fn in files :
2019-08-17 00:50:48 +03:00
# bu.debug('Filename: ' + fn)
2018-11-05 18:15:51 +02:00
if fn . endswith ( '"' ):
fn = fn [: - 1 ]
2018-10-08 00:14:56 +08:00
if os . path . isfile ( fn ) and ( p_replayfile . match ( fn ) != None ):
2019-12-24 02:52:56 +02:00
await queue . put ( await mk_readerQ_item ( fn ))
2019-08-17 00:50:48 +03:00
bu . debug ( 'File added to queue: ' + fn )
2018-10-08 00:14:56 +08:00
elif os . path . isdir ( fn ):
with os . scandir ( fn ) as dirEntry :
for entry in dirEntry :
if entry . is_file () and ( p_replayfile . match ( entry . name ) != None ):
bu . debug ( entry . name )
2019-12-24 02:52:56 +02:00
await queue . put ( await mk_readerQ_item ( entry . path ))
2019-08-17 00:50:48 +03:00
bu . debug ( 'File added to queue: ' + entry . path )
bu . debug ( 'Finished: ' + str ( queue . qsize ()) + ' replays to process' )
2018-10-08 00:14:56 +08:00
return None
2019-12-24 02:52:56 +02:00
async def mk_readerQ_item ( filename : str ) -> list :
2018-10-08 00:14:56 +08:00
"""Make an item to replay queue"""
global REPLAY_N
REPLAY_N += 1
return [ filename , REPLAY_N ]
2019-12-24 02:52:56 +02:00
async def replay_reader ( queue : asyncio . Queue , readerID : int , args : argparse . Namespace ):
2018-10-08 00:14:56 +08:00
"""Async Worker to process the replay queue"""
#global SKIPPED_N
2019-12-25 02:55:47 +02:00
# account_id = args.account_id
2018-10-08 00:14:56 +08:00
results = []
playerstanks = set ()
try :
while True :
item = await queue . get ()
filename = item [ 0 ]
replayID = item [ 1 ]
try :
2019-12-24 02:52:56 +02:00
replay_json = await bu . open_JSON ( filename , wi . chk_JSON_replay )
2018-11-05 14:48:07 +02:00
if replay_json == None :
bu . verbose ( 'Replay[' + str ( replayID ) + ']: ' + filename + ' is invalid. Skipping.' )
#SKIPPED_N += 1
queue . task_done ()
continue
2018-10-08 00:14:56 +08:00
2018-11-05 14:48:07 +02:00
## Read the replay JSON
2019-12-24 02:52:56 +02:00
bu . debug ( 'reading replay: ' + filename , readerID )
result = await read_replay_JSON ( replay_json , args )
bu . print_progress ()
2018-11-05 14:48:07 +02:00
if result == None :
2019-12-24 02:52:56 +02:00
bu . error ( 'Replay[' + str ( replayID ) + ']: Error reading: ' + filename , id = readerID )
2018-11-05 14:48:07 +02:00
queue . task_done ()
continue
2019-12-18 00:19:21 +02:00
# if (account_id != None):
playerstanks . update ( set ( result [ 'allies' ]))
playerstanks . update ( set ( result [ 'enemies' ]))
2018-11-05 14:48:07 +02:00
results . append ( result )
bu . debug ( 'Marking task ' + str ( replayID ) + ' done' )
2018-10-08 00:14:56 +08:00
except Exception as err :
2019-12-24 02:52:56 +02:00
bu . error ( exception = err )
2018-10-08 00:14:56 +08:00
queue . task_done ()
2019-08-17 00:50:48 +03:00
except ( asyncio . CancelledError , concurrent . futures . CancelledError ):
2019-12-24 02:52:56 +02:00
bu . debug ( str ( len ( results )) + ' replays, ' + str ( len ( playerstanks )) + ' player/tanks' , readerID )
2018-10-08 00:14:56 +08:00
return results , playerstanks
return None
2019-12-24 02:52:56 +02:00
async def read_replay_JSON ( replay_json : dict , args : argparse . Namespace ) -> dict :
2018-10-08 00:14:56 +08:00
"""Parse replay JSON dict"""
2019-08-17 00:50:48 +03:00
global REPLAY_I
2019-12-25 02:55:47 +02:00
account_id = args . account_id
2018-10-08 00:14:56 +08:00
url = args . url
2019-12-24 02:52:56 +02:00
#db = args.db
2018-10-08 00:14:56 +08:00
result = {}
try :
if account_id == None :
account_id = replay_json [ 'data' ][ 'summary' ][ 'protagonist' ]
elif replay_json [ 'data' ][ 'summary' ][ 'protagonist' ] != account_id :
if account_id in replay_json [ 'data' ][ 'summary' ][ 'enemies' ]:
# switch the teams...
if replay_json [ 'data' ][ 'summary' ][ 'battle_result' ] != 2 :
if replay_json [ 'data' ][ 'summary' ][ 'battle_result' ] == 0 :
replay_json [ 'data' ][ 'summary' ][ 'battle_result' ] = 1
else :
replay_json [ 'data' ][ 'summary' ][ 'battle_result' ] = 0
tmp = replay_json [ 'data' ][ 'summary' ][ 'enemies' ]
replay_json [ 'data' ][ 'summary' ][ 'enemies' ] = replay_json [ 'data' ][ 'summary' ][ 'allies' ]
replay_json [ 'data' ][ 'summary' ][ 'allies' ] = tmp
elif account_id not in replay_json [ 'data' ][ 'summary' ][ 'allies' ]:
# account_id looked for but not found in teams
bu . debug ( 'Replay ' + replay_json [ 'data' ][ 'summary' ][ 'title' ] + ' does not have account_id ' + str ( account_id ) + '. Skipping.' )
return None
if url :
result [ 'url' ] = replay_json [ 'data' ][ 'view_url' ]
for key in replay_summary_flds :
result [ key ] = replay_json [ 'data' ][ 'summary' ][ key ]
2018-11-05 14:48:07 +02:00
result [ 'battle_start_timestamp' ] = int ( replay_json [ 'data' ][ 'summary' ][ 'battle_start_timestamp' ])
2018-10-08 00:14:56 +08:00
result [ 'allies' ] = set ()
result [ 'enemies' ] = set ()
btl_duration = 0
btl_tier = 0
for player in replay_json [ 'data' ][ 'summary' ][ 'details' ]:
btl_duration = max ( btl_duration , player [ 'time_alive' ])
2019-12-24 02:52:56 +02:00
player_tank_tier = wg . get_tank_data ( player [ 'vehicle_descr' ], 'tier' )
2018-10-08 00:14:56 +08:00
btl_tier = max ( btl_tier , player_tank_tier )
2019-12-24 02:52:56 +02:00
2018-10-08 00:14:56 +08:00
if player [ 'dbid' ] == account_id :
2019-12-18 00:19:21 +02:00
# player itself is not put in results['allies']
2018-10-08 00:14:56 +08:00
tmp = {}
2019-12-18 00:19:21 +02:00
tmp [ 'account_id' ] = account_id
tmp [ 'tank_id' ] = player [ 'vehicle_descr' ]
tmp [ 'tank_tier' ] = player_tank_tier
2019-12-24 02:52:56 +02:00
tmp [ 'tank_name' ] = wg . get_tank_data ( tmp [ 'tank_id' ], 'name' )
2019-12-18 00:19:21 +02:00
tmp [ 'squad_index' ] = player [ 'squad_index' ]
2018-10-08 00:14:56 +08:00
for key in replay_details_flds :
tmp [ key ] = player [ key ]
if player [ 'hitpoints_left' ] == 0 :
tmp [ 'survived' ] = 0
tmp [ 'destroyed' ] = 1
else :
tmp [ 'survived' ] = 1
tmp [ 'destroyed' ] = 0
for key in tmp . keys ():
result [ key ] = tmp [ key ]
else :
2019-12-18 00:19:21 +02:00
tmp_account_id = player [ 'dbid' ]
tmp_tank_id = player [ 'vehicle_descr' ]
2019-12-24 02:52:56 +02:00
# tmp_tank_tier = wg.get_tank_tier(tmp_tank_id)
2019-12-18 00:19:21 +02:00
tmp_battletime = int ( replay_json [ 'data' ][ 'summary' ][ 'battle_start_timestamp' ])
if player [ 'dbid' ] in replay_json [ 'data' ][ 'summary' ][ 'allies' ]:
2019-12-24 02:52:56 +02:00
result [ 'allies' ] . add ( get_stat_id ( tmp_account_id , tmp_tank_id , tmp_battletime ))
2019-12-18 00:19:21 +02:00
else :
2019-12-24 02:52:56 +02:00
result [ 'enemies' ] . add ( get_stat_id ( tmp_account_id , tmp_tank_id , tmp_battletime ))
2019-12-18 00:19:21 +02:00
2019-12-24 02:52:56 +02:00
# remove platoon buddy from stats
if result [ 'squad_index' ] != None :
for player in replay_json [ 'data' ][ 'summary' ][ 'details' ]:
bu . debug ( str ( player ))
if ( player [ 'squad_index' ] == result [ 'squad_index' ]) and ( player [ 'dbid' ] in replay_json [ 'data' ][ 'summary' ][ 'allies' ]) and ( player [ 'dbid' ] != account_id ):
# platoon buddy found
tmp_account_id = player [ 'dbid' ]
tmp_tank_id = player [ 'vehicle_descr' ]
#tmp_tank_tier = str(wg.get_tank_tier(tmp_tank_id))
tmp_battletime = int ( replay_json [ 'data' ][ 'summary' ][ 'battle_start_timestamp' ])
# platoon buddy removed from stats
result [ 'allies' ] . remove ( get_stat_id ( tmp_account_id , tmp_tank_id , tmp_battletime ))
2018-10-08 00:14:56 +08:00
result [ 'time_alive%' ] = result [ 'time_alive' ] / btl_duration
result [ 'battle_tier' ] = btl_tier
result [ 'top_tier' ] = 1 if ( result [ 'tank_tier' ] == btl_tier ) else 0
result [ 'win' ] = 1 if result [ 'battle_result' ] == 1 else 0
2019-08-17 00:50:48 +03:00
REPLAY_I += 1
result [ 'battle_i' ] = REPLAY_I
2018-10-08 00:14:56 +08:00
bu . debug ( str ( result ))
return result
except KeyError as err :
2019-12-24 02:52:56 +02:00
bu . error ( 'Key not found' , err )
2018-10-08 00:14:56 +08:00
except Exception as err :
2019-12-24 02:52:56 +02:00
bu . error ( exception = err )
2018-10-08 00:14:56 +08:00
return None
2019-12-18 00:19:21 +02:00
2019-12-24 02:52:56 +02:00
def str2ints ( stat_id_str : str ) -> list :
"""Convert stat_id string e.g. 'account_id:tank_id' to list of ints"""
return [ int ( x ) for x in stat_id_str . split ( ':' ) ]
def prune_stat_id ( stat_id_str : str ) -> str :
stat_id = stat_id_str . split ( ':' )
del stat_id [ - 1 ]
return ':' . join ( stat_id )
def get_stat_id ( account_id : int , tank_id : int , battletime : int ) -> str :
return ':' . join ([ str ( account_id ), str ( tank_id ), str ( battletime )])
def get_stat_id_tank_tier ( stat_id_str : str ) -> str :
"""Return stat_id = account_id:tank_tier"""
stat_id = stat_id_str . split ( ':' )
tank_tier = wg . get_tank_tier ( int ( stat_id [ 1 ]))
return ':' . join ([ stat_id [ 0 ], str ( tank_tier ), stat_id [ 2 ] ])
def get_stat_id_player ( stat_id_str : str ) -> str :
"""Return stat_id = account_id:tank_tier"""
stat_id = stat_id_str . split ( ':' )
return ':' . join ([ stat_id [ 0 ], stat_id [ 2 ] ])
2019-12-18 00:19:21 +02:00
2018-10-08 00:14:56 +08:00
### main()
if __name__ == "__main__" :
2018-12-15 02:47:21 +02:00
asyncio . run ( main ( sys . argv [ 1 :]), debug = False )