mirror of
https://github.com/Jylpah/blitz-tools.git
synced 2026-10-24 11:39:52 +02:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c0de49077 | ||
|
|
07aa7af32f | ||
|
|
ea3306ee6c | ||
|
|
e4882d70af | ||
|
|
433c26d2f8 | ||
|
|
f6b1b1353f |
No files matched your search
+55
-8
@@ -28,6 +28,8 @@ REPLAY_I = 0
|
|||||||
STAT_TANK_BATTLE_MIN = 100
|
STAT_TANK_BATTLE_MIN = 100
|
||||||
BATTLE_TIME_BUCKET = 3600*24*14
|
BATTLE_TIME_BUCKET = 3600*24*14
|
||||||
|
|
||||||
|
OPT_EXPORT_REPLAY_DIR = 'export_replays'
|
||||||
|
|
||||||
RE_SRC_IS_DB = re.compile(r'^DB:')
|
RE_SRC_IS_DB = re.compile(r'^DB:')
|
||||||
|
|
||||||
class StatFunc:
|
class StatFunc:
|
||||||
@@ -1419,6 +1421,7 @@ async def main(argv):
|
|||||||
parser.add_argument('--mapfile', type=str, default='maps.json', help='JSON file to read Blitz map names from. Default is "maps.json"')
|
parser.add_argument('--mapfile', type=str, default='maps.json', help='JSON file to read Blitz map names from. Default is "maps.json"')
|
||||||
parser.add_argument('--json', action='store_true', default=False, help='Export data in JSON format')
|
parser.add_argument('--json', action='store_true', default=False, help='Export data in JSON format')
|
||||||
parser.add_argument('--csv', action='store_true', default=False, help='Export data in CSV format')
|
parser.add_argument('--csv', action='store_true', default=False, help='Export data in CSV format')
|
||||||
|
parser.add_argument('--replays', action='store_true', default=False, help='Export replay info in JSON format')
|
||||||
parser.add_argument('-o','--outfile', type=str, default='-', metavar="OUTPUT", help='File to write results. Default STDOUT')
|
parser.add_argument('-o','--outfile', type=str, default='-', metavar="OUTPUT", help='File to write results. Default STDOUT')
|
||||||
parser.add_argument('--db', action='store_true', default=OPT_DB, help='Use DB - You are unlikely to have it')
|
parser.add_argument('--db', action='store_true', default=OPT_DB, help='Use DB - You are unlikely to have it')
|
||||||
parser.add_argument('--filters', type=str, default=None, help='Filter replays based on categories. Filters given in JSON format.\nUse array "[]" for multiple filters/values. see --mode help.\nExample: : [ {"tier" : [8,9,10] }, { "player_wins" : 5 }]')
|
parser.add_argument('--filters', type=str, default=None, help='Filter replays based on categories. Filters given in JSON format.\nUse array "[]" for multiple filters/values. see --mode help.\nExample: : [ {"tier" : [8,9,10] }, { "player_wins" : 5 }]')
|
||||||
@@ -1498,7 +1501,7 @@ async def main(argv):
|
|||||||
|
|
||||||
bu.debug('Waiting for the replay scanner to finish')
|
bu.debug('Waiting for the replay scanner to finish')
|
||||||
await asyncio.wait([scanner_task])
|
await asyncio.wait([scanner_task])
|
||||||
|
bu.finish_progress_bar()
|
||||||
# bu.debug('Scanner finished. Waiting for replay readers to finish the queue')
|
# bu.debug('Scanner finished. Waiting for replay readers to finish the queue')
|
||||||
await replayQ.join()
|
await replayQ.join()
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
@@ -1508,15 +1511,19 @@ async def main(argv):
|
|||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
results = []
|
results = []
|
||||||
players = set()
|
players = set()
|
||||||
|
replays = dict()
|
||||||
for res in await asyncio.gather(*reader_tasks):
|
for res in await asyncio.gather(*reader_tasks):
|
||||||
results.extend(res[0])
|
results.extend(res[0])
|
||||||
players.update(res[1])
|
players.update(res[1])
|
||||||
|
replays.update(res[2])
|
||||||
|
|
||||||
if len(players) == 0:
|
if len(players) == 0:
|
||||||
raise Exception("No players found to fetch stats for. No replays found?")
|
raise Exception("No players found to fetch stats for. No replays found?")
|
||||||
|
|
||||||
if args.min != None:
|
if args.min != None:
|
||||||
results = filter_min_replays_by_player(results, args.min)
|
results = filter_min_replays_by_player(results, args.min)
|
||||||
players = get_players(results)
|
players = get_players(results)
|
||||||
|
replays = filter_replays(replays, results)
|
||||||
|
|
||||||
(player_stats, stat_id_map) = await process_player_stats(players, OPT_WORKERS_N, args, db)
|
(player_stats, stat_id_map) = await process_player_stats(players, OPT_WORKERS_N, args, db)
|
||||||
bu.debug('Number of player stats: ' + str(len(player_stats)))
|
bu.debug('Number of player stats: ' + str(len(player_stats)))
|
||||||
@@ -1563,6 +1570,9 @@ async def main(argv):
|
|||||||
csvwriter.writerows(rows)
|
csvwriter.writerows(rows)
|
||||||
bu.verbose_std('Results exported to: ' + export_file)
|
bu.verbose_std('Results exported to: ' + export_file)
|
||||||
|
|
||||||
|
if args.replays:
|
||||||
|
await export_replays(replays)
|
||||||
|
|
||||||
bu.debug('Finished. Cleaning up..................')
|
bu.debug('Finished. Cleaning up..................')
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
bu.error(exception=err)
|
bu.error(exception=err)
|
||||||
@@ -1629,6 +1639,25 @@ async def help_extended(db : motor.motor_asyncio.AsyncIOMotorDatabase = None, pa
|
|||||||
print('No, I am not planning to fix these for now.')
|
print('No, I am not planning to fix these for now.')
|
||||||
|
|
||||||
|
|
||||||
|
async def export_replays(replays: dict):
|
||||||
|
"""Export replays"""
|
||||||
|
try:
|
||||||
|
replay_dir = OPT_EXPORT_REPLAY_DIR + '_' + bu.get_date_str()
|
||||||
|
bu.verbose_std('Exporting replays to ' + replay_dir)
|
||||||
|
if not os.path.isdir(replay_dir):
|
||||||
|
os.mkdir(replay_dir)
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
for replay in replays.values():
|
||||||
|
filename = wg.get_replay_filename(replay)
|
||||||
|
async with aiofiles.open(os.path.join(replay_dir, filename), 'w', encoding="utf8") as f:
|
||||||
|
await f.write(json.dumps(replay, indent=4))
|
||||||
|
i += 1
|
||||||
|
bu.verbose_std( str(i) + ' replays exported')
|
||||||
|
except Exception as err:
|
||||||
|
bu.error(exception=err)
|
||||||
|
|
||||||
|
|
||||||
## move the class PlayerHistogram?
|
## move the class PlayerHistogram?
|
||||||
def set_histogram_buckets(json: dict):
|
def set_histogram_buckets(json: dict):
|
||||||
global histogram_fields
|
global histogram_fields
|
||||||
@@ -1668,7 +1697,7 @@ def filter_min_replays_by_player(results: list, min_replays: int) -> list:
|
|||||||
for res in results:
|
for res in results:
|
||||||
if res['protagonist'] in players_enough_replays:
|
if res['protagonist'] in players_enough_replays:
|
||||||
res_ret.append(res)
|
res_ret.append(res)
|
||||||
|
bu.verbose_std('Replays after filtering: ' + str(len(res_ret)))
|
||||||
return res_ret
|
return res_ret
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
@@ -1719,7 +1748,7 @@ def process_battle_results(results: dict, args : argparse.Namespace):
|
|||||||
|
|
||||||
|
|
||||||
if args.filters != None:
|
if args.filters != None:
|
||||||
results = filter_replays(results, args.filters)
|
results = filter_results(results, args.filters)
|
||||||
if len(results) > 0:
|
if len(results) > 0:
|
||||||
blt_cat_list = BattleCategorizationList(cats)
|
blt_cat_list = BattleCategorizationList(cats)
|
||||||
for result in results:
|
for result in results:
|
||||||
@@ -1739,7 +1768,7 @@ def process_battle_results(results: dict, args : argparse.Namespace):
|
|||||||
return blt_cat_list
|
return blt_cat_list
|
||||||
|
|
||||||
|
|
||||||
def filter_replays(results: list, filter_json : str) -> bool:
|
def filter_results(results: list, filter_json : str) -> bool:
|
||||||
"""Filter replays based on battle category filters"""
|
"""Filter replays based on battle category filters"""
|
||||||
try:
|
try:
|
||||||
filters = json.loads(filter_json)
|
filters = json.loads(filter_json)
|
||||||
@@ -1773,13 +1802,27 @@ def filter_replays(results: list, filter_json : str) -> bool:
|
|||||||
res.append(result)
|
res.append(result)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
bu.error(exception=err)
|
bu.error(exception=err)
|
||||||
bu.verbose('Replays after filtering: ' + str(len(res)))
|
bu.verbose_std('Replays after filtering: ' + str(len(res)))
|
||||||
return res
|
return res
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
bu.error(exception=err)
|
bu.error(exception=err)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def filter_replays(replays_in: dict, results: list) -> list:
|
||||||
|
"""Filter source replays based on (filtered) results"""
|
||||||
|
replays = dict()
|
||||||
|
for res in results:
|
||||||
|
try:
|
||||||
|
# bu.debug('res[_id] : ' + res['_id'], force=True)
|
||||||
|
_id = res['_id']
|
||||||
|
replays[_id] = replays_in[_id]
|
||||||
|
except Exception as err:
|
||||||
|
bu.error(exception=err)
|
||||||
|
return replays
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def process_player_stats(players, N_workers: int, args : argparse.Namespace, db : motor.motor_asyncio.AsyncIOMotorDatabase) -> dict:
|
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"""
|
"""Start stat_workers to retrieve and store players' stats"""
|
||||||
## Create queue of player-tank pairs to find stats for
|
## Create queue of player-tank pairs to find stats for
|
||||||
@@ -2314,7 +2357,8 @@ async def mk_readerQ_item(replay_json, filename : str = None, _id: str = None) -
|
|||||||
try:
|
try:
|
||||||
if wi.chk_JSON_replay(replay_json):
|
if wi.chk_JSON_replay(replay_json):
|
||||||
if not '_id' in replay_json:
|
if not '_id' in replay_json:
|
||||||
_id = wi.read_replay_id(replay_json)
|
if _id != None:
|
||||||
|
_id = wi.read_replay_id(replay_json)
|
||||||
replay_json['_id'] = _id
|
replay_json['_id'] = _id
|
||||||
else:
|
else:
|
||||||
replay_json = None # mark error
|
replay_json = None # mark error
|
||||||
@@ -2334,6 +2378,7 @@ async def replay_reader(queue: asyncio.Queue, readerID: int, args : argparse.Nam
|
|||||||
#global SKIPPED_N
|
#global SKIPPED_N
|
||||||
results = []
|
results = []
|
||||||
playerstanks = set()
|
playerstanks = set()
|
||||||
|
replays = dict()
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
item = await queue.get()
|
item = await queue.get()
|
||||||
@@ -2360,8 +2405,10 @@ async def replay_reader(queue: asyncio.Queue, readerID: int, args : argparse.Nam
|
|||||||
playerstanks.update(set(result['allies']))
|
playerstanks.update(set(result['allies']))
|
||||||
playerstanks.update(set(result['enemies']))
|
playerstanks.update(set(result['enemies']))
|
||||||
playerstanks.update(set([result['player']]))
|
playerstanks.update(set([result['player']]))
|
||||||
|
_id = wi.read_replay_id(replay_json)
|
||||||
|
replays[_id] = replay_json
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
|
||||||
bu.debug('Marking replay [' + str(replayID) + '] done', id=readerID)
|
bu.debug('Marking replay [' + str(replayID) + '] done', id=readerID)
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
@@ -2369,7 +2416,7 @@ async def replay_reader(queue: asyncio.Queue, readerID: int, args : argparse.Nam
|
|||||||
queue.task_done()
|
queue.task_done()
|
||||||
except (asyncio.CancelledError, concurrent.futures.CancelledError):
|
except (asyncio.CancelledError, concurrent.futures.CancelledError):
|
||||||
bu.debug( str(len(results)) + ' replays, ' + str(len(playerstanks)) + ' player/tanks', readerID)
|
bu.debug( str(len(results)) + ' replays, ' + str(len(playerstanks)) + ' player/tanks', readerID)
|
||||||
return results, playerstanks
|
return results, playerstanks, replays
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+58
-20
@@ -486,6 +486,16 @@ def NOW() -> int:
|
|||||||
return int(time.time())
|
return int(time.time())
|
||||||
|
|
||||||
|
|
||||||
|
def get_date_str(timestamp:int = NOW(), date_format = '%Y%m%d_%H%M%S'):
|
||||||
|
"""Return YYYYMMDD_HHmm date string"""
|
||||||
|
try:
|
||||||
|
ts = datetime.fromtimestamp(timestamp)
|
||||||
|
return ts.strftime(date_format)
|
||||||
|
except Exception as err:
|
||||||
|
error(exception=err)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def rebase_file_args(current_dir, files):
|
def rebase_file_args(current_dir, files):
|
||||||
"""REbase file command line params after moving working dir to the script's dir"""
|
"""REbase file command line params after moving working dir to the script's dir"""
|
||||||
if isinstance(files, list):
|
if isinstance(files, list):
|
||||||
@@ -916,33 +926,18 @@ class WG:
|
|||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_map(cls, map_str: str) -> str:
|
def get_map(self, map_str: str) -> str:
|
||||||
"""Return map name from short map string in replays"""
|
"""Return map name from short map string in replays"""
|
||||||
try:
|
try:
|
||||||
return cls.maps[map_str]
|
return self.maps[map_str]
|
||||||
except:
|
except:
|
||||||
error('Map ' + map_str + ' not found')
|
error('Map ' + map_str + ' not found')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_map_user_strs(cls) -> str:
|
def get_tank_user_strs(self) -> str:
|
||||||
return cls.maps.keys()
|
return self.tanks["userStr"].keys()
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def tank_str2name(cls, tank_str: str) -> str:
|
|
||||||
"""Return tank name from short tank string in replays"""
|
|
||||||
try:
|
|
||||||
return cls.tanks["userStr"][tank_str]
|
|
||||||
except:
|
|
||||||
error('Tank ' + tank_str + ' not found')
|
|
||||||
return tank_str
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_tank_user_strs(cls) -> str:
|
|
||||||
return cls.tanks["userStr"].keys()
|
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -1079,10 +1074,14 @@ class WG:
|
|||||||
"""Load tanks from tankopedia JSON"""
|
"""Load tanks from tankopedia JSON"""
|
||||||
if tankopedia_fn == None:
|
if tankopedia_fn == None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(tankopedia_fn, 'rt', encoding='utf8') as f:
|
with open(tankopedia_fn, 'rt', encoding='utf8') as f:
|
||||||
self.tanks = json.loads(f.read())
|
self.tanks = json.loads(f.read())
|
||||||
|
self.tanks['tankStr'] = dict()
|
||||||
|
p = re.compile('.+_short$')
|
||||||
|
for usr_str in self.tanks['userStr']:
|
||||||
|
if p.match(usr_str) == None:
|
||||||
|
self.tanks['tankStr'][self.tanks['userStr'][usr_str]] = usr_str
|
||||||
self.tanks_by_tier = dict()
|
self.tanks_by_tier = dict()
|
||||||
for tier in range(1,11):
|
for tier in range(1,11):
|
||||||
self.tanks_by_tier[str(tier)] = list()
|
self.tanks_by_tier[str(tier)] = list()
|
||||||
@@ -1094,6 +1093,43 @@ class WG:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_replay_filename(self, replay: dict):
|
||||||
|
try:
|
||||||
|
summary = replay['data']['summary']
|
||||||
|
timestamp = int(summary['battle_start_timestamp'])
|
||||||
|
timestamp = get_date_str(timestamp, date_format='%Y%m%d_%H%M')
|
||||||
|
player = summary['player_name']
|
||||||
|
arena_id = int(summary['arena_unique_id'])
|
||||||
|
vehicle = self.name2tank_str(summary['vehicle'])
|
||||||
|
vehicle.replace(' ','_')
|
||||||
|
|
||||||
|
return '{:s}_{:s}_{:s}_{:d}.wotbreplay.json'.format(timestamp, player, vehicle, arena_id)
|
||||||
|
except Exception as err:
|
||||||
|
error('Unexpected Exception', err)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_map_user_strs(self) -> str:
|
||||||
|
return self.maps.keys()
|
||||||
|
|
||||||
|
|
||||||
|
def tank_str2name(self, tank_str: str) -> str:
|
||||||
|
"""Return tank name from short tank string in replays"""
|
||||||
|
try:
|
||||||
|
return self.tanks["userStr"][tank_str]
|
||||||
|
except:
|
||||||
|
error('Tank ' + tank_str + ' not found')
|
||||||
|
return tank_str
|
||||||
|
|
||||||
|
|
||||||
|
def name2tank_str(self, name: str) -> str:
|
||||||
|
"""Return shorttank string from tank string in replays"""
|
||||||
|
try:
|
||||||
|
return self.tanks["tankStr"][name]
|
||||||
|
except:
|
||||||
|
debug('Tank ' + name + ' not found')
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
def get_tanks_by_tier(self, tier: int) -> list():
|
def get_tanks_by_tier(self, tier: int) -> list():
|
||||||
"""Returns tank_ids by tier"""
|
"""Returns tank_ids by tier"""
|
||||||
try:
|
try:
|
||||||
@@ -1831,6 +1867,8 @@ class WoTinspector:
|
|||||||
except Exception as err:
|
except Exception as err:
|
||||||
debug(exception=err, id=N)
|
debug(exception=err, id=N)
|
||||||
await asyncio.sleep(SLEEP)
|
await asyncio.sleep(SLEEP)
|
||||||
|
_id = self.read_replay_id(json_resp)
|
||||||
|
json_resp['_id'] = _id
|
||||||
|
|
||||||
debug(' Could not post replay: ' + title, id=N)
|
debug(' Could not post replay: ' + title, id=N)
|
||||||
return json_resp
|
return json_resp
|
||||||
|
|||||||
Reference in new issue
Block a user