mirror of
https://github.com/Jylpah/blitz-tools.git
synced 2026-10-25 02:59:53 +01:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33b0efb48d | ||
|
|
574fb95f00 | ||
|
|
fdc5db966e | ||
|
|
65e4fd83d8 | ||
|
|
e9451c7f5d | ||
|
|
fa65872ad2 |
No files matched your search
@@ -1,5 +1,5 @@
|
||||
# blitz-tools
|
||||
Python scripts to analyze WoT Blitz replays and stats. All the scripts require [Python 3.7](https://www.python.org/downloads/) or higher. I am using 3.8 myself so you may have to change the Python interpreter version in the scripts' first line.
|
||||
Python scripts to analyze WoT Blitz replays and stats. All the scripts require [Python 3.7](https://www.python.org/downloads/) or later.
|
||||
|
||||
### Replays
|
||||
* [upload_wotb_replays.py](upload_wotb_replays.py): An asynchronous replay uploader.
|
||||
@@ -13,46 +13,48 @@ Python scripts to analyze WoT Blitz replays and stats. All the scripts require [
|
||||
* [blitzutils.py](blitzutils.py): A utils class for various Blitz related to functions. *Other scripts use this*, so you need to place it to the same folder with the other scripts
|
||||
|
||||
### Data
|
||||
* [tanks.json](tanks.json): Tankopedia in WG JSON format extracted from the Blitz app files with [extract_tankopedia.py]](extractTankopedia.py]). Contains only limited information required by the scripts.
|
||||
* [maps.json](maps.json): Blitz map names extracted from the Blitz app files with [extract_tankopedia.py]](extract_tankopedia.py]).
|
||||
* [tanks.json](tanks.json): Tankopedia in WG JSON format extracted from the Blitz app files with [extract_tankopedia.py](extractTankopedia.py). Contains only limited information required by the scripts.
|
||||
* [maps.json](maps.json): Blitz map names extracted from the Blitz app files with [extract_tankopedia.py](extract_tankopedia.py).
|
||||
|
||||
### Quick instructions
|
||||
|
||||
1. Install [Python 3.8](https://www.python.org/downloads/) on to your computer/laptop
|
||||
2. Install the required Python modules: motor, aiohttp, aioconsole, aiofiles, aiosqlite, beautifulsoup4, lxml, xmltodict, progress
|
||||
1. Install [Python 3.8](https://www.python.org/downloads/) or later to your computer/laptop
|
||||
2. Install the required Python modules (see [requirements.txt](requirements.txt): motor, aiohttp, aioconsole, aiofiles, aiosqlite, beautifulsoup4, lxml, xmltodict, progress
|
||||
```
|
||||
# Remember to run the command with a Python interpreter >= 3.7
|
||||
python -m pip install motor aiohttp aioconsole aiofiles aiosqlite beautifulsoup4 lxml xmltodict progress
|
||||
python -m pip install -r requirements.txt
|
||||
```
|
||||
3. Download all the files including the [maps.json](maps.json) and [tanks.json](tanks.json) files. **The recommened way** is to install the scipts via `git clone`: [GitHub Desktop](https://desktop.github.com/) is an excellent GUI for git. This makes it easy to receive the updates via `git pull`.
|
||||
|
||||
4. Upload your replays.
|
||||
3. Clone the repository. [GitHub Desktop](https://desktop.github.com/) is an excellent GUI for `git`. This makes it easy to receive the updates via `git pull`.
|
||||
```
|
||||
# Windows, run directly from Command Prompt or Powershell
|
||||
# Check the options
|
||||
git clone https://github.com/Jylpah/blitz-tools.git
|
||||
```
|
||||
4. Upload your replays with `upload_wotb_replays.py`
|
||||
```
|
||||
## Check the options
|
||||
# Windows Command Prompt or Powershell
|
||||
python.exe .\upload_wotb_replays.py --help
|
||||
|
||||
# Syntax
|
||||
python.exe .\upload_wotb_replays.py REPLAY_FILE.wotbreplay
|
||||
# Linux or Windows Subsystem for Linux (WSL)
|
||||
./upload_wotb_replays.py --help
|
||||
|
||||
# Upload replays (Windows)
|
||||
python.exe .\upload_wotb_replays.py REPLAY_FILE.wotbreplay DIR_WITH_REPLAYS
|
||||
|
||||
# example
|
||||
python.exe .\upload_wotb_replays.py 20200223_0122__jylpah_T-54_grossberg.wotbreplay
|
||||
|
||||
# Linux
|
||||
./upload_wotb_replays.py --help
|
||||
```
|
||||
5. Analyze replays.
|
||||
5. Analyze replays with `analyze_wotb_replays.py`
|
||||
```
|
||||
# Windows, run directly from Command Prompt or Powershell
|
||||
## Check options
|
||||
# Windows Command Prompt or Powershell
|
||||
python.exe .\analyze_wotb_replays.py --help
|
||||
|
||||
# Syntax
|
||||
python.exe .\analyze_wotb_replays.py REPLAY_FILE.wotbreplay.json
|
||||
# Syntax (Windows)
|
||||
python.exe .\analyze_wotb_replays.py REPLAY_FILE.wotbreplay.json [DIR_WITH_REPLAYS ...]
|
||||
|
||||
# Example
|
||||
# Example (Windows)
|
||||
python.exe .\analyze_wotb_replays.py 20200223_0122__jylpah_T-54_grossberg.wotbreplay.json
|
||||
|
||||
# Linux
|
||||
# Linux or Windows Subsystem for Linux (WSL)
|
||||
./analyze_wotb_replays.py --help
|
||||
```
|
||||
|
||||
|
||||
+166
-106
@@ -29,6 +29,8 @@ STAT_TANK_BATTLE_MIN = 100
|
||||
BATTLE_TIME_BUCKET = 3600*24*14
|
||||
|
||||
OPT_EXPORT_REPLAY_DIR = 'export_replays'
|
||||
OPT_EXPORT_CSV_FILE = 'export.csv'
|
||||
OPT_EXPORT_JSON_FILE = 'export.json'
|
||||
|
||||
RE_SRC_IS_DB = re.compile(r'^DB:')
|
||||
|
||||
@@ -226,16 +228,41 @@ class BattleCategorizationList():
|
||||
'top_tier'
|
||||
]
|
||||
|
||||
## Player stats based categorizations
|
||||
_categorizations_stats = [ group + '_' + measure for group in [ 'player', 'allies', 'enemies'] for measure in ['wins', 'battles', 'damage_dealt' ]]
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_categorizations_all(cls, cats: list = None) -> list:
|
||||
try:
|
||||
if cats == None:
|
||||
return list(cls._categorizations.keys())
|
||||
else:
|
||||
return list(set(cls._categorizations.keys()) & set(cats))
|
||||
except Exception as err:
|
||||
bu.error(exception=err)
|
||||
return None
|
||||
|
||||
_categorizations_stats = [
|
||||
'player_wins',
|
||||
'player_battles',
|
||||
'player_battles'
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_categorizations_all(cls):
|
||||
return cls._categorizations.keys()
|
||||
def get_categorizations_stats(cls, cats=None, stats_cats=None):
|
||||
try:
|
||||
if cats == None:
|
||||
return cls._categorizations_stats
|
||||
else:
|
||||
if stats_cats == None:
|
||||
return cats
|
||||
player_stat_filters = set(cls._categorizations_stats)
|
||||
cats = set(cls.get_categorizations_all(cats))
|
||||
if stats_cats:
|
||||
cats = cats & player_stat_filters
|
||||
else:
|
||||
cats = cats - player_stat_filters
|
||||
bu.debug(', '.join(list(cats)))
|
||||
return list(cats)
|
||||
except Exception as err:
|
||||
bu.error(exception=err)
|
||||
return None
|
||||
|
||||
|
||||
@classmethod
|
||||
@@ -370,6 +397,10 @@ class BattleCategorizationList():
|
||||
return BattleCategorizationListIterator(self)
|
||||
|
||||
|
||||
def len(self):
|
||||
return len(self.categorizations)
|
||||
|
||||
|
||||
def get_categorization(self, cat: str):
|
||||
"""Return a BattleCategorization for a category"""
|
||||
try:
|
||||
@@ -378,6 +409,7 @@ class BattleCategorizationList():
|
||||
bu.error(exception=err)
|
||||
return None
|
||||
|
||||
|
||||
def record_result(self, result: dict):
|
||||
try:
|
||||
for cat in self.categorizations_list:
|
||||
@@ -1388,7 +1420,7 @@ class ErrorCatchingArgumentParser(argparse.ArgumentParser):
|
||||
## main() -------------------------------------------------------------
|
||||
|
||||
async def main(argv):
|
||||
global wg, wi, WG_APP_ID, OPT_MODE_DEFAULT
|
||||
global wg, wi, WG_APP_ID, OPT_MODE_DEFAULT, OPT_EXPORT_CSV_FILE, OPT_EXPORT_JSON_FILE
|
||||
# set the directory for the script
|
||||
current_dir = os.getcwd()
|
||||
os.chdir(os.path.dirname(sys.argv[0]))
|
||||
@@ -1401,8 +1433,8 @@ async def main(argv):
|
||||
OPT_HIST = False
|
||||
OPT_STAT_FUNC = StatFunc.get_default()
|
||||
OPT_WORKERS_N = 10
|
||||
OPT_EXPORT_CSV_FILE = 'export.csv'
|
||||
OPT_EXPORT_JSON_FILE = 'export.json'
|
||||
# OPT_EXPORT_CSV_FILE = 'export.csv'
|
||||
# OPT_EXPORT_JSON_FILE = 'export.json'
|
||||
|
||||
#WG_ACCOUNT = None # format: nick@server, where server is either 'eu', 'ru', 'na', 'asia' or 'china'.
|
||||
# China is not supported since WG API stats are not available there
|
||||
@@ -1558,103 +1590,78 @@ async def main(argv):
|
||||
args.account_id = await wg.get_account_id(args.account)
|
||||
bu.debug('WG account_id: ' + str(args.account_id))
|
||||
|
||||
try:
|
||||
BattleCategory.set_fields(args.mode)
|
||||
replayQ = asyncio.Queue(maxsize=1000)
|
||||
reader_tasks = []
|
||||
# Make replay Queue
|
||||
# try:
|
||||
BattleCategory.set_fields(args.mode)
|
||||
replayQ = asyncio.Queue(maxsize=1000)
|
||||
reader_tasks = []
|
||||
# Make replay Queue
|
||||
|
||||
scanner_task = asyncio.create_task(mk_replayQ(replayQ, args, db))
|
||||
bu.set_counter('Reading replays', 10)
|
||||
# Start tasks to process the Queue
|
||||
for i in range(OPT_WORKERS_N):
|
||||
reader_tasks.append(asyncio.create_task(replay_reader(replayQ, i, args)))
|
||||
bu.debug('ReplayReader ' + str(i) + ' started')
|
||||
scanner_task = asyncio.create_task(mk_replayQ(replayQ, args, db))
|
||||
bu.set_counter('Reading replays', 10)
|
||||
# Start tasks to process the Queue
|
||||
for i in range(OPT_WORKERS_N):
|
||||
reader_tasks.append(asyncio.create_task(replay_reader(replayQ, i, args)))
|
||||
bu.debug('ReplayReader ' + str(i) + ' started')
|
||||
|
||||
bu.debug('Waiting for the replay scanner to finish')
|
||||
await asyncio.wait([scanner_task])
|
||||
bu.finish_progress_bar()
|
||||
# bu.debug('Scanner finished. Waiting for replay readers to finish the queue')
|
||||
await replayQ.join()
|
||||
await asyncio.sleep(0.1)
|
||||
bu.debug('Replays read. Cancelling Readers and analyzing results')
|
||||
for task in reader_tasks:
|
||||
task.cancel()
|
||||
await asyncio.sleep(0.1)
|
||||
results = []
|
||||
players = set()
|
||||
replays = dict()
|
||||
for res in await asyncio.gather(*reader_tasks):
|
||||
results.extend(res[0])
|
||||
players.update(res[1])
|
||||
replays.update(res[2])
|
||||
bu.debug('Waiting for the replay scanner to finish')
|
||||
await asyncio.wait([scanner_task])
|
||||
bu.finish_progress_bar()
|
||||
# bu.debug('Scanner finished. Waiting for replay readers to finish the queue')
|
||||
await replayQ.join()
|
||||
await asyncio.sleep(0.1)
|
||||
bu.debug('Replays read. Cancelling Readers and analyzing results')
|
||||
for task in reader_tasks:
|
||||
task.cancel()
|
||||
await asyncio.sleep(0.1)
|
||||
results = []
|
||||
players = set()
|
||||
replays = dict()
|
||||
for res in await asyncio.gather(*reader_tasks):
|
||||
results.extend(res[0])
|
||||
players.update(res[1])
|
||||
replays.update(res[2])
|
||||
|
||||
if len(players) == 0:
|
||||
raise Exception("No players found to fetch stats for. No replays found?")
|
||||
|
||||
if args.min != None:
|
||||
results = filter_min_replays_by_player(results, args.min)
|
||||
|
||||
if len(players) == 0:
|
||||
raise Exception("No players found to fetch stats for. No replays found?")
|
||||
# Filter based on non-stats filters
|
||||
results = filter_results(results, args.filters, stats_filters=False)
|
||||
players = get_players(results)
|
||||
|
||||
(player_stats, stat_id_map) = await process_player_stats(players, OPT_WORKERS_N, args, db)
|
||||
|
||||
# Filter based on stats filters
|
||||
results = filter_results(results, args.filters, stats_filters=True)
|
||||
bu.debug(str(len(replays)) + ' replays to process')
|
||||
replays = filter_replays(replays, results)
|
||||
|
||||
bu.debug('Number of player stats: ' + str(len(player_stats)))
|
||||
teamresults = calc_team_stats(results, player_stats, stat_id_map, args)
|
||||
|
||||
if args.min != None:
|
||||
results = filter_min_replays_by_player(results, args.min)
|
||||
players = get_players(results)
|
||||
replays = filter_replays(replays, results)
|
||||
blt_cat_list = process_battle_results(teamresults, args)
|
||||
bu.verbose_std('WR = ' + StatFunc.get_title())
|
||||
blt_cat_list.print_results()
|
||||
|
||||
# Filter based on non-stats filters
|
||||
results = filter_results(results, args.filters, stats_filters=False)
|
||||
histograms = None
|
||||
if args.hist:
|
||||
histograms = process_player_dist(results, player_stats, stat_id_map)
|
||||
print_player_dist(histograms)
|
||||
|
||||
if args.json:
|
||||
await export_json(args, blt_cat_list, histograms)
|
||||
|
||||
if args.csv:
|
||||
await export_csv(args, blt_cat_list, histograms)
|
||||
|
||||
if args.replays:
|
||||
await export_replays(replays)
|
||||
|
||||
(player_stats, stat_id_map) = await process_player_stats(players, OPT_WORKERS_N, args, db)
|
||||
|
||||
# Filter based on stats filters
|
||||
results = filter_results(results, args.filters, stats_filters=True)
|
||||
|
||||
bu.debug('Number of player stats: ' + str(len(player_stats)))
|
||||
teamresults = calc_team_stats(results, player_stats, stat_id_map, args)
|
||||
|
||||
blt_cat_list = process_battle_results(teamresults, args)
|
||||
bu.verbose_std('WR = ' + StatFunc.get_title())
|
||||
blt_cat_list.print_results()
|
||||
|
||||
histograms = None
|
||||
if args.hist:
|
||||
histograms = process_player_dist(results, player_stats, stat_id_map)
|
||||
print_player_dist(histograms)
|
||||
|
||||
if args.json:
|
||||
res = blt_cat_list.get_results_json()
|
||||
if histograms != None:
|
||||
res_hist = dict()
|
||||
for histogram in histograms.keys():
|
||||
res_hist[histogram] = histograms[histogram].get_results()
|
||||
res['histograms'] = res_hist
|
||||
if args.outfile == '-':
|
||||
export_file = OPT_EXPORT_JSON_FILE
|
||||
else:
|
||||
export_file = args.outfile
|
||||
await bu.save_JSON(export_file, res, pretty=False) ## Excel does not understand pretty JSON
|
||||
bu.verbose_std('Results exported to: ' + export_file)
|
||||
|
||||
if args.csv:
|
||||
rows = blt_cat_list.get_results_list()
|
||||
if args.hist:
|
||||
rows.append('')
|
||||
rows.append(['Player Histograms'])
|
||||
for histogram in histograms.values():
|
||||
rows.append('')
|
||||
rows.extend(histogram.get_results_list())
|
||||
if args.outfile == '-':
|
||||
export_file = OPT_EXPORT_CSV_FILE
|
||||
else:
|
||||
export_file = args.outfile
|
||||
with open(export_file, 'w') as csvfile:
|
||||
# creating a csv writer object
|
||||
csvwriter = csv.writer(csvfile)
|
||||
csvwriter.writerows(rows)
|
||||
bu.verbose_std('Results exported to: ' + export_file)
|
||||
|
||||
if args.replays:
|
||||
await export_replays(replays)
|
||||
|
||||
bu.debug('Finished. Cleaning up..................')
|
||||
except Exception as err:
|
||||
bu.error(exception=err)
|
||||
bu.debug('Finished. Cleaning up..................')
|
||||
# except Exception as err:
|
||||
# bu.error(exception=err)
|
||||
except UserWarning as err:
|
||||
bu.warning(str(err))
|
||||
except asyncio.CancelledError:
|
||||
@@ -1670,6 +1677,48 @@ async def main(argv):
|
||||
return None
|
||||
|
||||
|
||||
async def export_csv(args : argparse.Namespace, blt_cat_list: BattleCategorizationList, histograms: dict = None):
|
||||
"""Export results into a CSV file"""
|
||||
try:
|
||||
rows = blt_cat_list.get_results_list()
|
||||
if args.hist:
|
||||
rows.append('')
|
||||
rows.append(['Player Histograms'])
|
||||
for histogram in histograms.values():
|
||||
rows.append('')
|
||||
rows.extend(histogram.get_results_list())
|
||||
if args.outfile == '-':
|
||||
export_file = OPT_EXPORT_CSV_FILE
|
||||
else:
|
||||
export_file = args.outfile
|
||||
with open(export_file, 'w') as csvfile:
|
||||
# creating a csv writer object
|
||||
csvwriter = csv.writer(csvfile)
|
||||
csvwriter.writerows(rows)
|
||||
bu.verbose_std('Results exported to: ' + export_file)
|
||||
except Exception as err:
|
||||
bu.error(exception=err)
|
||||
|
||||
|
||||
async def export_json(args : argparse.Namespace, blt_cat_list: BattleCategorizationList, histograms: dict = None):
|
||||
"""Export results into a JSON file"""
|
||||
try:
|
||||
res = blt_cat_list.get_results_json()
|
||||
if histograms != None:
|
||||
res_hist = dict()
|
||||
for histogram in histograms.keys():
|
||||
res_hist[histogram] = histograms[histogram].get_results()
|
||||
res['histograms'] = res_hist
|
||||
if args.outfile == '-':
|
||||
export_file = OPT_EXPORT_JSON_FILE
|
||||
else:
|
||||
export_file = args.outfile
|
||||
await bu.save_JSON(export_file, res, pretty=False) ## Excel does not understand pretty JSON
|
||||
bu.verbose_std('Results exported to: ' + export_file)
|
||||
except Exception as err:
|
||||
bu.error(exception=err)
|
||||
|
||||
|
||||
async def help_extended(db : motor.motor_asyncio.AsyncIOMotorDatabase = None, parser: argparse.ArgumentParser = None):
|
||||
"""Help for --filters"""
|
||||
if parser != None:
|
||||
@@ -1851,21 +1900,28 @@ def process_battle_results(results: dict, args : argparse.Namespace):
|
||||
return blt_cat_list
|
||||
|
||||
|
||||
def filter_results(results: list, filter_json : str, stats_filters=False) -> bool:
|
||||
def filter_results(results: list, filters_json : str, stats_filters=None) -> bool:
|
||||
"""Filter replays based on battle category filters"""
|
||||
try:
|
||||
filters = json.loads(filter_json)
|
||||
if filters_json == None:
|
||||
return results
|
||||
filters = json.loads(filters_json)
|
||||
bu.debug(str(filters))
|
||||
if type(filters) != dict:
|
||||
bu.error('invalid --filter arguments: ' + str(filter))
|
||||
sys.exit(1)
|
||||
|
||||
filter_cats = BattleCategorizationList(filters.keys())
|
||||
filter_cats = BattleCategorizationList.get_categorizations_stats(filters.keys(), stats_filters)
|
||||
filter_cats = BattleCategorizationList(filter_cats)
|
||||
if filter_cats.len() == 0:
|
||||
bu.debug('Nothing to filter')
|
||||
return results
|
||||
|
||||
bu.verbose('Filters applied:')
|
||||
for category in filter_cats:
|
||||
cat = category.category_key
|
||||
categorization = filter_cats.get_categorization(cat)
|
||||
bu.debug('Category: ' + cat + ' filters type: ' + str(type(filters)) + ' filters: ' + str(filters))
|
||||
bu.debug('Category: ' + cat + ' filters: ' + str(filters))
|
||||
filters[cat] = categorization.get_filter_categories(filters[cat])
|
||||
bu.verbose(' ' + cat + ' = ' + ','.join(filters[cat]))
|
||||
bu.verbose('')
|
||||
@@ -1885,8 +1941,12 @@ def filter_results(results: list, filter_json : str, stats_filters=False) -> boo
|
||||
res.append(result)
|
||||
except Exception as err:
|
||||
bu.error(exception=err)
|
||||
if len(res) == 0:
|
||||
raise UserWarning('No replays left after filtering')
|
||||
bu.verbose_std('Replays after filtering: ' + str(len(res)))
|
||||
return res
|
||||
except UserWarning:
|
||||
raise
|
||||
except Exception as err:
|
||||
bu.error(exception=err)
|
||||
return None
|
||||
@@ -2439,7 +2499,7 @@ async def mk_readerQ_item(replay_json, filename : str = None, _id: str = None) -
|
||||
try:
|
||||
if wi.chk_JSON_replay(replay_json):
|
||||
if not '_id' in replay_json:
|
||||
if _id != None:
|
||||
if _id == None:
|
||||
_id = wi.read_replay_id(replay_json)
|
||||
replay_json['_id'] = _id
|
||||
else:
|
||||
|
||||
@@ -56,4 +56,4 @@
|
||||
"skit": "Naval Frontier",
|
||||
"test": "World of Ducks",
|
||||
"tutorial": "Proving Grounds"
|
||||
}
|
||||
}
|
||||
+142
-7
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"status": "ok",
|
||||
"meta": {
|
||||
"count": 520
|
||||
"count": 530
|
||||
},
|
||||
"data": {
|
||||
"1": {
|
||||
@@ -1996,6 +1996,14 @@
|
||||
"tier": 4,
|
||||
"type": "AT-SPG"
|
||||
},
|
||||
"9089": {
|
||||
"is_premium": true,
|
||||
"name": "Škoda T 56",
|
||||
"nation": "european",
|
||||
"tank_id": 9089,
|
||||
"tier": 8,
|
||||
"type": "heavyTank"
|
||||
},
|
||||
"9217": {
|
||||
"is_premium": true,
|
||||
"name": "IS-6",
|
||||
@@ -2028,6 +2036,22 @@
|
||||
"tier": 10,
|
||||
"type": "AT-SPG"
|
||||
},
|
||||
"9329": {
|
||||
"is_premium": true,
|
||||
"name": "Titan-150",
|
||||
"nation": "other",
|
||||
"tank_id": 9329,
|
||||
"tier": 6,
|
||||
"type": "heavyTank"
|
||||
},
|
||||
"9345": {
|
||||
"is_premium": true,
|
||||
"name": "Svear",
|
||||
"nation": "european",
|
||||
"tank_id": 9345,
|
||||
"tier": 7,
|
||||
"type": "mediumTank"
|
||||
},
|
||||
"9489": {
|
||||
"is_premium": false,
|
||||
"name": "E 100",
|
||||
@@ -2044,6 +2068,14 @@
|
||||
"tier": 9,
|
||||
"type": "heavyTank"
|
||||
},
|
||||
"9521": {
|
||||
"is_premium": true,
|
||||
"name": "122 TM",
|
||||
"nation": "china",
|
||||
"tank_id": 9521,
|
||||
"tier": 8,
|
||||
"type": "mediumTank"
|
||||
},
|
||||
"9553": {
|
||||
"is_premium": false,
|
||||
"name": "AT 8",
|
||||
@@ -3396,6 +3428,14 @@
|
||||
"tier": 7,
|
||||
"type": "heavyTank"
|
||||
},
|
||||
"23809": {
|
||||
"is_premium": false,
|
||||
"name": "Object 84",
|
||||
"nation": "ussr",
|
||||
"tank_id": 23809,
|
||||
"tier": 9,
|
||||
"type": "lightTank"
|
||||
},
|
||||
"23825": {
|
||||
"is_premium": true,
|
||||
"name": "Krupp-Steyr Waffenträger",
|
||||
@@ -3412,6 +3452,14 @@
|
||||
"tier": 7,
|
||||
"type": "AT-SPG"
|
||||
},
|
||||
"24065": {
|
||||
"is_premium": false,
|
||||
"name": "LTG",
|
||||
"nation": "ussr",
|
||||
"tank_id": 24065,
|
||||
"tier": 7,
|
||||
"type": "lightTank"
|
||||
},
|
||||
"24081": {
|
||||
"is_premium": true,
|
||||
"name": "U-Panzer",
|
||||
@@ -3429,7 +3477,7 @@
|
||||
"type": "mediumTank"
|
||||
},
|
||||
"24321": {
|
||||
"is_premium": true,
|
||||
"is_premium": false,
|
||||
"name": "T-100 LT",
|
||||
"nation": "ussr",
|
||||
"tank_id": 24321,
|
||||
@@ -3452,14 +3500,46 @@
|
||||
"tier": 10,
|
||||
"type": "AT-SPG"
|
||||
},
|
||||
"24593": {
|
||||
"is_premium": true,
|
||||
"name": "E 75 RT",
|
||||
"nation": "germany",
|
||||
"tank_id": 24593,
|
||||
"tier": 8,
|
||||
"type": "heavyTank"
|
||||
},
|
||||
"24609": {
|
||||
"is_premium": false,
|
||||
"is_premium": true,
|
||||
"name": "Concept 1B",
|
||||
"nation": "usa",
|
||||
"tank_id": 24609,
|
||||
"tier": 10,
|
||||
"type": "heavyTank"
|
||||
},
|
||||
"24849": {
|
||||
"is_premium": true,
|
||||
"name": "Kryos",
|
||||
"nation": "germany",
|
||||
"tank_id": 24849,
|
||||
"tier": 6,
|
||||
"type": "AT-SPG"
|
||||
},
|
||||
"25089": {
|
||||
"is_premium": true,
|
||||
"name": "Object 752",
|
||||
"nation": "ussr",
|
||||
"tank_id": 25089,
|
||||
"tier": 9,
|
||||
"type": "heavyTank"
|
||||
},
|
||||
"25345": {
|
||||
"is_premium": true,
|
||||
"name": "Object 274a",
|
||||
"nation": "ussr",
|
||||
"tank_id": 25345,
|
||||
"tier": 8,
|
||||
"type": "mediumTank"
|
||||
},
|
||||
"51201": {
|
||||
"is_premium": true,
|
||||
"name": "KV-220 Beta-Test",
|
||||
@@ -4217,6 +4297,8 @@
|
||||
"A125_AEP_1": "AE Phase I",
|
||||
"A125_AEP_1_short": "AE Phase I",
|
||||
"A128_Concept_1b": "Concept 1B",
|
||||
"A128_Concept_1b_Custom": "Concept 1B Thunderbird",
|
||||
"A128_Concept_1b_Custom_short": "Concept 1B Thunderbird",
|
||||
"A128_Concept_1b_short": "Concept 1B",
|
||||
"A130_Super_Hellcat": "Super Hellcat",
|
||||
"A130_Super_Hellcat_Custom": "Super Hellcat Delta",
|
||||
@@ -4353,6 +4435,7 @@
|
||||
"CH38_WZ113G-FT_short": "WZ-113G FT",
|
||||
"CH38_WZ113G_FT_Custom": "WZ-113G FT Lucky",
|
||||
"CH38_WZ113G_FT_Custom_short": "WZ-113G FT Lucky",
|
||||
"CKD_AXK": "ČKD AXK",
|
||||
"Cadillac_V8": "3xCadillacV8",
|
||||
"Cannone_da_105_51_M68": "Cannone da 105/51 M68",
|
||||
"Cannone_da_105_51_M68_short": "Cannone da 105/51 M68",
|
||||
@@ -4374,6 +4457,8 @@
|
||||
"Canon_de_105_mle_1930_Schneider_AC_short": "105 mm AC\\nmle. 1930 S",
|
||||
"Canon_de_90mm_Vo_930_m_s": "Canon de 90 mm Vo 930 m/s",
|
||||
"Ch01_Type59": "Type 59",
|
||||
"Ch01_Type59_Custom": "Type 59 Qinglong",
|
||||
"Ch01_Type59_Custom_short": "Type 59 Qinglong",
|
||||
"Ch02_Type62": "Type 62",
|
||||
"Ch02_Type62_Dragon": "Type 62 Dragon",
|
||||
"Ch03_WZ-111": "WZ-111",
|
||||
@@ -4431,6 +4516,8 @@
|
||||
"Ch42_M41D_Custom": "M41D Jade",
|
||||
"Ch42_M41D_Custom_short": "M41D Jade",
|
||||
"Ch42_M41D_short": "M41D",
|
||||
"Ch43_WZ-122_2": "122 TM",
|
||||
"Ch43_WZ-122_2_short": "122 TM",
|
||||
"Ch_LT_vz38": "LT vz. 38",
|
||||
"Ch_LT_vz38_short": "LT vz. 38",
|
||||
"Ch_WZ-112v2": "WZ-112-2",
|
||||
@@ -4545,6 +4632,8 @@
|
||||
"Cz13_T-27_Custom": "Škoda T 27 Rampant",
|
||||
"Cz13_T-27_Custom_short": "Škoda T 27 Rampant",
|
||||
"Cz13_T-27_short": "Škoda T 27",
|
||||
"Cz14_Skoda_T-56": "Škoda T 56",
|
||||
"Cz14_Skoda_T-56_short": "Škoda T 56",
|
||||
"D-744": "D-744",
|
||||
"D1": "D1",
|
||||
"D1_short": "D1",
|
||||
@@ -4557,6 +4646,8 @@
|
||||
"D51072": "D51072",
|
||||
"D51080": "D51080",
|
||||
"DB_605": "DB 605",
|
||||
"DB_605NY": "DB 605 A",
|
||||
"DG": "DG",
|
||||
"DMA-RD-620C2": "DMA RD-620C2",
|
||||
"DW_II": "Durchbruchswagen 2",
|
||||
"DW_II_short": "D.W. 2",
|
||||
@@ -4636,6 +4727,8 @@
|
||||
"F84_Somua_SM_Custom_short": "Somua Grenadier",
|
||||
"F84_Somua_SM_short": "Somua SM",
|
||||
"F89_Canon_dassaut_105": "AMX Canon d'assaut 105",
|
||||
"F89_Canon_dassaut_105_Custom": "CDA 105 Égalité",
|
||||
"F89_Canon_dassaut_105_Custom_short": "CDA 105 Égalité",
|
||||
"F89_Canon_dassaut_105_short": "AMX CDA 105",
|
||||
"FCM50t_fwhite": "FCM 50 t Titi",
|
||||
"FCM50t_fwhite_short": "FCM 50 t Titi",
|
||||
@@ -4686,6 +4779,8 @@
|
||||
"G106_PzKpfwPanther_AusfF": "Panther mit 8,8 cm L/71",
|
||||
"G106_PzKpfwPanther_AusfF_short": "Panther 8,8",
|
||||
"G109_Steyr_WT": "Krupp-Steyr Waffenträger",
|
||||
"G109_Steyr_WT_Custom": "Steyr WT Snakebite",
|
||||
"G109_Steyr_WT_Custom_short": "Steyr WT Snakebite",
|
||||
"G109_Steyr_WT_short": "Steyr WT",
|
||||
"G110_Mauschen": "Mäuschen",
|
||||
"G110_Mauschen_short": "Mäuschen",
|
||||
@@ -4717,6 +4812,8 @@
|
||||
"G138_VK16801P": "VK 168.01 (P)",
|
||||
"G138_VK16801P_short": "VK 168.01 (P)",
|
||||
"G142_M48RPz": "M48A2 Räumpanzer",
|
||||
"G142_M48RPz_Custom": "Räumpanzer Rhino",
|
||||
"G142_M48RPz_Custom_short": "Räumpanzer Rhino",
|
||||
"G142_M48RPz_short": "M48A2 Räumpanzer",
|
||||
"G144_Kpz_50t": "Kampfpanzer 50 t",
|
||||
"G144_Kpz_50t_Custom": "Kampfpanzer 50 t König",
|
||||
@@ -4736,6 +4833,8 @@
|
||||
"GAZ-74b": "SU-85B",
|
||||
"GAZ-M1": "GAZ-M1",
|
||||
"GAZ_M1": "GAZ-M1",
|
||||
"GAZ_O": "GAZ-O",
|
||||
"GAZ_OU": "GAZ-OU",
|
||||
"GB01_Medium_Mark_I": "Vickers Medium Mk. I",
|
||||
"GB01_Medium_Mark_I_short": "Medium I",
|
||||
"GB03_Cruiser_Mk_I": "Cruiser Mk. I",
|
||||
@@ -4899,7 +4998,10 @@
|
||||
"GM_6-71_M": "GM 6062 6-71M",
|
||||
"GM_X-11": "GM X-11",
|
||||
"GM_X-12": "GM X-12",
|
||||
"GT_101": "GT 101",
|
||||
"G_E": "G.W. E 100",
|
||||
"G_E-75RT": "E 75 RT",
|
||||
"G_E-75RT_short": "E 75 RT",
|
||||
"G_E-75TS": "E 75 TS",
|
||||
"G_E-75TS_short": "E 75 TS",
|
||||
"G_E_75TS_Custom": "E 75 TS Eagle",
|
||||
@@ -5040,6 +5142,10 @@
|
||||
"JagdPanther_short": "JPanther",
|
||||
"JagdPzIV": "Jagdpanzer IV",
|
||||
"JagdPzIV_0": "Jagdpanzer IV",
|
||||
"JagdPzIV_AusfBP": "Kryos",
|
||||
"JagdPzIV_AusfBP_Custom": "Kryos Survivor",
|
||||
"JagdPzIV_AusfBP_Custom_short": "Kryos Survivor",
|
||||
"JagdPzIV_AusfBP_short": "Kryos",
|
||||
"JagdPzIV_Ausf_1940": "Jagdpanzer IV 0-Serie",
|
||||
"JagdPzIV_Ausf_1943": "Jagdpanzer IV",
|
||||
"JagdPzIV_short": "Jg.Pz. IV",
|
||||
@@ -5285,6 +5391,7 @@
|
||||
"Maybach_HL_116": "Maybach HL 116",
|
||||
"Maybach_HL_116_S": "Maybach HL 116 S",
|
||||
"Maybach_HL_120": "Maybach HL 120",
|
||||
"Maybach_HL_120BP": "Maybach HL 120",
|
||||
"Maybach_HL_120_TR": "Maybach HL 120 TR",
|
||||
"Maybach_HL_120_TRM": "Maybach HL 120 TRM",
|
||||
"Maybach_HL_120_TRMB": "Maybach HL 120 TRM Ausf. A",
|
||||
@@ -5369,8 +5476,8 @@
|
||||
"NC27": "Renault Otsu",
|
||||
"NC27_short": "R. Otsu",
|
||||
"NORINCO_12150L7": "NORINCO 12150L",
|
||||
"NVSS_T66": "NVSS T66",
|
||||
"NVSS_T80": "NVSS T80",
|
||||
"NVSS_T66": "HVSS T66",
|
||||
"NVSS_T80": "HVSS T80",
|
||||
"Nashorn": "Nashorn",
|
||||
"Nashorn_short": "Nashorn",
|
||||
"Nuffield_liberty_MKII": "Nuffield Liberty Mk. II",
|
||||
@@ -5475,6 +5582,10 @@
|
||||
"Oth28_Sturmfeur_HW_short": "Titan H-N",
|
||||
"Oth29_A15HW": "Titan Mk. I",
|
||||
"Oth29_A15HW_short": "Titan Mk. I",
|
||||
"Oth31_T150BP": "Titan-150",
|
||||
"Oth31_T150BP_Custom": "Titan-150 Trailblazer",
|
||||
"Oth31_T150BP_Custom_short": "Titan-150 Trailblazer",
|
||||
"Oth31_T150BP_short": "Titan-150",
|
||||
"PLInz_RWA": "PLInz R.W.A.",
|
||||
"PLT_27": "PLT-27",
|
||||
"Paccar_120_325HT": "Gas Boiler",
|
||||
@@ -5508,6 +5619,8 @@
|
||||
"Pl13_53TP_Markowskiego": "53TP Markowskiego",
|
||||
"Pl13_53TP_Markowskiego_short": "53TP Markowskiego",
|
||||
"Pl15_60TP": "60TP Lewandowskiego",
|
||||
"Pl15_60TP_Custom": "60TP Krzemień",
|
||||
"Pl15_60TP_Custom_short": "60TP Krzemień",
|
||||
"Pl15_60TP_short": "60TP Lewandowskiego",
|
||||
"Porsche_16_Cyl_Y1": "Porsche 16 Cyl. Y",
|
||||
"Porsche_16_Cyl_Y2": "Porsche Typ 212 NL",
|
||||
@@ -5722,9 +5835,15 @@
|
||||
"R120_T22SR_short": "T-22 medium",
|
||||
"R127_T44_100_P": "T-44-100",
|
||||
"R127_T44_100_P_short": "T-44-100",
|
||||
"R131_LTG": "LTG",
|
||||
"R131_LTG_short": "LTG",
|
||||
"R132_T100LT": "T-100 LT",
|
||||
"R132_T100LT_Custom": "T-100 LT Interceptor",
|
||||
"R132_T100LT_Custom_short": "T-100 LT Interceptor",
|
||||
"R132_T100LT_short": "T-100 LT",
|
||||
"R134_Object_252U": "Object 252U",
|
||||
"R134_Object_252U_Custom": "Object 252U Vigilant",
|
||||
"R134_Object_252U_Custom_short": "Object 252U Vigilant",
|
||||
"R134_Object_252U_short": "Obj. 252U",
|
||||
"R140_M4A2_Loza": "Loza's M4-A2 Sherman",
|
||||
"R140_M4A2_Loza_short": "Loza's Sherman",
|
||||
@@ -5744,6 +5863,10 @@
|
||||
"R159_SU_130PM_short": "SU-130PM",
|
||||
"R160_object_244": "Object 244",
|
||||
"R160_object_244_short": "Object 244",
|
||||
"R172_Object_752": "Object 752",
|
||||
"R172_Object_752_short": "Object 752",
|
||||
"R180_Object_274A": "Object 274a",
|
||||
"R180_Object_274A_short": "Object 274a",
|
||||
"R43_T-70_57": "T-70/57",
|
||||
"R43_T-70_57_short": "T-70/57",
|
||||
"R63_ST_IBD": "T-2020",
|
||||
@@ -5754,11 +5877,13 @@
|
||||
"R98_T44_85": "T-44-85",
|
||||
"R98_T44_85_Custom": "T-44-85 Bravery",
|
||||
"R98_T44_85_Custom_short": "T-44-85 Bravery",
|
||||
"R98_T44_85_Custom_short_v2": "T-44-85 Space",
|
||||
"R98_T44_85_Custom_v2": "T-44-85 Space",
|
||||
"R98_T44_85_Custom_short_v2": "T-44-85 Cosmic",
|
||||
"R98_T44_85_Custom_v2": "T-44-85 Cosmic",
|
||||
"R98_T44_85_short": "T-44-85",
|
||||
"RX_20_1_chassis": "RX-20-1",
|
||||
"RX_20_1_turret": "RX-20-1",
|
||||
"R_Object_84": "Object 84",
|
||||
"R_Object_84_short": "Object 84",
|
||||
"Ram-II": "Ram II",
|
||||
"Ram_II_D1": "Ram II D1",
|
||||
"Redcell_Mk1": "Redcell Mk. 1",
|
||||
@@ -5821,6 +5946,8 @@
|
||||
"S07_Strv_74": "Strv 74",
|
||||
"S07_Strv_74_short": "Strv 74",
|
||||
"S13_Leo": "Leo",
|
||||
"S13_Leo_NY": "Svear",
|
||||
"S13_Leo_NY_short": "Svear",
|
||||
"S13_Leo_short": "Leo",
|
||||
"S16_Kranvagn": "Kranvagn",
|
||||
"S16_Kranvagn_Custom": "Kranvagn Nidhögg",
|
||||
@@ -5837,6 +5964,8 @@
|
||||
"S25_EMIL_51_Custom_short": "EMIL 1951 Viking",
|
||||
"S25_EMIL_51_short": "EMIL 1951",
|
||||
"S26_Lancen_C": "Lansen C",
|
||||
"S26_Lancen_C_Custom": "Lansen Moose",
|
||||
"S26_Lancen_C_Custom_short": "Lansen Moose",
|
||||
"S26_Lancen_C_short": "Lansen C",
|
||||
"S35_captured": "Pz.Kpfw. S35 739 (f)",
|
||||
"S35_captured_short": "Pz. S35",
|
||||
@@ -6118,6 +6247,7 @@
|
||||
"T95E2_short": "T95E2",
|
||||
"T99E2": "T99E2",
|
||||
"TD-12": "TD-12",
|
||||
"TD-12S": "TD-12S",
|
||||
"TD-30": "M-50D",
|
||||
"T_44_100_Custom": "T-44-100 Hellfire",
|
||||
"T_44_100_Custom_short": "T-44-100 Hellfire",
|
||||
@@ -6167,6 +6297,7 @@
|
||||
"V-12-5U": "V-12-5U",
|
||||
"V-12-5_shortened": "V-12-5 shortened",
|
||||
"V-12-6": "V-12-6",
|
||||
"V-12-6B": "V-12-6B",
|
||||
"V-12A": "V-12 A",
|
||||
"V-12P": "V-12P",
|
||||
"V-12PA": "V-12PA",
|
||||
@@ -6178,6 +6309,7 @@
|
||||
"V-2-34E": "V-2-34E",
|
||||
"V-2-34M": "V-2-34M",
|
||||
"V-2-34M_BP": "V-2-34M B",
|
||||
"V-2-34S": "V-2-34S",
|
||||
"V-2-44": "V-2-44",
|
||||
"V-2-54": "V-2-54",
|
||||
"V-2-54IS": "V-2-54IS",
|
||||
@@ -6195,6 +6327,7 @@
|
||||
"V-2SNx2": "2x V-2K-F",
|
||||
"V-4": "V-4",
|
||||
"V-44": "V-44",
|
||||
"V-44-2": "V-44-2",
|
||||
"V-44A": "V-44A",
|
||||
"V-5": "V-5",
|
||||
"V-54": "V-54",
|
||||
@@ -6204,6 +6337,7 @@
|
||||
"V-54K_TU": "V-54K",
|
||||
"V-55": "V-55",
|
||||
"V-55U": "V-14",
|
||||
"V-5BP": "V-5B",
|
||||
"V-5F": "V-5F",
|
||||
"V-5U": "V-5U",
|
||||
"V-64": "V-64",
|
||||
@@ -6294,6 +6428,7 @@
|
||||
"V_2_34M": "V-2-34M",
|
||||
"V_2_34_modificato": "V-2-34 modificato",
|
||||
"V_2_54SCG": "V-2-54SCG",
|
||||
"V_46_5": "V-46-5",
|
||||
"V_54": "V-54",
|
||||
"V_54_105": "V-54-105",
|
||||
"ValentineMK_I": "Valentine",
|
||||
|
||||
Reference in new issue
Block a user