Merge pull request #36 from Jylpah/master

Mix fixes
This commit is contained in:
Jylpah authored and GitHub committed 2023-09-10 17:48:11 +03:00
commit da8efce128
4 files changed
+550 -108

No files matched your search

+1
View File
@@ -13,3 +13,4 @@ export*
*.csv *.csv
!/stats/*.csv !/stats/*.csv
!*.py !*.py
/.venv
+89 -83
View File
@@ -100,7 +100,7 @@ class ThrottledClientSession(aiohttp.ClientSession):
def get_stats_str(self) -> str: def get_stats_str(self) -> str:
"""Print session statistics""" """Print session statistics"""
return f"rate limit: {str(self.rate_limit if self.rate_limit != None else '-')} \ return f"rate limit: {str(self.rate_limit if self.rate_limit is not None else '-')} \
rate: {0:.1f}.format(self.get_rate()) requests: {str(self._count)}" rate: {0:.1f}.format(self.get_rate()) requests: {str(self._count)}"
@@ -203,7 +203,7 @@ class AsyncLogger():
async def open(self, logfn: str = None): async def open(self, logfn: str = None):
"""Set logging to file""" """Set logging to file"""
if logfn == None: if logfn is None:
logfn = 'LOG_' + _randomword(6) + '.log' logfn = 'LOG_' + _randomword(6) + '.log'
try: try:
self._file = await aiofiles.open(logfn, mode='a') self._file = await aiofiles.open(logfn, mode='a')
@@ -216,7 +216,7 @@ class AsyncLogger():
async def logger(self): async def logger(self):
"""Async file logger""" """Async file logger"""
if self._file == None: if self._file is None:
error('No log file defined') error('No log file defined')
return False return False
while True: while True:
@@ -306,7 +306,7 @@ async def set_file_logging(logfn=None, add_timestamp=False):
"""Set logging to file""" """Set logging to file"""
global LOG, LOGGER global LOG, LOGGER
LOG = True LOG = True
if logfn == None: if logfn is None:
logfn = 'LOG_' + _randomword(6) logfn = 'LOG_' + _randomword(6)
else: else:
dateTimeObj = datetime.now() dateTimeObj = datetime.now()
@@ -382,7 +382,9 @@ def get_class_from_frame(fr):
return None return None
def _print_log_msg(prefix = 'LOG', msg = '', exception = None, id = None, print_msg : bool = True): def _print_log_msg(prefix: str = 'LOG', msg:str = '',
exception: Exception | None = None,
id: int|None = None, print_msg : bool = True):
# Use empty prefix to determine standard verbose messages # Use empty prefix to determine standard verbose messages
if not (print_msg or LOG): if not (print_msg or LOG):
return False return False
@@ -392,20 +394,20 @@ def _print_log_msg(prefix = 'LOG', msg = '', exception = None, id = None, print_
calframe = inspect.getouterframes(curframe) calframe = inspect.getouterframes(curframe)
class_name = get_class_from_frame(curframe) class_name = get_class_from_frame(curframe)
caller = calframe[2].function caller = calframe[2].function
if class_name != None: if class_name is not None:
prefix = prefix + ': ' + class_name + '.' + caller prefix = f'{prefix}: {class_name}.{caller}'
else: else:
prefix = prefix + ': ' + caller prefix = prefix + ': ' + caller
prefix = prefix + '(): ' prefix = prefix + '(): '
if id != None: if id is not None:
prefix = prefix + '[' + str(id) + ']: ' prefix = f'{prefix} [ {id}]: '
exception_msg = '' exception_msg = ''
if (exception != None) and isinstance(exception, Exception): if (exception is not None) and isinstance(exception, Exception):
exception_msg = ' : Exception: ' + str(type(exception)) + ' : ' + str(exception) exception_msg = f' : Exception: {type(exception)} : {exception}'
msg = prefix + msg + exception_msg msg = f'{prefix}{msg}{exception_msg}'
if print_msg: if print_msg:
print(msg) print(msg)
retval = True retval = True
@@ -414,8 +416,8 @@ def _print_log_msg(prefix = 'LOG', msg = '', exception = None, id = None, print_
return retval return retval
def _log_msg(msg =''): def _log_msg(msg :str=''):
if LOG and (LOGGER != None): if LOG and (LOGGER is not None):
LOGGER.log(msg) LOGGER.log(msg)
return True return True
return False return False
@@ -438,11 +440,11 @@ def get_progress_step():
def set_progress_bar(heading: str, max_value: int, step: int = None, slow: bool = False, id: str = None): def set_progress_bar(heading: str, max_value: int, step: int = None, slow: bool = False, id: str = None):
global _progress_obj, _progress_N, _progress_i, _progress_id global _progress_obj, _progress_N, _progress_i, _progress_id
_progress_id = id _progress_id = id
if step == None: if step is None:
_progress_N = int(max_value / 1000) if (max_value > 1000) else 2 _progress_N = int(max_value / 1000) if (max_value > 1000) else 2
else: else:
_progress_N = step _progress_N = step
if _progress_obj != None: if _progress_obj is not None:
finish_progress_bar() finish_progress_bar()
if slow: if slow:
_progress_obj = SlowBar(heading, max=max_value) _progress_obj = SlowBar(heading, max=max_value)
@@ -457,10 +459,10 @@ def set_progress_bar(heading: str, max_value: int, step: int = None, slow: bool
def set_counter(heading: str, step = None): def set_counter(heading: str, step = None):
global _progress_obj, _progress_i global _progress_obj, _progress_i
_progress_i = 0 _progress_i = 0
if _progress_obj != None: if _progress_obj is not None:
finish_progress_bar() finish_progress_bar()
_progress_obj = Counter(heading + ': ') _progress_obj = Counter(heading + ': ')
if step != None: if step is not None:
set_progress_step(step) set_progress_step(step)
return return
@@ -472,7 +474,7 @@ def print_progress(force = False, id : str = None) -> bool:
_progress_i += 1 _progress_i += 1
if ((_progress_i % _progress_N) == 0): if ((_progress_i % _progress_N) == 0):
if (_log_level > SILENT) and ( force or (_log_level < DEBUG ) ): if (_log_level > SILENT) and ( force or (_log_level < DEBUG ) ):
if (_progress_obj != None): if (_progress_obj is not None):
if (_progress_id == id): if (_progress_id == id):
_progress_obj.next(_progress_N) _progress_obj.next(_progress_N)
return True return True
@@ -489,7 +491,7 @@ def finish_progress_bar():
global _progress_obj global _progress_obj
# print_nl = True # print_nl = True
if _progress_obj != None: if _progress_obj is not None:
# if isinstance(_progress_obj, Counter): # if isinstance(_progress_obj, Counter):
# print_nl = False # print_nl = False
_progress_obj.finish() _progress_obj.finish()
@@ -574,7 +576,7 @@ async def open_JSON(filename: str, chk_JSON_func = None) -> dict:
try: try:
async with aiofiles.open(filename) as fp: async with aiofiles.open(filename) as fp:
json_data = json.loads(await fp.read()) json_data = json.loads(await fp.read())
if (chk_JSON_func == None): if (chk_JSON_func is None):
debug("JSON file content not checked: " + filename) debug("JSON file content not checked: " + filename)
return json_data return json_data
elif chk_JSON_func(json_data): elif chk_JSON_func(json_data):
@@ -589,10 +591,10 @@ async def open_JSON(filename: str, chk_JSON_func = None) -> dict:
async def get_url_JSON(session: aiohttp.ClientSession, url: str, chk_JSON_func = None, max_tries = MAX_RETRIES) -> dict: async def get_url_JSON(session: aiohttp.ClientSession, url: str, chk_JSON_func = None, max_tries = MAX_RETRIES) -> dict:
"""Retrieve (GET) an URL and return JSON object""" """Retrieve (GET) an URL and return JSON object"""
if session == None: if session is None:
error('Session must be initialized first') error('Session must be initialized first')
sys.exit(1) sys.exit(1)
if url == None: if url is None:
return None return None
# To avoid excessive use of servers # To avoid excessive use of servers
@@ -602,7 +604,7 @@ async def get_url_JSON(session: aiohttp.ClientSession, url: str, chk_JSON_func =
if resp.status == 200: if resp.status == 200:
debug('HTTP request OK') debug('HTTP request OK')
json_resp = await resp.json() json_resp = await resp.json()
if (chk_JSON_func == None) or chk_JSON_func(json_resp): if (chk_JSON_func is None) or chk_JSON_func(json_resp):
# debug("Received valid JSON: " + str(json_resp)) # debug("Received valid JSON: " + str(json_resp))
return json_resp return json_resp
# Sometimes WG API returns JSON error even a retry gives valid JSON # Sometimes WG API returns JSON error even a retry gives valid JSON
@@ -644,14 +646,18 @@ def bld_dict_hierarcy(d : dict, key : str, value) -> dict:
return None return None
def get_JSON_keypath(keypath: str, key: str): def get_JSON_keypath(keypath: str, key: str):
if keypath == None: if keypath is None:
return key return key
else: else:
return '.'.join([keypath, key]) return '.'.join([keypath, key])
def get_JSON_value(json, key : str = None, keys : list = None, keypath = None): def get_JSON_value(json, key : str | None = None, keys : list | None = None,
if (keys == None) and (key != None): keypath : str | None = None):
if (keys is None):
if key is not None:
keys = key.split('.') keys = key.split('.')
else:
return json
if len(keys) == 0: if len(keys) == 0:
return json return json
key = keys.pop(0) key = keys.pop(0)
@@ -678,7 +684,7 @@ def sort_dict(d: dict, number: bool = False) -> dict:
if number: if number:
return dict(sorted(d.items(), key=lambda item: int(item[0]))) return dict(sorted(d.items(), key=lambda item: int(item[0])))
else: else:
return dict(sorted(d.items(), key=lambda item: item[0])) return dict(sorted(d.items(), key=lambda item: str(item[0])))
# ----------------------------------------------------------- # -----------------------------------------------------------
@@ -874,7 +880,7 @@ class WG:
WG.tanks = self.tanks WG.tanks = self.tanks
self.global_rate_limit = global_rate_limit self.global_rate_limit = global_rate_limit
if (maps_fn != None): if (maps_fn is not None):
if os.path.exists(maps_fn) and os.path.isfile(maps_fn): if os.path.exists(maps_fn) and os.path.isfile(maps_fn):
try: try:
with open(maps_fn, 'rt', encoding='utf8') as f: with open(maps_fn, 'rt', encoding='utf8') as f:
@@ -883,7 +889,7 @@ class WG:
error('Could not read maps file: ' + maps_fn + '\n' + str(err)) error('Could not read maps file: ' + maps_fn + '\n' + str(err))
else: else:
verbose('Could not find maps file: ' + maps_fn) verbose('Could not find maps file: ' + maps_fn)
if self.WG_app_id != None: if self.WG_app_id is not None:
headers = {'Accept-Encoding': 'gzip, deflate'} headers = {'Accept-Encoding': 'gzip, deflate'}
if self.global_rate_limit: if self.global_rate_limit:
self.session = ThrottledClientSession(rate_limit=rate_limit, headers=headers) self.session = ThrottledClientSession(rate_limit=rate_limit, headers=headers)
@@ -912,7 +918,7 @@ class WG:
async def close(self): async def close(self):
# close stats queue # close stats queue
try: try:
if self.statsQ != None: if self.statsQ is not None:
debug('WG.close(): Waiting for statsQ to finish') debug('WG.close(): Waiting for statsQ to finish')
await self.statsQ.join() await self.statsQ.join()
debug('WG.close(): statsQ finished') debug('WG.close(): statsQ finished')
@@ -921,13 +927,13 @@ class WG:
await self.stat_saver_task await self.stat_saver_task
# close cacheDB # close cacheDB
if self.cache != None: if self.cache is not None:
# prune old cache records # prune old cache records
await self.cleanup_cache() await self.cleanup_cache()
await self.cache.commit() await self.cache.commit()
await self.cache.close() await self.cache.close()
if self.session != None: if self.session is not None:
if self.global_rate_limit: if self.global_rate_limit:
await self.session.close() await self.session.close()
else: else:
@@ -981,7 +987,7 @@ class WG:
@classmethod @classmethod
def chk_JSON(cls, json_obj, check = None) -> bool: def chk_JSON(cls, json_obj, check = None) -> bool:
try: try:
if (check == None): if (check is None):
# nothing to check # nothing to check
return True return True
if (check == 'tank_stats'): if (check == 'tank_stats'):
@@ -1016,16 +1022,16 @@ class WG:
@classmethod @classmethod
def chk_JSON_status(cls, json_resp: dict) -> bool: def chk_JSON_status(cls, json_resp: dict) -> bool:
try: try:
if (json_resp == None) or ('status' not in json_resp) or (json_resp['status'] == None): if (json_resp is None) or ('status' not in json_resp) or (json_resp['status'] is None):
return False return False
elif (json_resp['status'] == 'ok') and ('data' in json_resp): elif (json_resp['status'] == 'ok') and ('data' in json_resp):
return True return True
elif json_resp['status'] == 'error': elif json_resp['status'] == 'error':
if ('error' in json_resp): if ('error' in json_resp):
error_msg = 'Received an error' error_msg = 'Received an error'
if ('message' in json_resp['error']) and (json_resp['error']['message'] != None): if ('message' in json_resp['error']) and (json_resp['error']['message'] is not None):
error_msg = error_msg + ': ' + json_resp['error']['message'] error_msg = error_msg + ': ' + json_resp['error']['message']
if ('value' in json_resp['error']) and (json_resp['error']['value'] != None): if ('value' in json_resp['error']) and (json_resp['error']['value'] is not None):
error_msg = error_msg + ' Value: ' + json_resp['error']['value'] error_msg = error_msg + ' Value: ' + json_resp['error']['value']
debug(error_msg) debug(error_msg)
return False return False
@@ -1084,7 +1090,7 @@ class WG:
try: try:
if cls.chk_JSON_status(json_resp): if cls.chk_JSON_status(json_resp):
for acc in json_resp['data']: for acc in json_resp['data']:
if json_resp['data'][acc] != None: if json_resp['data'][acc] is not None:
return True return True
except KeyError as err: except KeyError as err:
error('Key not found', err) error('Key not found', err)
@@ -1110,7 +1116,7 @@ class WG:
# Methods -------------------------------------------------- # Methods --------------------------------------------------
def load_tanks(self, tankopedia_fn: str): def load_tanks(self, tankopedia_fn: str):
"""Load tanks from tankopedia JSON""" """Load tanks from tankopedia JSON"""
if tankopedia_fn == None: if tankopedia_fn is 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:
@@ -1118,7 +1124,7 @@ class WG:
self.tanks['tankStr'] = dict() self.tanks['tankStr'] = dict()
p = re.compile('.+_short$') p = re.compile('.+_short$')
for usr_str in self.tanks['userStr']: for usr_str in self.tanks['userStr']:
if p.match(usr_str) == None: if p.match(usr_str) is None:
self.tanks['tankStr'][self.tanks['userStr'][usr_str]] = usr_str 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):
@@ -1179,11 +1185,11 @@ class WG:
def get_url_clan_info(self, server: str, clan_id: int) -> str: def get_url_clan_info(self, server: str, clan_id: int) -> str:
try: try:
if server == None: if server is None:
return None return None
return self.URL_WG_SERVER[server] + self.URL_WG_CLAN_INFO + self.WG_app_id + '&clan_id=' + str(clan_id) return self.URL_WG_SERVER[server] + self.URL_WG_CLAN_INFO + self.WG_app_id + '&clan_id=' + str(clan_id)
except Exception as err: except Exception as err:
if (server == None) or (server.lower() not in WG.ACCOUNT_ID_SERVER.keys()): if (server is None) or (server.lower() not in WG.ACCOUNT_ID_SERVER.keys()):
error('No server name or invalid server name given: ' + server if (server != None) else '') error('No server name or invalid server name given: ' + server if (server != None) else '')
error('Available servers: ' + ', '.join(WG.ACCOUNT_ID_SERVER.keys())) error('Available servers: ' + ', '.join(WG.ACCOUNT_ID_SERVER.keys()))
error(exception=err) error(exception=err)
@@ -1196,15 +1202,15 @@ class WG:
def get_url_player_tanks_stats(self, account_id: int, tank_ids: list = [], fields: list = []) -> str: def get_url_player_tanks_stats(self, account_id: int, tank_ids: list = [], fields: list = []) -> str:
server = self.get_server(account_id) server = self.get_server(account_id)
if server == None: if server is None:
return None return None
if (tank_ids != None) and (len(tank_ids) > 0): if (tank_ids is not None) and (len(tank_ids) > 0):
tank_id_str= '&tank_id=' + '%2C'.join([ str(x) for x in tank_ids]) tank_id_str= '&tank_id=' + '%2C'.join([ str(x) for x in tank_ids])
else: else:
# emtpy tank-id list returns all the player's tanks # emtpy tank-id list returns all the player's tanks
tank_id_str = '' tank_id_str = ''
if (fields != None) and (len(fields) > 0): if (fields is not None) and (len(fields) > 0):
field_str = '&fields=' + '%2C'.join(fields) field_str = '&fields=' + '%2C'.join(fields)
else: else:
# return all the fields # return all the fields
@@ -1216,9 +1222,9 @@ class WG:
def get_url_player_stats(self, account_id, fields) -> str: def get_url_player_stats(self, account_id, fields) -> str:
try: try:
server = self.get_server(account_id) server = self.get_server(account_id)
if server == None: if server is None:
return None return None
if (fields != None) and (len(fields) > 0): if (fields is not None) and (len(fields) > 0):
field_str = '&fields=' + '%2C'.join(fields) field_str = '&fields=' + '%2C'.join(fields)
else: else:
# return all the fields # return all the fields
@@ -1226,7 +1232,7 @@ class WG:
return self.URL_WG_SERVER[server] + self.URL_WG_PLAYER_STATS + self.WG_app_id + '&account_id=' + str(account_id) + field_str return self.URL_WG_SERVER[server] + self.URL_WG_PLAYER_STATS + self.WG_app_id + '&account_id=' + str(account_id) + field_str
except Exception as err: except Exception as err:
if (server == None): if (server is None):
error('Invalid account_id') error('Invalid account_id')
error(exception=err) error(exception=err)
return None return None
@@ -1237,10 +1243,10 @@ class WG:
# assumming that all account_ids are from the same server. This has to be taken care. # assumming that all account_ids are from the same server. This has to be taken care.
server = self.get_server(account_ids[0]) server = self.get_server(account_ids[0])
if server == None: if server is None:
return None return None
account_ids_str = '%2C'.join(str(id) for id in account_ids) account_ids_str = '%2C'.join(str(id) for id in account_ids)
if (fields != None) and (len(fields) > 0): if (fields is not None) and (len(fields) > 0):
field_str = '&fields=' + '%2C'.join(fields) field_str = '&fields=' + '%2C'.join(fields)
else: else:
# return all the fields # return all the fields
@@ -1248,7 +1254,7 @@ class WG:
return self.URL_WG_SERVER[server] + self.URL_WG_PLAYER_ACHIEVEMENTS + self.WG_app_id + '&account_id=' + account_ids_str + field_str return self.URL_WG_SERVER[server] + self.URL_WG_PLAYER_ACHIEVEMENTS + self.WG_app_id + '&account_id=' + account_ids_str + field_str
except Exception as err: except Exception as err:
if (server == None): if (server is None):
error('Invalid account_id') error('Invalid account_id')
error(exception=err) error(exception=err)
return None return None
@@ -1258,9 +1264,9 @@ class WG:
try: try:
return self.URL_WG_SERVER[server] + self.URL_WG_ACCOUNT_ID + self.WG_app_id + '&search=' + urllib.parse.quote(nickname) return self.URL_WG_SERVER[server] + self.URL_WG_ACCOUNT_ID + self.WG_app_id + '&search=' + urllib.parse.quote(nickname)
except Exception as err: except Exception as err:
if nickname == None or len(nickname) == 0: if nickname is None or len(nickname) == 0:
error('No nickname given') error('No nickname given')
if (server == None) or (server.lower() not in WG.ACCOUNT_ID_SERVER.keys()): if (server is None) or (server.lower() not in WG.ACCOUNT_ID_SERVER.keys()):
error('No server name or invalid server name given: ' + server if (server != None) else '') error('No server name or invalid server name given: ' + server if (server != None) else '')
error('Available servers: ' + ', '.join(WG.ACCOUNT_ID_SERVER.keys())) error('Available servers: ' + ', '.join(WG.ACCOUNT_ID_SERVER.keys()))
error(exception=err) error(exception=err)
@@ -1308,7 +1314,7 @@ class WG:
nick, server = nickname.split('@') nick, server = nickname.split('@')
debug(nick + ' @ '+ server) debug(nick + ' @ '+ server)
server = server.lower() server = server.lower()
if nick == None or server == None: if nick is None or server is None:
raise ValueError('Invalid nickname given: ' + nickname) raise ValueError('Invalid nickname given: ' + nickname)
url = self.get_url_account_id(nick, server) url = self.get_url_account_id(nick, server)
@@ -1331,7 +1337,7 @@ class WG:
# try cached stats first: # try cached stats first:
if cache: if cache:
stats = await self.get_cached_tank_stats(account_id, tank_ids, fields) stats = await self.get_cached_tank_stats(account_id, tank_ids, fields)
if stats != None: if stats is not None:
return stats return stats
if cache_only: if cache_only:
return None return None
@@ -1339,7 +1345,7 @@ class WG:
# Cached stats not found, fetching new ones # Cached stats not found, fetching new ones
url = self.get_url_player_tanks_stats(account_id, tank_ids, fields) url = self.get_url_player_tanks_stats(account_id, tank_ids, fields)
json_data = await self.get_url_JSON(url, self.chk_JSON_status) json_data = await self.get_url_JSON(url, self.chk_JSON_status)
if json_data != None: if json_data is not None:
# debug('JSON Response received: ' + str(json_data)) # debug('JSON Response received: ' + str(json_data))
stats = json_data['data'][str(account_id)] stats = json_data['data'][str(account_id)]
if cache: if cache:
@@ -1372,7 +1378,7 @@ class WG:
# Cached stats not found, fetching new ones # Cached stats not found, fetching new ones
url = self.get_url_player_stats(account_id, fields) url = self.get_url_player_stats(account_id, fields)
json_data = await self.get_url_JSON(url, self.chk_JSON_status) json_data = await self.get_url_JSON(url, self.chk_JSON_status)
if json_data != None: if json_data is not None:
# debug('JSON Response received: ' + str(json_data)) # debug('JSON Response received: ' + str(json_data))
stats = json_data['data'][str(account_id)] stats = json_data['data'][str(account_id)]
if cache: if cache:
@@ -1410,7 +1416,7 @@ class WG:
# Cached stats not found, fetching new ones # Cached stats not found, fetching new ones
url = self.get_url_player_achievements(list(account_ids), fields) url = self.get_url_player_achievements(list(account_ids), fields)
json_data = await self.get_url_JSON(url, self.chk_JSON_status) json_data = await self.get_url_JSON(url, self.chk_JSON_status)
if (json_data != None) and ('data' in json_data): if (json_data is not None) and ('data' in json_data):
# debug('JSON Response received: ' + str(json_data)) # debug('JSON Response received: ' + str(json_data))
for account_id in json_data['data'].keys(): for account_id in json_data['data'].keys():
stats[account_id] = json_data['data'][account_id] stats[account_id] = json_data['data'][account_id]
@@ -1424,7 +1430,7 @@ class WG:
def merge_player_stats(self, stats1: dict, stats2: dict) -> dict: def merge_player_stats(self, stats1: dict, stats2: dict) -> dict:
try: try:
if stats2 == None: return stats1 if stats2 is None: return stats1
for keyA in stats2: for keyA in stats2:
if keyA not in stats1: if keyA not in stats1:
stats1[keyA] = stats2[keyA] stats1[keyA] = stats2[keyA]
@@ -1438,7 +1444,7 @@ class WG:
def get_tank_data(self, tank_id: int, field: str): def get_tank_data(self, tank_id: int, field: str):
if self.tanks == None: if self.tanks is None:
return None return None
try: try:
return self.tanks['data'][str(tank_id)][field] return self.tanks['data'][str(tank_id)][field]
@@ -1524,7 +1530,7 @@ class WG:
async def save_stats(self, statsType: str, key: list, stats: list): async def save_stats(self, statsType: str, key: list, stats: list):
"""Save stats to a async queue to be saved by the stat_saver -task""" """Save stats to a async queue to be saved by the stat_saver -task"""
if self.statsQ == None: if self.statsQ is None:
return False return False
else: else:
await self.statsQ.put([ statsType, key, stats, NOW() ]) await self.statsQ.put([ statsType, key, stats, NOW() ])
@@ -1558,7 +1564,7 @@ class WG:
async def stat_saver(self): async def stat_saver(self):
"""Async task for saving stats into cache in background""" """Async task for saving stats into cache in background"""
if self.statsQ == None: if self.statsQ is None:
error('No statsQ defined') error('No statsQ defined')
return None return None
try: try:
@@ -1612,12 +1618,12 @@ class WG:
async def cleanup_cache(self, grace_time = CACHE_GRACE_TIME): async def cleanup_cache(self, grace_time = CACHE_GRACE_TIME):
"""Clean old cache records""" """Clean old cache records"""
if self.cache == None: if self.cache is None:
debug('No active cache') debug('No active cache')
return None return None
for table in WG.SQL_TABLES: for table in WG.SQL_TABLES:
async with self.cache.execute(WG.SQL_CHECK_TABLE_EXITS, (table,)) as cursor: async with self.cache.execute(WG.SQL_CHECK_TABLE_EXITS, (table,)) as cursor:
if (await cursor.fetchone()) != None: if (await cursor.fetchone()) is not None:
debug('Pruning cache table: ' + table) debug('Pruning cache table: ' + table)
await self.cache.execute(WG.SQL_PRUNE_CACHE.format(table, NOW() - grace_time)) await self.cache.execute(WG.SQL_PRUNE_CACHE.format(table, NOW() - grace_time))
await self.cache.commit() await self.cache.commit()
@@ -1631,7 +1637,7 @@ class WG:
try: try:
account_id = key[0] account_id = key[0]
tank_ids = set(key[1]) tank_ids = set(key[1])
if stats_data != None: if stats_data is not None:
for stat in stats_data: for stat in stats_data:
tank_id = stat['tank_id'] tank_id = stat['tank_id']
await self.cache.execute(WG.SQL_TANK_STATS_UPDATE, (account_id, tank_id, update_time, json.dumps(stat))) await self.cache.execute(WG.SQL_TANK_STATS_UPDATE, (account_id, tank_id, update_time, json.dumps(stat)))
@@ -1653,7 +1659,7 @@ class WG:
try: try:
account_id = key[0] account_id = key[0]
if stats_data != None: if stats_data is not None:
await self.cache.execute(WG.SQL_PLAYER_STATS_UPDATE, (account_id, update_time, json.dumps(stats_data))) await self.cache.execute(WG.SQL_PLAYER_STATS_UPDATE, (account_id, update_time, json.dumps(stats_data)))
else: else:
await self.cache.execute(WG.SQL_PLAYER_STATS_UPDATE, (account_id, update_time, None)) await self.cache.execute(WG.SQL_PLAYER_STATS_UPDATE, (account_id, update_time, None))
@@ -1671,7 +1677,7 @@ class WG:
try: try:
account_id = key[0] account_id = key[0]
if stats_data != None: if stats_data is not None:
await self.cache.execute(WG.SQL_PLAYER_ACHIEVEMENTS_UPDATE, (account_id, update_time, json.dumps(stats_data))) await self.cache.execute(WG.SQL_PLAYER_ACHIEVEMENTS_UPDATE, (account_id, update_time, json.dumps(stats_data)))
else: else:
await self.cache.execute(WG.SQL_PLAYER_ACHIEVEMENTS_UPDATE, (account_id, update_time, None)) await self.cache.execute(WG.SQL_PLAYER_ACHIEVEMENTS_UPDATE, (account_id, update_time, None))
@@ -1689,7 +1695,7 @@ class WG:
try: try:
# test for cacheDB existence # test for cacheDB existence
debug('Trying cached stats first') debug('Trying cached stats first')
if self.cache == None: if self.cache is None:
debug('No cache DB') debug('No cache DB')
return None return None
@@ -1703,7 +1709,7 @@ class WG:
tank_ids = set(tank_ids) tank_ids = set(tank_ids)
async for row in cursor: async for row in cursor:
# debug('account_id: ' + str(account_id) + ': 1') # debug('account_id: ' + str(account_id) + ': 1')
if row[3] == None: if row[3] is None:
# None/null stats found in cache # None/null stats found in cache
# i.e. stats have been requested, but not returned from WG API # i.e. stats have been requested, but not returned from WG API
tank_ids.remove(row[1]) tank_ids.remove(row[1])
@@ -1728,20 +1734,20 @@ class WG:
try: try:
# test for cacheDB existence # test for cacheDB existence
debug('Trying cached stats first') debug('Trying cached stats first')
if self.cache == None: if self.cache is None:
# debug('No cache DB') # debug('No cache DB')
raise StatsNotFound('No cache DB in use') raise StatsNotFound('No cache DB in use')
async with self.cache.execute(WG.SQL_PLAYER_STATS_CACHED, [account_id, NOW() - WG.CACHE_GRACE_TIME] ) as cursor: async with self.cache.execute(WG.SQL_PLAYER_STATS_CACHED, [account_id, NOW() - WG.CACHE_GRACE_TIME] ) as cursor:
row = await cursor.fetchone() row = await cursor.fetchone()
# debug('account_id: ' + str(account_id) + ': 1') # debug('account_id: ' + str(account_id) + ': 1')
if row == None: if row is None:
# no cached stats found, marked with an empty array # no cached stats found, marked with an empty array
# debug('No cached stats found') # debug('No cached stats found')
raise StatsNotFound('No cached stats found') raise StatsNotFound('No cached stats found')
debug('Cached stats found') debug('Cached stats found')
if row[2] == None: if row[2] is None:
# None/null stats found in cache # None/null stats found in cache
# i.e. stats have been requested, but not returned from WG API # i.e. stats have been requested, but not returned from WG API
return None return None
@@ -1760,20 +1766,20 @@ class WG:
try: try:
# test for cacheDB existence # test for cacheDB existence
debug('Trying cached stats first') debug('Trying cached stats first')
if self.cache == None: if self.cache is None:
# debug('No cache DB') # debug('No cache DB')
raise StatsNotFound('No cache DB in use') raise StatsNotFound('No cache DB in use')
async with self.cache.execute(WG.SQL_PLAYER_ACHIEVEMENTS_CACHED, [account_id, NOW() - WG.CACHE_GRACE_TIME] ) as cursor: async with self.cache.execute(WG.SQL_PLAYER_ACHIEVEMENTS_CACHED, [account_id, NOW() - WG.CACHE_GRACE_TIME] ) as cursor:
row = await cursor.fetchone() row = await cursor.fetchone()
# debug('account_id: ' + str(account_id) + ': 1') # debug('account_id: ' + str(account_id) + ': 1')
if row == None: if row is None:
# no cached stats found, marked with an empty array # no cached stats found, marked with an empty array
# debug('No cached stats found') # debug('No cached stats found')
raise StatsNotFound('No cached stats found') raise StatsNotFound('No cached stats found')
debug('Cached stats found') debug('Cached stats found')
if row[2] == None: if row[2] is None:
# None/null stats found in cache # None/null stats found in cache
# i.e. stats have been requested, but not returned from WG API # i.e. stats have been requested, but not returned from WG API
return None return None
@@ -1876,7 +1882,7 @@ class WoTinspector:
async def post_replay(self, data, filename = 'Replay', account_id = 0, title = 'Replay', priv = False, N = None): async def post_replay(self, data, filename = 'Replay', account_id = 0, title = 'Replay', priv = False, N = None):
try: try:
N = N if N != None else self.REPLAY_N N = N if N is not None else self.REPLAY_N
self.REPLAY_N += 1 self.REPLAY_N += 1
hash = hashlib.md5() hash = hashlib.md5()
@@ -1885,7 +1891,7 @@ class WoTinspector:
## Testing if the replay has already been posted ## Testing if the replay has already been posted
json_resp = await self.get_replay_JSON(replay_id) json_resp = await self.get_replay_JSON(replay_id)
if json_resp != None: if json_resp is not None:
debug('Already uploaded: ' + title, id=N) debug('Already uploaded: ' + title, id=N)
return json_resp return json_resp
@@ -1981,7 +1987,7 @@ class WoTinspector:
""""Check String for being a valid JSON file""" """"Check String for being a valid JSON file"""
try: try:
if ('status' in json_resp) and json_resp['status'] == 'ok' and \ if ('status' in json_resp) and json_resp['status'] == 'ok' and \
(get_JSON_value(json_resp, key='data.summary.exp_base') != None) : (get_JSON_value(json_resp, key='data.summary.exp_base') is not None) :
debug("JSON check OK") debug("JSON check OK")
return True return True
except KeyError as err: except KeyError as err:
@@ -2008,7 +2014,7 @@ class BlitzStars:
self.session = ThrottledClientSession(rate_limit=rate_limit, headers=headers) self.session = ThrottledClientSession(rate_limit=rate_limit, headers=headers)
async def close(self): async def close(self):
if self.session != None: if self.session is not None:
await self.session.close() await self.session.close()
@@ -2066,7 +2072,7 @@ class BlitzStars:
async def tank_stats2WG(cls, BS_tank_stats: list) -> list: async def tank_stats2WG(cls, BS_tank_stats: list) -> list:
"""Convert BlitzStars player tank stats to WG API format""" """Convert BlitzStars player tank stats to WG API format"""
try: try:
if BS_tank_stats == None: if BS_tank_stats is None:
return None return None
WG_tank_stats = [] WG_tank_stats = []
for stat in BS_tank_stats: for stat in BS_tank_stats:
@@ -2108,7 +2114,7 @@ class BlitzStars:
# Cached stats not found, fetching new ones # Cached stats not found, fetching new ones
url = self.get_url_player_stats(account_id) url = self.get_url_player_stats(account_id)
stats = await get_url_JSON(self.session, url, self.chk_JSON_player_stats) stats = await get_url_JSON(self.session, url, self.chk_JSON_player_stats)
if stats != None: if stats is not None:
# debug('JSON Response received: ' + str(json_data)) # debug('JSON Response received: ' + str(json_data))
if cache: if cache:
error('CACHE NOT IMPLEMENTED YET FOR BlitzStars()') error('CACHE NOT IMPLEMENTED YET FOR BlitzStars()')
@@ -2139,13 +2145,13 @@ class BlitzStars:
try: try:
# Cached stats not found, fetching new ones # Cached stats not found, fetching new ones
if tank_id == None: if tank_id is None:
# get stats for all the player's tanks # get stats for all the player's tanks
url = self.get_url_player_tanks_stats(account_id) url = self.get_url_player_tanks_stats(account_id)
else: else:
url = self.get_url_player_tank_stats(account_id, tank_id) url = self.get_url_player_tank_stats(account_id, tank_id)
stats = await get_url_JSON(self.session, url, self.chk_JSON_tank_stats) stats = await get_url_JSON(self.session, url, self.chk_JSON_tank_stats)
if stats != None: if stats is not None:
# debug('JSON Response received: ' + str(json_data)) # debug('JSON Response received: ' + str(json_data))
if cache: if cache:
error('CACHE NOT IMPLEMENTED YET FOR BlitzStars()') error('CACHE NOT IMPLEMENTED YET FOR BlitzStars()')
+33 -18
View File
@@ -4,7 +4,11 @@
# Extract tankopedia data in WG API JSON format from Blitz app files (Android APK unzipped) # Extract tankopedia data in WG API JSON format from Blitz app files (Android APK unzipped)
from os import path from os import path
import sys, argparse, json, os, inspect, aiohttp, asyncio, aiofiles, re, logging, time, xmltodict, collections, configparser import aiofiles # type: ignore
import xmltodict # type: ignore
from asyncio import set_event_loop_policy, run, create_task, get_event_loop_policy
import sys, argparse, json, os, inspect, asyncio, re, logging, time, configparser
from collections import OrderedDict
import blitzutils as bu import blitzutils as bu
from blitzutils import WG from blitzutils import WG
@@ -18,10 +22,10 @@ BLITZAPP_STRINGS='Data/Strings/en.yaml'
BLITZAPP_VEHICLES_DIR='Data/XML/item_defs/vehicles/' BLITZAPP_VEHICLES_DIR='Data/XML/item_defs/vehicles/'
BLITZAPP_VEHICLE_FILE='list.xml' BLITZAPP_VEHICLE_FILE='list.xml'
wg = None wg : WG | None = None
## main() ------------------------------------------------------------- ## main() -------------------------------------------------------------
async def main(argv): async def main() -> None:
global wg global wg
# set the directory for the script # set the directory for the script
os.chdir(os.path.dirname(sys.argv[0])) os.chdir(os.path.dirname(sys.argv[0]))
@@ -79,7 +83,7 @@ async def main(argv):
# merge old and new tankopedia # merge old and new tankopedia
tanks.update(new_tanks) tanks.update(new_tanks)
userStrs.update(new_userStrs) userStrs.update(new_userStrs)
tankopedia = collections.OrderedDict() tankopedia : OrderedDict[str, str| int | dict ]= OrderedDict()
tankopedia['status'] = 'ok' tankopedia['status'] = 'ok'
tankopedia['meta'] = { "count": len(tanks) } tankopedia['meta'] = { "count": len(tanks) }
tankopedia['data'] = bu.sort_dict(tanks, number=True) tankopedia['data'] = bu.sort_dict(tanks, number=True)
@@ -88,7 +92,7 @@ async def main(argv):
bu.verbose_std('New tankopedia \'' + args.tanks + '\' contains ' + str(len(userStrs)) + ' tank strings') bu.verbose_std('New tankopedia \'' + args.tanks + '\' contains ' + str(len(userStrs)) + ' tank strings')
await outfile.write(json.dumps(tankopedia, ensure_ascii=False, indent=4, sort_keys=False)) await outfile.write(json.dumps(tankopedia, ensure_ascii=False, indent=4, sort_keys=False))
if args.maps != None: if args.maps is not None:
maps = {} maps = {}
if os.path.exists(args.maps): if os.path.exists(args.maps):
try: try:
@@ -120,7 +124,7 @@ async def extract_tanks(blitz_app_base : str, nation: str):
tankList = xmltodict.parse(await f.read()) tankList = xmltodict.parse(await f.read())
for data in tankList['root'].keys(): for data in tankList['root'].keys():
tank_xml = tankList['root'][data] tank_xml = tankList['root'][data]
tank = dict() tank : dict[str, bool | int | str]= dict()
tank['is_premium'] = issubclass(type(tank_xml['price']), dict) tank['is_premium'] = issubclass(type(tank_xml['price']), dict)
tank['nation'] = nation tank['nation'] = nation
tank['tank_id'] = get_tank_id(nation, int(tank_xml['id'])) tank['tank_id'] = get_tank_id(nation, int(tank_xml['id']))
@@ -134,7 +138,7 @@ async def extract_tanks(blitz_app_base : str, nation: str):
sys.exit(2) sys.exit(2)
return tanks return tanks
async def read_user_strs(blitz_app_base : str) -> dict: async def read_user_strs(blitz_app_base : str) -> tuple[dict, dict]:
"""Read user strings to convert map and tank names""" """Read user strings to convert map and tank names"""
tank_strs = {} tank_strs = {}
map_strs = {} map_strs = {}
@@ -147,11 +151,11 @@ async def read_user_strs(blitz_app_base : str) -> dict:
async for l in f: async for l in f:
m = p_tank.match(l) m = p_tank.match(l)
if m != None: if m is not None:
tank_strs[m.group(1)] = m.group(2) tank_strs[m.group(1)] = m.group(2)
m = p_map.match(l) m = p_map.match(l)
if m != None and m.group(2) != 'Macragge': if m is not None and m.group(2) != 'Macragge':
map_strs[m.group(1)] = m.group(2) map_strs[m.group(1)] = m.group(2)
except Exception as err: except Exception as err:
@@ -160,20 +164,30 @@ async def read_user_strs(blitz_app_base : str) -> dict:
return tank_strs, map_strs return tank_strs, map_strs
async def convert_tank_names(tanklist : list, tank_strs: dict) -> dict: async def convert_tank_names(tanklist : list, tank_strs: dict) -> tuple[dict, dict]:
"""Convert tank names for Tankopedia""" """Convert tank names for Tankopedia"""
tankopedia = {} tankopedia = {}
userStrs = {} userStrs = {}
bu.debug(f'tank_strs:')
for key, value in tank_strs.items():
bu.debug(f'{key}: {value}')
bu.debug('---------')
try: try:
for tank in tanklist: for tank in tanklist:
try:
bu.debug(f'tank: {tank}')
if tank['userStr'] in tank_strs:
tank['name'] = tank_strs[tank['userStr']] tank['name'] = tank_strs[tank['userStr']]
#userStrs[tank['userStr'].split(':')[1]] = tank['name'] else:
tank['name'] = tank['userStr'].split(':')[1]
tank.pop('userStr', None) tank.pop('userStr', None)
tank_tmp = collections.OrderedDict() tank_tmp = OrderedDict()
for key in sorted(tank.keys()): for key in sorted(tank.keys()):
tank_tmp[key] = tank[key] tank_tmp[key] = tank[key]
tankopedia[str(tank['tank_id'])] = tank_tmp tankopedia[str(tank['tank_id'])] = tank_tmp
except:
bu.error(f'Could not process tank: {tank}')
for tank_str in tank_strs: for tank_str in tank_strs:
skip = False skip = False
@@ -191,11 +205,11 @@ async def convert_tank_names(tanklist : list, tank_strs: dict) -> dict:
userStrs[key] = tank_strs[tank_str] userStrs[key] = tank_strs[tank_str]
# sorting # sorting
tankopedia_sorted = collections.OrderedDict() tankopedia_sorted = OrderedDict()
for tank_id in sorted(tankopedia.keys(), key=int): for tank_id in sorted(tankopedia.keys(), key=int):
tankopedia_sorted[str(tank_id)] = tankopedia[str(tank_id)] tankopedia_sorted[str(tank_id)] = tankopedia[str(tank_id)]
userStrs_sorted = collections.OrderedDict() userStrs_sorted = OrderedDict()
for userStr in sorted(userStrs.keys()): for userStr in sorted(userStrs.keys()):
userStrs_sorted[userStr] = userStrs[userStr] userStrs_sorted[userStr] = userStrs[userStr]
# bu.debug('Tank strings: ' + str(len(userStrs_sorted))) # bu.debug('Tank strings: ' + str(len(userStrs_sorted)))
@@ -208,10 +222,12 @@ async def convert_tank_names(tanklist : list, tank_strs: dict) -> dict:
def get_tank_id(nation: str, tank_id : int) -> int: def get_tank_id(nation: str, tank_id : int) -> int:
assert wg is not None, "wg has to be set"
return (tank_id << 8) + (wg.NATION_ID[nation] << 4) + 1 return (tank_id << 8) + (wg.NATION_ID[nation] << 4) + 1
def get_tank_type(tagstr : str): def get_tank_type(tagstr : str):
assert wg is not None, "wg has to be set"
tags = tagstr.split(' ') tags = tagstr.split(' ')
for t_type in wg.TANK_TYPE: for t_type in wg.TANK_TYPE:
if tags[0] == t_type: if tags[0] == t_type:
@@ -227,7 +243,6 @@ if __name__ == "__main__":
except ImportError: except ImportError:
pass pass
else: else:
if not isinstance(asyncio.get_event_loop_policy(), WindowsSelectorEventLoopPolicy): if not isinstance(get_event_loop_policy(), WindowsSelectorEventLoopPolicy):
asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) set_event_loop_policy(WindowsSelectorEventLoopPolicy())
#asyncio.run(main(sys.argv[1:]), debug=True) run(main())
asyncio.run(main(sys.argv[1:]))
+427 -7
View File
@@ -1,7 +1,7 @@
{ {
"status": "ok", "status": "ok",
"meta": { "meta": {
"count": 564 "count": 592
}, },
"data": { "data": {
"1": { "1": {
@@ -788,6 +788,14 @@
"tier": 5, "tier": 5,
"type": "heavyTank" "type": "heavyTank"
}, },
"3377": {
"is_premium": false,
"name": "WZ-131",
"nation": "china",
"tank_id": 3377,
"tier": 7,
"type": "lightTank"
},
"3425": { "3425": {
"is_premium": false, "is_premium": false,
"name": "Type 61", "name": "Type 61",
@@ -876,6 +884,14 @@
"tier": 7, "tier": 7,
"type": "heavyTank" "type": "heavyTank"
}, },
"3889": {
"is_premium": false,
"name": "WZ-132",
"nation": "china",
"tank_id": 3889,
"tier": 8,
"type": "lightTank"
},
"3905": { "3905": {
"is_premium": false, "is_premium": false,
"name": "AMX 50 120", "name": "AMX 50 120",
@@ -1092,6 +1108,14 @@
"tier": 3, "tier": 3,
"type": "mediumTank" "type": "mediumTank"
}, },
"4913": {
"is_premium": false,
"name": "59-16",
"nation": "china",
"tank_id": 4913,
"tier": 6,
"type": "lightTank"
},
"4929": { "4929": {
"is_premium": false, "is_premium": false,
"name": "AMX 13 90", "name": "AMX 13 90",
@@ -2172,6 +2196,14 @@
"tier": 6, "tier": 6,
"type": "lightTank" "type": "lightTank"
}, },
"9777": {
"is_premium": true,
"name": "WZ-114",
"nation": "china",
"tank_id": 9777,
"tier": 9,
"type": "heavyTank"
},
"9793": { "9793": {
"is_premium": false, "is_premium": false,
"name": "Somua SAu 40", "name": "Somua SAu 40",
@@ -2228,6 +2260,14 @@
"tier": 6, "tier": 6,
"type": "mediumTank" "type": "mediumTank"
}, },
"10033": {
"is_premium": false,
"name": "WZ-132A",
"nation": "china",
"tank_id": 10033,
"tier": 9,
"type": "lightTank"
},
"10049": { "10049": {
"is_premium": false, "is_premium": false,
"name": "S35 CA", "name": "S35 CA",
@@ -2284,6 +2324,14 @@
"tier": 4, "tier": 4,
"type": "AT-SPG" "type": "AT-SPG"
}, },
"10289": {
"is_premium": false,
"name": "WZ-132-1",
"nation": "china",
"tank_id": 10289,
"tier": 10,
"type": "lightTank"
},
"10353": { "10353": {
"is_premium": true, "is_premium": true,
"name": "Pharaoh", "name": "Pharaoh",
@@ -2293,7 +2341,7 @@
"type": "heavyTank" "type": "heavyTank"
}, },
"10369": { "10369": {
"is_premium": true, "is_premium": false,
"name": "Controcarro 3 Minotauro", "name": "Controcarro 3 Minotauro",
"nation": "european", "nation": "european",
"tank_id": 10369, "tank_id": 10369,
@@ -2324,6 +2372,22 @@
"tier": 5, "tier": 5,
"type": "AT-SPG" "type": "AT-SPG"
}, },
"10545": {
"is_premium": true,
"name": "Wind",
"nation": "china",
"tank_id": 10545,
"tier": 6,
"type": "lightTank"
},
"10625": {
"is_premium": false,
"name": "Controcarro 1 Mk. 2",
"nation": "european",
"tank_id": 10625,
"tier": 9,
"type": "AT-SPG"
},
"10753": { "10753": {
"is_premium": false, "is_premium": false,
"name": "ST-I", "name": "ST-I",
@@ -2348,6 +2412,14 @@
"tier": 10, "tier": 10,
"type": "heavyTank" "type": "heavyTank"
}, },
"10801": {
"is_premium": true,
"name": "Panlong",
"nation": "china",
"tank_id": 10801,
"tier": 6,
"type": "AT-SPG"
},
"10817": { "10817": {
"is_premium": false, "is_premium": false,
"name": "AMX AC mle. 46", "name": "AMX AC mle. 46",
@@ -2356,6 +2428,14 @@
"tier": 7, "tier": 7,
"type": "AT-SPG" "type": "AT-SPG"
}, },
"10881": {
"is_premium": false,
"name": "Semovente Controcarro mod. 1964",
"nation": "european",
"tank_id": 10881,
"tier": 8,
"type": "AT-SPG"
},
"11009": { "11009": {
"is_premium": false, "is_premium": false,
"name": "KV-4", "name": "KV-4",
@@ -2388,6 +2468,14 @@
"tier": 9, "tier": 9,
"type": "AT-SPG" "type": "AT-SPG"
}, },
"11137": {
"is_premium": false,
"name": "Semovente Controcarro mod. 1956",
"nation": "european",
"tank_id": 11137,
"tier": 7,
"type": "AT-SPG"
},
"11265": { "11265": {
"is_premium": true, "is_premium": true,
"name": "T-150", "name": "T-150",
@@ -2412,6 +2500,14 @@
"tier": 8, "tier": 8,
"type": "AT-SPG" "type": "AT-SPG"
}, },
"11393": {
"is_premium": false,
"name": "Semovente M43 Bassotto",
"nation": "european",
"tank_id": 11393,
"tier": 6,
"type": "AT-SPG"
},
"11521": { "11521": {
"is_premium": false, "is_premium": false,
"name": "IS-8", "name": "IS-8",
@@ -2444,6 +2540,14 @@
"tier": 6, "tier": 6,
"type": "AT-SPG" "type": "AT-SPG"
}, },
"11649": {
"is_premium": false,
"name": "Semovente M41",
"nation": "european",
"tank_id": 11649,
"tier": 5,
"type": "AT-SPG"
},
"11777": { "11777": {
"is_premium": false, "is_premium": false,
"name": "KV-1", "name": "KV-1",
@@ -2532,6 +2636,14 @@
"tier": 4, "tier": 4,
"type": "mediumTank" "type": "mediumTank"
}, },
"12417": {
"is_premium": true,
"name": "Bisonte C45",
"nation": "european",
"tank_id": 12417,
"tier": 8,
"type": "heavyTank"
},
"12545": { "12545": {
"is_premium": true, "is_premium": true,
"name": "K-91", "name": "K-91",
@@ -2540,6 +2652,14 @@
"tier": 9, "tier": 9,
"type": "heavyTank" "type": "heavyTank"
}, },
"12673": {
"is_premium": true,
"name": "Bofors Tornvagn",
"nation": "european",
"tank_id": 12673,
"tier": 8,
"type": "heavyTank"
},
"13073": { "13073": {
"is_premium": true, "is_premium": true,
"name": "Pz.Kpfw. II Ausf. G", "name": "Pz.Kpfw. II Ausf. G",
@@ -2556,6 +2676,14 @@
"tier": 10, "tier": 10,
"type": "AT-SPG" "type": "AT-SPG"
}, },
"13185": {
"is_premium": true,
"name": "Vz. 55",
"nation": "european",
"tank_id": 13185,
"tier": 10,
"type": "heavyTank"
},
"13329": { "13329": {
"is_premium": true, "is_premium": true,
"name": "Durchbruchswagen 2", "name": "Durchbruchswagen 2",
@@ -3140,6 +3268,14 @@
"tier": 8, "tier": 8,
"type": "AT-SPG" "type": "AT-SPG"
}, },
"19521": {
"is_premium": true,
"name": "Bat-Chat-25CL",
"nation": "france",
"tank_id": 19521,
"tier": 9,
"type": "lightTank"
},
"19537": { "19537": {
"is_premium": false, "is_premium": false,
"name": "Vickers Light 105", "name": "Vickers Light 105",
@@ -3212,6 +3348,14 @@
"tier": 9, "tier": 9,
"type": "lightTank" "type": "lightTank"
}, },
"20033": {
"is_premium": true,
"name": "Char Futur 4",
"nation": "france",
"tank_id": 20033,
"tier": 9,
"type": "mediumTank"
},
"20049": { "20049": {
"is_premium": false, "is_premium": false,
"name": "FV301", "name": "FV301",
@@ -3750,7 +3894,7 @@
}, },
"25361": { "25361": {
"is_premium": true, "is_premium": true,
"name": "Waffenträger Camelot", "name": "Waffenträger Ritter",
"nation": "germany", "nation": "germany",
"tank_id": 25361, "tank_id": 25361,
"tier": 9, "tier": 9,
@@ -3772,6 +3916,14 @@
"tier": 7, "tier": 7,
"type": "heavyTank" "type": "heavyTank"
}, },
"25617": {
"is_premium": true,
"name": "HWK 30",
"nation": "germany",
"tank_id": 25617,
"tier": 8,
"type": "lightTank"
},
"25633": { "25633": {
"is_premium": true, "is_premium": true,
"name": "M-IV-Y", "name": "M-IV-Y",
@@ -3780,6 +3932,14 @@
"tier": 8, "tier": 8,
"type": "heavyTank" "type": "heavyTank"
}, },
"25857": {
"is_premium": true,
"name": "Object 777 Version II",
"nation": "ussr",
"tank_id": 25857,
"tier": 10,
"type": "heavyTank"
},
"25889": { "25889": {
"is_premium": true, "is_premium": true,
"name": "Ranger", "name": "Ranger",
@@ -3788,6 +3948,22 @@
"tier": 6, "tier": 6,
"type": "lightTank" "type": "lightTank"
}, },
"26113": {
"is_premium": true,
"name": "Object 452K",
"nation": "ussr",
"tank_id": 26113,
"tier": 9,
"type": "heavyTank"
},
"26129": {
"is_premium": true,
"name": "Epsilon",
"nation": "germany",
"tank_id": 26129,
"tier": 6,
"type": "AT-SPG"
},
"26145": { "26145": {
"is_premium": true, "is_premium": true,
"name": "High Score", "name": "High Score",
@@ -3812,6 +3988,54 @@
"tier": 8, "tier": 8,
"type": "mediumTank" "type": "mediumTank"
}, },
"26913": {
"is_premium": true,
"name": "Frosty",
"nation": "usa",
"tank_id": 26913,
"tier": 6,
"type": "lightTank"
},
"27169": {
"is_premium": true,
"name": "Cyborg",
"nation": "usa",
"tank_id": 27169,
"tier": 6,
"type": "heavyTank"
},
"27425": {
"is_premium": false,
"name": "TL-7-120",
"nation": "usa",
"tank_id": 27425,
"tier": 9,
"type": "heavyTank"
},
"27681": {
"is_premium": true,
"name": "Pawlack Tank",
"nation": "usa",
"tank_id": 27681,
"tier": 6,
"type": "heavyTank"
},
"27937": {
"is_premium": true,
"name": "T49 Fearless",
"nation": "usa",
"tank_id": 27937,
"tier": 8,
"type": "lightTank"
},
"28449": {
"is_premium": true,
"name": "T42",
"nation": "usa",
"tank_id": 28449,
"tier": 7,
"type": "mediumTank"
},
"51201": { "51201": {
"is_premium": true, "is_premium": true,
"name": "KV-220 Beta-Test", "name": "KV-220 Beta-Test",
@@ -4520,6 +4744,7 @@
"userStr": { "userStr": {
"1_test": "#germany_vehicles:1_test", "1_test": "#germany_vehicles:1_test",
"1_test_short": "#germany_vehicles:1_test_short", "1_test_short": "#germany_vehicles:1_test_short",
"85mm_D5T-85BML": "85 mm D-5T-85BML",
"8V135-2": "8V135-2", "8V135-2": "8V135-2",
"A-20": "A-20", "A-20": "A-20",
"A-20_mod_1938": "A-20 mod. 1938", "A-20_mod_1938": "A-20 mod. 1938",
@@ -4532,6 +4757,8 @@
"A100_T49": "T49", "A100_T49": "T49",
"A100_T49_Custom": "T49 Hornet", "A100_T49_Custom": "T49 Hornet",
"A100_T49_Custom_short": "T49 Hornet", "A100_T49_Custom_short": "T49 Hornet",
"A100_T49_Custom_short_v2": "T49 Guanlong",
"A100_T49_Custom_v2": "T49 Guanlong",
"A100_T49_short": "T49", "A100_T49_short": "T49",
"A102_T28_concept": "T28 Concept", "A102_T28_concept": "T28 Concept",
"A102_T28_concept_Custom": "T28 HTC Independence", "A102_T28_concept_Custom": "T28 HTC Independence",
@@ -4582,13 +4809,25 @@
"A132_T77_Custom": "T77 Cannonball", "A132_T77_Custom": "T77 Cannonball",
"A132_T77_Custom_short": "T77 Cannonball", "A132_T77_Custom_short": "T77 Cannonball",
"A132_T77_short": "T77", "A132_T77_short": "T77",
"A136_T42": "T42",
"A136_T42_short": "T42",
"A140_ASTRON_REX_105": "ASTRON Rex 105 mm", "A140_ASTRON_REX_105": "ASTRON Rex 105 mm",
"A140_ASTRON_REX_105_Custom": "ASTRON Rex Ayame",
"A140_ASTRON_REX_105_Custom_short": "ASTRON Rex Ayame",
"A140_ASTRON_REX_105_short": "ASTRON Rex", "A140_ASTRON_REX_105_short": "ASTRON Rex",
"A141_M-4-Y": "M-IV-Y", "A141_M-4-Y": "M-IV-Y",
"A141_M-4-Y_Custom": "M-IV-Y Stalker", "A141_M-4-Y_Custom": "M-IV-Y Stalker",
"A141_M-4-Y_Custom_short": "M-IV-Y Stalker", "A141_M-4-Y_Custom_short": "M-IV-Y Stalker",
"A141_M-4-Y_short": "M-IV-Y", "A141_M-4-Y_short": "M-IV-Y",
"A142_Pawlack_Tank": "Pawlack Tank",
"A142_Pawlack_Tank_short": "Pawlack Tank",
"A146_TL_7_120": "TL-7-120",
"A146_TL_7_120_Custom": "TL-7-120 Arachnid",
"A146_TL_7_120_Custom_short": "TL-7-120 Arachnid",
"A146_TL_7_120_short": "TL-7-120",
"A68_T28D": "T28 Defender", "A68_T28D": "T28 Defender",
"A68_T28D_Custom": "Defender D-Gunner",
"A68_T28D_Custom_short": "Defender D-Gunner",
"A68_T28D_short": "T28 Defender", "A68_T28D_short": "T28 Defender",
"A77_T6M": "T6E1 Grizzly", "A77_T6M": "T6E1 Grizzly",
"A77_T6M_short": "T6E1 Grizzly", "A77_T6M_short": "T6E1 Grizzly",
@@ -4647,6 +4886,7 @@
"AMX_dracula_short": "Dracula", "AMX_dracula_short": "Dracula",
"AOI-1490": "Continental AOI-1490", "AOI-1490": "Continental AOI-1490",
"AOI-1490A": "Continental AOI-1490-A", "AOI-1490A": "Continental AOI-1490-A",
"AOI_695": "AOI-695",
"AOSI_1490-1": "Continental AOSI-1490-1", "AOSI_1490-1": "Continental AOSI-1490-1",
"AOS_895": "AOS-895", "AOS_895": "AOS-895",
"APCNR": "Pzgr. 41 HK", "APCNR": "Pzgr. 41 HK",
@@ -4718,6 +4958,8 @@
"Bat_Chatillon25t": "Bat.-Châtillon 25 t", "Bat_Chatillon25t": "Bat.-Châtillon 25 t",
"Bat_Chatillon25t_Custom": "Bat.-Châtillon 25 t Musketeer", "Bat_Chatillon25t_Custom": "Bat.-Châtillon 25 t Musketeer",
"Bat_Chatillon25t_Custom_short": "B-C 25 t Musketeer", "Bat_Chatillon25t_Custom_short": "B-C 25 t Musketeer",
"Bat_Chatillon25t_Custom_short_v2": "B-C 25 t Runner",
"Bat_Chatillon25t_Custom_v2": "B-C 25 t Runner",
"Bat_Chatillon25t_short": "B-C 25 t", "Bat_Chatillon25t_short": "B-C 25 t",
"Bedford_Twin-Six": "Bedford Twin-Six", "Bedford_Twin-Six": "Bedford Twin-Six",
"Berliet_MDP": "Berliet MDP", "Berliet_MDP": "Berliet MDP",
@@ -4731,6 +4973,7 @@
"CH38_WZ113G_FT_Custom": "WZ-113G FT Lucky", "CH38_WZ113G_FT_Custom": "WZ-113G FT Lucky",
"CH38_WZ113G_FT_Custom_short": "WZ-113G FT Lucky", "CH38_WZ113G_FT_Custom_short": "WZ-113G FT Lucky",
"CKD_AXK": "ČKD AXK", "CKD_AXK": "ČKD AXK",
"CKD_AXL": "ČKD AXL",
"Cadillac_V8": "3xCadillacV8", "Cadillac_V8": "3xCadillacV8",
"Cannone_da_105_51_M68": "Cannone da 105/51 M68", "Cannone_da_105_51_M68": "Cannone da 105/51 M68",
"Cannone_da_105_51_M68_short": "Cannone da 105/51 M68", "Cannone_da_105_51_M68_short": "Cannone da 105/51 M68",
@@ -4772,6 +5015,12 @@
"Ch12_111_1_2_3_short": "WZ-111 1-4", "Ch12_111_1_2_3_short": "WZ-111 1-4",
"Ch14_T34_3": "T-34-3", "Ch14_T34_3": "T-34-3",
"Ch14_T34_3_short": "T-34-3", "Ch14_T34_3_short": "T-34-3",
"Ch15_59_16": "59-16",
"Ch15_59_16_BP": "Wind",
"Ch15_59_16_BP_Custom": "Free Wind",
"Ch15_59_16_BP_Custom_short": "Free Wind",
"Ch15_59_16_BP_short": "Wind",
"Ch16_WZ_131": "WZ-131",
"Ch17_WZ131_1_WZ132": "WZ-132", "Ch17_WZ131_1_WZ132": "WZ-132",
"Ch18_WZ-120": "WZ-120", "Ch18_WZ-120": "WZ-120",
"Ch19_121": "WZ-121", "Ch19_121": "WZ-121",
@@ -4782,6 +5031,8 @@
"Ch22_113": "WZ-113", "Ch22_113": "WZ-113",
"Ch22_113_Custom": "WZ-113 Bronze Warrior", "Ch22_113_Custom": "WZ-113 Bronze Warrior",
"Ch22_113_Custom_short": "WZ-113 Bronze Warrior", "Ch22_113_Custom_short": "WZ-113 Bronze Warrior",
"Ch22_113_Custom_short_v2": "WZ-113 Nine-Colors",
"Ch22_113_Custom_v2": "WZ-113 Nine-Colors",
"Ch23_112": "112 Glacial", "Ch23_112": "112 Glacial",
"Ch24_Type64": "Type 64", "Ch24_Type64": "Type 64",
"Ch24_Type64_short": "Type 64", "Ch24_Type64_short": "Type 64",
@@ -4791,7 +5042,17 @@
"Ch26_59_Patton_Custom": "59-Patton Aratinga", "Ch26_59_Patton_Custom": "59-Patton Aratinga",
"Ch26_59_Patton_Custom_short": "59-Patton Aratinga", "Ch26_59_Patton_Custom_short": "59-Patton Aratinga",
"Ch26_59_Patton_short": "59-Patton", "Ch26_59_Patton_short": "59-Patton",
"Ch28_WZ_132A": "WZ-132A",
"Ch28_WZ_132A_short": "WZ-132A",
"Ch29_Type_62C_prot": "WZ-132-1",
"Ch29_Type_62C_prot_Custom": "WZ-132-1 Futuro",
"Ch29_Type_62C_prot_Custom_short": "WZ-132-1 Futuro",
"Ch29_Type_62C_prot_short": "WZ-132-1",
"Ch34_WZ-131G_FT": "WZ-131G FT", "Ch34_WZ-131G_FT": "WZ-131G FT",
"Ch34_WZ-131G_FT_BP": "Panlong",
"Ch34_WZ-131G_FT_BP_Custom": "Panlong Immortal",
"Ch34_WZ-131G_FT_BP_Custom_short": "Panlong Immortal",
"Ch34_WZ-131G_FT_BP_short": "Panlong",
"Ch34_WZ-131G_FT_short": "WZ-131G FT", "Ch34_WZ-131G_FT_short": "WZ-131G FT",
"Ch35_T-34-2G_FT": "T-34-2G FT", "Ch35_T-34-2G_FT": "T-34-2G FT",
"Ch35_T-34-2G_FT_short": "T-34-2G FT", "Ch35_T-34-2G_FT_short": "T-34-2G FT",
@@ -4815,6 +5076,10 @@
"Ch42_M41D_short": "M41D", "Ch42_M41D_short": "M41D",
"Ch43_WZ-122_2": "WZ-122 TM", "Ch43_WZ-122_2": "WZ-122 TM",
"Ch43_WZ-122_2_short": "WZ-122 TM", "Ch43_WZ-122_2_short": "WZ-122 TM",
"Ch45_WZ_114": "WZ-114",
"Ch45_WZ_114_Custom": "WZ-114 Zhulong",
"Ch45_WZ_114_Custom_short": "WZ-114 Zhulong",
"Ch45_WZ_114_short": "WZ-114",
"Ch_LT_vz38": "LT vz. 38", "Ch_LT_vz38": "LT vz. 38",
"Ch_LT_vz38_short": "LT vz. 38", "Ch_LT_vz38_short": "LT vz. 38",
"Ch_WZ-112v2": "WZ-112-2", "Ch_WZ-112v2": "WZ-112-2",
@@ -4823,18 +5088,64 @@
"Ch_WZ_112v2_Custom_short": "WZ-112-2 Celestial", "Ch_WZ_112v2_Custom_short": "WZ-112-2 Celestial",
"Chassis1_A140_ASTRON_REX_105": "Rex", "Chassis1_A140_ASTRON_REX_105": "Rex",
"Chassis1_A140_ASTRON_REX_105_short": "Rex", "Chassis1_A140_ASTRON_REX_105_short": "Rex",
"Chassis1_Ch16_WZ_131": "WZ-131",
"Chassis1_Ch16_WZ_131_short": "WZ-131",
"Chassis1_Ch17_WZ131_1_WZ132": "WZ-131-1",
"Chassis1_Ch17_WZ131_1_WZ132_short": "WZ-131-1",
"Chassis1_Ch28_WZ_132A": "WZ-132A",
"Chassis1_Ch28_WZ_132A_short": "WZ-132A",
"Chassis1_Ch29_Type_62C_prot": "WZ-132-1",
"Chassis1_Ch29_Type_62C_prot_short": "WZ-132-1",
"Chassis1_Ch45_WZ_114": "WZ-114",
"Chassis1_Ch45_WZ_114_short": "WZ-114",
"Chassis1_F117_Alt_Proto_AMX_30": "AltProto AMX 30", "Chassis1_F117_Alt_Proto_AMX_30": "AltProto AMX 30",
"Chassis1_F117_Alt_Proto_AMX_30_short": "A.P. AMX 30", "Chassis1_F117_Alt_Proto_AMX_30_short": "A.P. AMX 30",
"Chassis1_GB09_Churchill_VII_BP": "Churchill W", "Chassis1_GB09_Churchill_VII_BP": "Churchill W",
"Chassis1_GB09_Churchill_VII_BP_short": "Churchill W", "Chassis1_GB09_Churchill_VII_BP_short": "Churchill W",
"Chassis1_It10_P43_bis_BP": "Shadowhunter", "Chassis1_It10_P43_bis_BP": "Shadowhunter",
"Chassis1_It10_P43_bis_BP_short": "Shadowhunter", "Chassis1_It10_P43_bis_BP_short": "Shadowhunter",
"Chassis1_It15_Semovente_M41": "Semovente M41",
"Chassis1_It15_Semovente_M41_short": "Semovente M41",
"Chassis1_It16_Bassoto": "Semovente M43",
"Chassis1_It16_Bassoto_short": "Semovente M43",
"Chassis1_It17_Semovente_mod_56": "CC mod. 56",
"Chassis1_It17_Semovente_mod_56_short": "CC mod. 56",
"Chassis1_It18_Semovente_mod_64": "CC-64 tipo A",
"Chassis1_It18_Semovente_mod_64_short": "CC-64 tipo A",
"Chassis1_It19_Controcarro_1MK2_Ciclope": "Controcarro 1 Mk. 2 tipo A",
"Chassis1_It19_Controcarro_1MK2_Ciclope_short": "CC 1 Mk. 2 tipo A",
"Chassis1_It22_Progetto_C45_mod_71": "Bisonte C45",
"Chassis1_It22_Progetto_C45_mod_71_short": "Bisonte C45",
"Chassis1_M24_Chaffee_BP": "Frosty",
"Chassis1_M24_Chaffee_BP_short": "Frosty",
"Chassis1_M6E2V2_BP": "Cyborg",
"Chassis1_M6E2V2_BP_short": "Cyborg",
"Chassis1_Oth34_PanzerMummy": "Royal Tread", "Chassis1_Oth34_PanzerMummy": "Royal Tread",
"Chassis1_Oth34_PanzerMummy_short": "Royal Tread", "Chassis1_Oth34_PanzerMummy_short": "Royal Tread",
"Chassis1_Oth35_PanzerMejai": "Desert Wanderer", "Chassis1_Oth35_PanzerMejai": "Desert Wanderer",
"Chassis1_Oth35_PanzerMejai_short": "Desert Wanderer", "Chassis1_Oth35_PanzerMejai_short": "Desert Wanderer",
"Chassis1_R119_Object_777": "Object 777 Version II",
"Chassis1_R119_Object_777_short": "Obj. 777 II",
"Chassis1_S31_Strv_K": "Stridsvagn KRV", "Chassis1_S31_Strv_K": "Stridsvagn KRV",
"Chassis1_S31_Strv_K_short": "Stridsvagn KRV", "Chassis1_S31_Strv_K_short": "Stridsvagn KRV",
"Chassis1_S32_Bofors_Tornvagn": "Bofors Tornvagn",
"Chassis1_S32_Bofors_Tornvagn_short": "Tornvagn",
"Chassis2_Ch16_WZ_131": "WZ-131 model 2",
"Chassis2_Ch16_WZ_131_short": "WZ-131 model 2",
"Chassis2_Ch17_WZ131_1_WZ132": "WZ-132",
"Chassis2_Ch17_WZ131_1_WZ132_short": "WZ-132",
"Chassis2_Ch28_WZ_132A": "WZ-132B",
"Chassis2_Ch28_WZ_132A_short": "WZ-132B",
"Chassis2_It15_Semovente_M41": "Semovente M42",
"Chassis2_It15_Semovente_M41_short": "Semovente M42",
"Chassis2_It16_Bassoto": "Semovente M44",
"Chassis2_It16_Bassoto_short": "Semovente M44",
"Chassis2_It17_Semovente_mod_56": "CC mod. 57",
"Chassis2_It17_Semovente_mod_56_short": "CC mod. 57",
"Chassis2_It18_Semovente_mod_64": "CC-64 tipo B",
"Chassis2_It18_Semovente_mod_64_short": "CC-64 tipo B",
"Chassis2_It19_Controcarro_1MK2_Ciclope": "Controcarro 1 Mk. 2 tipo B",
"Chassis2_It19_Controcarro_1MK2_Ciclope_short": "CC 1 Mk. 2 tipo B",
"Chi_Ha": "Type 97 Chi-Ha", "Chi_Ha": "Type 97 Chi-Ha",
"Chi_Ha_short": "Chi-Ha", "Chi_Ha_short": "Chi-Ha",
"Chi_He": "Type 1 Chi-He", "Chi_He": "Type 1 Chi-He",
@@ -4866,16 +5177,20 @@
"Continental_AOI-628-2": "Continental AOI-628-2", "Continental_AOI-628-2": "Continental AOI-628-2",
"Continental_AOI-628-2A": "Continental AOI-628-2A", "Continental_AOI-628-2A": "Continental AOI-628-2A",
"Continental_AOI-628-2_short": "Continental\\nAOI-628-2", "Continental_AOI-628-2_short": "Continental\\nAOI-628-2",
"Continental_AOI_1490": "Continental AOI-1490",
"Continental_AOI_1490M": "Continental AOI-1490-M",
"Continental_AOI_1490_1": "Continental AOI-1490-1", "Continental_AOI_1490_1": "Continental AOI-1490-1",
"Continental_AOS-895-1": "Continental AOS-895-1", "Continental_AOS-895-1": "Continental AOS-895-1",
"Continental_AOS-895-1_short": "Continental\\nAOS-895-1", "Continental_AOS-895-1_short": "Continental\\nAOS-895-1",
"Continental_AOS-895-3": "AN/GRC-8", "Continental_AOS-895-3": "AN/GRC-8",
"Continental_AOS-895-3M": "Continental AOS-895-3M",
"Continental_AOS-895-3V2": "Continental AOS-895-3-2", "Continental_AOS-895-3V2": "Continental AOS-895-3-2",
"Continental_AOS-895-3_short": "AN/GRC-8", "Continental_AOS-895-3_short": "AN/GRC-8",
"Continental_AOSI-1195-1": "Continental AOSI 1195-1", "Continental_AOSI-1195-1": "Continental AOSI 1195-1",
"Continental_AOSI-1195-1A": "Continental AOSI-1195-1A", "Continental_AOSI-1195-1A": "Continental AOSI-1195-1A",
"Continental_AOSI-1195-5": "Continental AOSI-1195-5", "Continental_AOSI-1195-5": "Continental AOSI-1195-5",
"Continental_AOSI-895-5": "Continental AOSI-895-5", "Continental_AOSI-895-5": "Continental AOSI-895-5",
"Continental_AOSI-895-5FR": "Continental AOSI-895-5",
"Continental_AOSI-895-5_short": "Continental\\nAOSI-895-5", "Continental_AOSI-895-5_short": "Continental\\nAOSI-895-5",
"Continental_AOSI_1195_5A": "Continental AOSI-1195-5 Y IV", "Continental_AOSI_1195_5A": "Continental AOSI-1195-5 Y IV",
"Continental_AOSI_895_5_Prem": "Continental AOSI-895-5", "Continental_AOSI_895_5_Prem": "Continental AOSI-895-5",
@@ -4956,6 +5271,8 @@
"Cz15_Skoda_T-45_Custom": "Škoda T 45 Skvělý", "Cz15_Skoda_T-45_Custom": "Škoda T 45 Skvělý",
"Cz15_Skoda_T-45_Custom_short": "Škoda T 45 Skvělý", "Cz15_Skoda_T-45_Custom_short": "Škoda T 45 Skvělý",
"Cz15_Skoda_T-45_short": "Škoda T 45", "Cz15_Skoda_T-45_short": "Škoda T 45",
"Cz17_Vz_55": "Vz. 55",
"Cz17_Vz_55_short": "Vz. 55",
"D-744": "D-744", "D-744": "D-744",
"D1": "D1", "D1": "D1",
"D1_short": "D1", "D1_short": "D1",
@@ -5006,21 +5323,28 @@
"E50_Ausf_M_Custom": "E 50 M Roaring", "E50_Ausf_M_Custom": "E 50 M Roaring",
"E50_Ausf_M_Custom_short": "E 50 M Roaring", "E50_Ausf_M_Custom_short": "E 50 M Roaring",
"E50_Ausf_M_Custom_short_v2": "E 50 M Monster", "E50_Ausf_M_Custom_short_v2": "E 50 M Monster",
"E50_Ausf_M_Custom_short_v3": "E 50 M Monster Evo.",
"E50_Ausf_M_Custom_v2": "E 50 Ausf. M Monster", "E50_Ausf_M_Custom_v2": "E 50 Ausf. M Monster",
"E50_Ausf_M_Custom_v3": "E 50 M Monster Evo.",
"E50_Ausf_M_short": "E 50 M", "E50_Ausf_M_short": "E 50 M",
"ELC_AMX": "AMX ELC bis", "ELC_AMX": "AMX ELC bis",
"ELC_AMX_short": "AMX ELC bis", "ELC_AMX_short": "AMX ELC bis",
"E_9": "E-9",
"Engine_1_test": "#germany_vehicles:Engine_1_test", "Engine_1_test": "#germany_vehicles:Engine_1_test",
"Engine_1_test_short": "#germany_vehicles:Engine_1_test_short", "Engine_1_test_short": "#germany_vehicles:Engine_1_test_short",
"F01_Vickers_MkF": "Vickers Mk. F", "F01_Vickers_MkF": "Vickers Mk. F",
"F01_Vickers_MkF_short": "Vickers Mk. F", "F01_Vickers_MkF_short": "Vickers Mk. F",
"F113_PzV_FR": "Bretagne Panther", "F113_PzV_FR": "Bretagne Panther",
"F113_PzV_FR_short": "Bretagne Panther", "F113_PzV_FR_short": "Bretagne Panther",
"F114_Projet_4_1": "Char Futur 4",
"F114_Projet_4_1_short": "Char Futur 4",
"F116_Bat_Chat_Bourrasque": "Bat.-Châtillon Bourrasque", "F116_Bat_Chat_Bourrasque": "Bat.-Châtillon Bourrasque",
"F116_Bat_Chat_Bourrasque_Custom": "Bourrasque Noir", "F116_Bat_Chat_Bourrasque_Custom": "Bourrasque Noir",
"F116_Bat_Chat_Bourrasque_Custom_short": "Bourrasque Noir", "F116_Bat_Chat_Bourrasque_Custom_short": "Bourrasque Noir",
"F116_Bat_Chat_Bourrasque_short": "B-C Bourrasque", "F116_Bat_Chat_Bourrasque_short": "B-C Bourrasque",
"F117_Alt_Proto_AMX_30": "AltProto AMX 30", "F117_Alt_Proto_AMX_30": "AltProto AMX 30",
"F117_Alt_Proto_AMX_30_Custom": "A.P. AMX 30 Disruptor",
"F117_Alt_Proto_AMX_30_Custom_short": "A.P. AMX 30 Disruptor",
"F117_Alt_Proto_AMX_30_short": "A.P. AMX 30", "F117_Alt_Proto_AMX_30_short": "A.P. AMX 30",
"F19_Lorraine40t": "Lorraine 40 t", "F19_Lorraine40t": "Lorraine 40 t",
"F19_Lorraine40t_Com": "Lorraine 40 t Fearless", "F19_Lorraine40t_Com": "Lorraine 40 t Fearless",
@@ -5078,6 +5402,7 @@
"Ferdinandstandardketten": "Ferdinand", "Ferdinandstandardketten": "Ferdinand",
"Ferdinandturm": "Ferdinand", "Ferdinandturm": "Ferdinand",
"Ferdinandvetstaerktketten": "Elefant", "Ferdinandvetstaerktketten": "Elefant",
"Fiat_V10": "Fiat V-10",
"Firefly_GuP": "Firefly Saunders SP", "Firefly_GuP": "Firefly Saunders SP",
"Firefly_GuP_short": "Firefly Saunders SP", "Firefly_GuP_short": "Firefly Saunders SP",
"First_april": "#other_vehicles:First_april", "First_april": "#other_vehicles:First_april",
@@ -5107,6 +5432,8 @@
"G100_Gtraktor_Krupp": "Großtraktor - Krupp", "G100_Gtraktor_Krupp": "Großtraktor - Krupp",
"G100_Gtraktor_Krupp_short": "Großtraktor", "G100_Gtraktor_Krupp_short": "Großtraktor",
"G103_RU_251": "Spähpanzer Ru 251", "G103_RU_251": "Spähpanzer Ru 251",
"G103_RU_251_Custom": "Ru 251 Breakneck",
"G103_RU_251_Custom_short": "Ru 251 Breakneck",
"G103_RU_251_short": "Ru 251", "G103_RU_251_short": "Ru 251",
"G106_PzKpfwPanther_AusfF": "Panther mit 8,8 cm L/71", "G106_PzKpfwPanther_AusfF": "Panther mit 8,8 cm L/71",
"G106_PzKpfwPanther_AusfF_short": "Panther 8,8", "G106_PzKpfwPanther_AusfF_short": "Panther 8,8",
@@ -5138,11 +5465,15 @@
"G121_Grille_15_L63": "Grille 15", "G121_Grille_15_L63": "Grille 15",
"G121_Grille_15_L63_Custom": "Grille 15 RA1DER", "G121_Grille_15_L63_Custom": "Grille 15 RA1DER",
"G121_Grille_15_L63_Custom_short": "Grille 15 RA1DER", "G121_Grille_15_L63_Custom_short": "Grille 15 RA1DER",
"G121_Grille_15_L63_Custom_short_v3": "Grille 15 Förster",
"G121_Grille_15_L63_Custom_v3": "Grille 15 Förster",
"G121_Grille_15_L63_short": "Grille 15", "G121_Grille_15_L63_short": "Grille 15",
"G136_Tiger_131": "Tiger 131", "G136_Tiger_131": "Tiger 131",
"G136_Tiger_131_short": "Tiger 131", "G136_Tiger_131_short": "Tiger 131",
"G138_VK16801P": "VK 168.01 (P)", "G138_VK16801P": "VK 168.01 (P)",
"G138_VK16801P_short": "VK 168.01 (P)", "G138_VK16801P_short": "VK 168.01 (P)",
"G140_HWK_30": "HWK 30",
"G140_HWK_30_short": "HWK 30",
"G142_M48RPz": "M48A2 Räumpanzer", "G142_M48RPz": "M48A2 Räumpanzer",
"G142_M48RPz_Custom": "Räumpanzer Rhino", "G142_M48RPz_Custom": "Räumpanzer Rhino",
"G142_M48RPz_Custom_short": "Räumpanzer Rhino", "G142_M48RPz_Custom_short": "Räumpanzer Rhino",
@@ -5150,6 +5481,8 @@
"G144_Kpz_50t": "Kampfpanzer 50 t", "G144_Kpz_50t": "Kampfpanzer 50 t",
"G144_Kpz_50t_Custom": "Kpz 50 t König", "G144_Kpz_50t_Custom": "Kpz 50 t König",
"G144_Kpz_50t_Custom_short": "Kpz 50 t König", "G144_Kpz_50t_Custom_short": "Kpz 50 t König",
"G144_Kpz_50t_Custom_short_v2": "Kpz 50 t Jäger",
"G144_Kpz_50t_Custom_v2": "Kpz 50 t Jäger",
"G144_Kpz_50t_short": "Kpz 50 t", "G144_Kpz_50t_short": "Kpz 50 t",
"G147_mPanzer_64": "Kunze Panzer", "G147_mPanzer_64": "Kunze Panzer",
"G147_mPanzer_64_Custom": "Kunze Pz. Daredevil", "G147_mPanzer_64_Custom": "Kunze Pz. Daredevil",
@@ -5347,12 +5680,15 @@
"GMC_Allison_V1710-E32_short": "GMC Allison\\nV1710-E32", "GMC_Allison_V1710-E32_short": "GMC Allison\\nV1710-E32",
"GMS-6046B": "GMS 6046B", "GMS-6046B": "GMS 6046B",
"GM_6-71_M": "GM 6062 6-71M", "GM_6-71_M": "GM 6062 6-71M",
"GM_DIESEL_P_V2": "GM Diesel P V2",
"GM_X-11": "GM X-11", "GM_X-11": "GM X-11",
"GM_X-12": "GM X-12", "GM_X-12": "GM X-12",
"GTD": "GTD", "GTD": "GTD",
"GT_101": "GT 101", "GT_101": "GT 101",
"G_E": "G.W. E 100", "G_E": "G.W. E 100",
"G_E-75RT": "Keiler", "G_E-75RT": "Keiler",
"G_E-75RT_Custom": "Keiler Exterminator",
"G_E-75RT_Custom_short": "Keiler Exterminator",
"G_E-75RT_short": "Keiler", "G_E-75RT_short": "Keiler",
"G_E-75TS": "E 75 TS", "G_E-75TS": "E 75 TS",
"G_E-75TS_short": "E 75 TS", "G_E-75TS_short": "E 75 TS",
@@ -5459,6 +5795,8 @@
"IS_4_Custom_short": "IS-4 Skilled", "IS_4_Custom_short": "IS-4 Skilled",
"IS_6_Custom": "IS-6 Legend", "IS_6_Custom": "IS-6 Legend",
"IS_6_Custom_short": "IS-6 Legend", "IS_6_Custom_short": "IS-6 Legend",
"IS_Custom": "IS Steppe",
"IS_Custom_short": "IS Steppe",
"Indien_Panzer": "Indien-Panzer", "Indien_Panzer": "Indien-Panzer",
"Indien_Panzer_short": "Indien-Pz.", "Indien_Panzer_short": "Indien-Pz.",
"It08_Progetto_M40_mod65": "Progetto M40 mod. 65", "It08_Progetto_M40_mod65": "Progetto M40 mod. 65",
@@ -5485,12 +5823,28 @@
"It14_P44_Pantera_Custom": "P.44 Pantera Furious", "It14_P44_Pantera_Custom": "P.44 Pantera Furious",
"It14_P44_Pantera_Custom_short": "P.44 Pantera Furious", "It14_P44_Pantera_Custom_short": "P.44 Pantera Furious",
"It14_P44_Pantera_short": "P.44 Pantera", "It14_P44_Pantera_short": "P.44 Pantera",
"It15_Semovente_M41": "Semovente M41",
"It15_Semovente_M41_short": "Semovente M41",
"It16_Bassoto": "Semovente M43 Bassotto",
"It16_Bassoto_short": "Bassotto",
"It17_Semovente_mod_56": "Semovente Controcarro mod. 1956",
"It17_Semovente_mod_56_short": "SMV CC-56",
"It18_Semovente_mod_64": "Semovente Controcarro mod. 1964",
"It18_Semovente_mod_64_short": "SMV CC-64",
"It19_Controcarro_1MK2_Ciclope": "Controcarro 1 Mk. 2",
"It19_Controcarro_1MK2_Ciclope_short": "CC 1 Mk. 2",
"It20_Car_Comb_45t": "Carro da Combattimento 45t", "It20_Car_Comb_45t": "Carro da Combattimento 45t",
"It20_Car_Comb_45t_Custom": "Carro 45t Glitch", "It20_Car_Comb_45t_Custom": "Carro 45t Glitch",
"It20_Car_Comb_45t_Custom_short": "Carro 45t Glitch", "It20_Car_Comb_45t_Custom_short": "Carro 45t Glitch",
"It20_Car_Comb_45t_short": "Carro 45t", "It20_Car_Comb_45t_short": "Carro 45t",
"It21_Controcarro3_Minotauro": "Controcarro 3 Minotauro", "It21_Controcarro3_Minotauro": "Controcarro 3 Minotauro",
"It21_Controcarro3_Minotauro_Custom": "Minotauro Moderno",
"It21_Controcarro3_Minotauro_Custom_short": "Minotauro Moderno",
"It21_Controcarro3_Minotauro_short": "Minotauro", "It21_Controcarro3_Minotauro_short": "Minotauro",
"It22_Progetto_C45_mod_71": "Bisonte C45",
"It22_Progetto_C45_mod_71_Custom": "Bisonte Unbreakable",
"It22_Progetto_C45_mod_71_Custom_short": "Bisonte Unbreakable",
"It22_Progetto_C45_mod_71_short": "Bisonte C45",
"J12_Shinobi": "Shinobi", "J12_Shinobi": "Shinobi",
"J12_Shinobi_short": "Shinobi", "J12_Shinobi_short": "Shinobi",
"J20_Ho_Ri_type3": "Ho-Ri Type III", "J20_Ho_Ri_type3": "Ho-Ri Type III",
@@ -5572,6 +5926,8 @@
"KV1": "KV-1", "KV1": "KV-1",
"KV1S_mod_42": "KV-1S mod. 1942", "KV1S_mod_42": "KV-1S mod. 1942",
"KV1S_mod_43": "KV-85", "KV1S_mod_43": "KV-85",
"KV1_Custom": "KV-1 Ghost",
"KV1_Custom_short": "KV-1 Ghost",
"KV1s_bear": "KV-1S Football", "KV1s_bear": "KV-1S Football",
"KV1s_bear_short": "KV-1S Football", "KV1s_bear_short": "KV-1S Football",
"KV2": "KV-2", "KV2": "KV-2",
@@ -5607,6 +5963,8 @@
"Leopard1": "Leopard 1", "Leopard1": "Leopard 1",
"Leopard1_Custom": "Leopard 1 Wild", "Leopard1_Custom": "Leopard 1 Wild",
"Leopard1_Custom_short": "Leopard 1 Wild", "Leopard1_Custom_short": "Leopard 1 Wild",
"Leopard1_Custom_short_v2": "Leopard 1 Irbis",
"Leopard1_Custom_v2": "Leopard 1 Irbis",
"Leopard1_short": "Leopard 1", "Leopard1_short": "Leopard 1",
"Leopardketten_Ausf_A": "VK 16.02 Leopard", "Leopardketten_Ausf_A": "VK 16.02 Leopard",
"Leopardketten_Ausf_B": "VK 16.02 Leopard verstärkteketten", "Leopardketten_Ausf_B": "VK 16.02 Leopard verstärkteketten",
@@ -5618,7 +5976,9 @@
"Licoming_0-435t": "Lycoming O-435T", "Licoming_0-435t": "Lycoming O-435T",
"Lowe": "Löwe", "Lowe": "Löwe",
"Lowe_Custom": "Löwe Reliant", "Lowe_Custom": "Löwe Reliant",
"Lowe_Custom_2": "Löwe Colossus",
"Lowe_Custom_short": "Löwe Reliant", "Lowe_Custom_short": "Löwe Reliant",
"Lowe_Custom_short_2": "Löwe Colossus",
"Ltraktor": "Leichttraktor", "Ltraktor": "Leichttraktor",
"Ltraktor_short": "L.Tr.", "Ltraktor_short": "L.Tr.",
"Luchsketten_Ausf_A": "Luchs", "Luchsketten_Ausf_A": "Luchs",
@@ -5644,9 +6004,13 @@
"M-5-Y_short": "M-V-Yoh", "M-5-Y_short": "M-V-Yoh",
"M-50T": "M-50T", "M-50T": "M-50T",
"M-50TD": "M-50TD", "M-50TD": "M-50TD",
"M-50TV2": "M-50TV2",
"M-50TV2K": "M-50TV2K",
"M-6-Y": "M-VI-Yoh", "M-6-Y": "M-VI-Yoh",
"M-6-Y_Custom": "M-VI-Yoh Pride", "M-6-Y_Custom": "M-VI-Yoh Pride",
"M-6-Y_Custom_short": "M-VI-Yoh Pride", "M-6-Y_Custom_short": "M-VI-Yoh Pride",
"M-6-Y_Custom_short_v2": "M-VI-Yoh Ankylosaurus",
"M-6-Y_Custom_v2": "M-VI-Yoh Ankylosaurus",
"M-6-Y_short": "M-VI-Yoh", "M-6-Y_short": "M-VI-Yoh",
"M-7-Y": "M-VII-Yoh", "M-7-Y": "M-VII-Yoh",
"M-7-Y_short": "M-VII-Yoh", "M-7-Y_short": "M-VII-Yoh",
@@ -5665,6 +6029,10 @@
"M22_Locust_short": "Locust", "M22_Locust_short": "Locust",
"M24A1": "M24A1", "M24A1": "M24A1",
"M24_Chaffee": "M24 Chaffee", "M24_Chaffee": "M24 Chaffee",
"M24_Chaffee_BP": "Frosty",
"M24_Chaffee_BP_Custom": "Frosty Jinglebell",
"M24_Chaffee_BP_Custom_short": "Frosty Jinglebell",
"M24_Chaffee_BP_short": "Frosty",
"M24_Chaffee_short": "Chaffee", "M24_Chaffee_short": "Chaffee",
"M2D27810": "M2D27810", "M2D27810": "M2D27810",
"M2D27812": "M2D27812", "M2D27812": "M2D27812",
@@ -5727,6 +6095,10 @@
"M6D2": "M6D2", "M6D2": "M6D2",
"M6E2BP": "Magnus", "M6E2BP": "Magnus",
"M6E2BP_short": "Magnus", "M6E2BP_short": "Magnus",
"M6E2V2_BP": "Cyborg",
"M6E2V2_BP_Custom": "Cyborg T-800",
"M6E2V2_BP_Custom_short": "Cyborg T-800",
"M6E2V2_BP_short": "Cyborg",
"M7": "M5A1", "M7": "M5A1",
"M7D1": "M7D1", "M7D1": "M7D1",
"M7_Priest": "M7 Priest", "M7_Priest": "M7 Priest",
@@ -5743,6 +6115,7 @@
"MB-837-Ea-600": "MB 837 Ea 600", "MB-837-Ea-600": "MB 837 Ea 600",
"MB-837A": "MTU MB 837 A", "MB-837A": "MTU MB 837 A",
"MB-837A_short": "MB 837 A", "MB-837A_short": "MB 837 A",
"MB-837M": "MB 837 M",
"MB-838_CAM-500": "MTU MB 838 CaM 500", "MB-838_CAM-500": "MTU MB 838 CaM 500",
"MB-838_CAM-500_1": "MTU MB 838 CaM 500A", "MB-838_CAM-500_1": "MTU MB 838 CaM 500A",
"MB-838_CAM-500_1_short": "MB 838 CaM 500A", "MB-838_CAM-500_1_short": "MB 838 CaM 500A",
@@ -5765,6 +6138,10 @@
"MTLS-1G14": "MTLS-1G14", "MTLS-1G14": "MTLS-1G14",
"MTLS_D1": "MTLS D1", "MTLS_D1": "MTLS D1",
"MTLS_T1": "MTLS T1", "MTLS_T1": "MTLS T1",
"MTU_870": "MTU 870",
"MTU_870M": "MTU 870M",
"MTU_970": "MTU 970",
"MTU_970M": "MTU 970M",
"MTU_MB-838_Ca_M-500": "MTU MB 838 CaM 500B", "MTU_MB-838_Ca_M-500": "MTU MB 838 CaM 500B",
"MT_50_T": "MT-50T", "MT_50_T": "MT-50T",
"M_40": "M-40", "M_40": "M-40",
@@ -5806,6 +6183,7 @@
"Maybach_HL_120_TRM_BT": "Maybach HL 120 TRM", "Maybach_HL_120_TRM_BT": "Maybach HL 120 TRM",
"Maybach_HL_120_TRM_C": "Maybach HL 120 TRM V12", "Maybach_HL_120_TRM_C": "Maybach HL 120 TRM V12",
"Maybach_HL_120_TRM_R": "Maybach HL 120 TRM R", "Maybach_HL_120_TRM_R": "Maybach HL 120 TRM R",
"Maybach_HL_120_TRM_RBP": "Maybach HL 120 TRM R",
"Maybach_HL_120_TR_A": "Maybach HL 120 TRM 112", "Maybach_HL_120_TR_A": "Maybach HL 120 TRM 112",
"Maybach_HL_120_TR_R": "Maybach HL 120 TR R", "Maybach_HL_120_TR_R": "Maybach HL 120 TR R",
"Maybach_HL_120_WH": "Dark Soul", "Maybach_HL_120_WH": "Dark Soul",
@@ -5827,6 +6205,7 @@
"Maybach_HL_210_TRM_P30": "Maybach HL 210 TRM P30", "Maybach_HL_210_TRM_P30": "Maybach HL 210 TRM P30",
"Maybach_HL_210_TRM_P30_A": "Maybach HL 210 TRM P30", "Maybach_HL_210_TRM_P30_A": "Maybach HL 210 TRM P30",
"Maybach_HL_210_TRM_P30_short": "Maybach\\nHL 210 TRM P30", "Maybach_HL_210_TRM_P30_short": "Maybach\\nHL 210 TRM P30",
"Maybach_HL_210_TRM_P30v2": "Maybach HL 210 TRM P30 V2",
"Maybach_HL_210_TRM_P45": "Maybach HL 210 TRM P45", "Maybach_HL_210_TRM_P45": "Maybach HL 210 TRM P45",
"Maybach_HL_210_TRM_P45TU": "Maybach HL 210 TRM P45", "Maybach_HL_210_TRM_P45TU": "Maybach HL 210 TRM P45",
"Maybach_HL_210_TRM_P45_BP": "Maybach HL 210 TRM P45 B", "Maybach_HL_210_TRM_P45_BP": "Maybach HL 210 TRM P45 B",
@@ -5841,8 +6220,10 @@
"Maybach_HL_230P45_short": "Maybach HL 230\\nP45F", "Maybach_HL_230P45_short": "Maybach HL 230\\nP45F",
"Maybach_HL_230_F": "Maybach HL 230 F", "Maybach_HL_230_F": "Maybach HL 230 F",
"Maybach_HL_230_TRM_P30": "Maybach HL 230 TRM P30 pl.", "Maybach_HL_230_TRM_P30": "Maybach HL 230 TRM P30 pl.",
"Maybach_HL_230_TRM_P30V2": "Maybach HL 230 TRM P30 V2",
"Maybach_HL_230_TRM_P30_FR": "Maybach HL 230 TRM P30 fr.", "Maybach_HL_230_TRM_P30_FR": "Maybach HL 230 TRM P30 fr.",
"Maybach_HL_230_TRM_P30_short": "Maybach\\nHL 230 TRM P30", "Maybach_HL_230_TRM_P30_short": "Maybach\\nHL 230 TRM P30",
"Maybach_HL_230_TRM_P30v3": "Maybach HL 230 TRM P30 V3",
"Maybach_HL_230_TRM_P45": "Maybach HL 230 TRM P45", "Maybach_HL_230_TRM_P45": "Maybach HL 230 TRM P45",
"Maybach_HL_230_TRM_P45A": "Maybach HL 230 TRM P45 A", "Maybach_HL_230_TRM_P45A": "Maybach HL 230 TRM P45 A",
"Maybach_HL_230_TRM_P45_short": "Maybach HL 230\\nTRM P45", "Maybach_HL_230_TRM_P45_short": "Maybach HL 230\\nTRM P45",
@@ -5892,6 +6273,10 @@
"NVSS_T66": "HVSS T66", "NVSS_T66": "HVSS T66",
"NVSS_T80": "HVSS T80", "NVSS_T80": "HVSS T80",
"Nashorn": "Nashorn", "Nashorn": "Nashorn",
"Nashorn_BP": "Epsilon",
"Nashorn_BP_Custom": "Epsilon Sagittarius",
"Nashorn_BP_Custom_short": "Epsilon Sagittarius",
"Nashorn_BP_short": "Epsilon",
"Nashorn_short": "Nashorn", "Nashorn_short": "Nashorn",
"Nuffield_liberty_MKII": "Nuffield Liberty Mk. II", "Nuffield_liberty_MKII": "Nuffield Liberty Mk. II",
"Nuffield_liberty_MKIV": "Nuffield Liberty Mk. III", "Nuffield_liberty_MKIV": "Nuffield Liberty Mk. III",
@@ -5946,7 +6331,6 @@
"Oe_mle._1932A": "HE Shell", "Oe_mle._1932A": "HE Shell",
"Ordnance_QF_2pdr_AT_Gun_Mk.X": "QF Mk. IX", "Ordnance_QF_2pdr_AT_Gun_Mk.X": "QF Mk. IX",
"Ordnance_QF_2pdr_AT_Gun_Mk.X_short": "QF Mk. IX", "Ordnance_QF_2pdr_AT_Gun_Mk.X_short": "QF Mk. IX",
"Oth06_Sega_Lupus": "Lupus",
"Oth08_WH_Vindicator": "Vindicator Ultramarines", "Oth08_WH_Vindicator": "Vindicator Ultramarines",
"Oth08_WH_Vindicator_Dark_Angels_Custom": "Vindicator Dark Angels", "Oth08_WH_Vindicator_Dark_Angels_Custom": "Vindicator Dark Angels",
"Oth08_WH_Vindicator_Dark_Angels_Custom_short": "Vindicator DA", "Oth08_WH_Vindicator_Dark_Angels_Custom_short": "Vindicator DA",
@@ -6201,6 +6585,8 @@
"PzVI_Porsche_Turm_short": "Pz.Kpfw. Tiger\\nAusf. E", "PzVI_Porsche_Turm_short": "Pz.Kpfw. Tiger\\nAusf. E",
"PzVI_Standardketten": "Pz.Kpfw. VI H Ausf. H1", "PzVI_Standardketten": "Pz.Kpfw. VI H Ausf. H1",
"PzVI_Tiger_P": "Tiger (P)", "PzVI_Tiger_P": "Tiger (P)",
"PzVI_Tiger_P_Custom": "Tiger (P) Sandsturm",
"PzVI_Tiger_P_Custom_short": "Tiger (P) Sandsturm",
"PzVI_Tiger_P_short": "Tiger (P)", "PzVI_Tiger_P_short": "Tiger (P)",
"PzVI_Vetstaerktketten": "Pz.Kpfw. Tiger Ausf. E", "PzVI_Vetstaerktketten": "Pz.Kpfw. Tiger Ausf. E",
"PzVI_skin_gamescom": "Tiger I Gamescom", "PzVI_skin_gamescom": "Tiger I Gamescom",
@@ -6267,6 +6653,8 @@
"R115_IS-3_auto": "IS-3 Defender", "R115_IS-3_auto": "IS-3 Defender",
"R115_IS-3_auto_short": "IS-3 Defender", "R115_IS-3_auto_short": "IS-3 Defender",
"R116_ISU122C_Berlin": "ISU-122S", "R116_ISU122C_Berlin": "ISU-122S",
"R119_Object_777": "Object 777 Version II",
"R119_Object_777_short": "Obj. 777 II",
"R120_T22SR": "T-22 medium", "R120_T22SR": "T-22 medium",
"R120_T22SR_Custom": "T-22 Uncatchable", "R120_T22SR_Custom": "T-22 Uncatchable",
"R120_T22SR_Custom_short": "T-22 Uncatchable", "R120_T22SR_Custom_short": "T-22 Uncatchable",
@@ -6290,6 +6678,8 @@
"R146_STG_Custom_short": "STG Guard", "R146_STG_Custom_short": "STG Guard",
"R146_STG_short": "STG", "R146_STG_short": "STG",
"R149_Object_268_4": "Object 268 Version 4", "R149_Object_268_4": "Object 268 Version 4",
"R149_Object_268_4_Custom": "Object 268/4 Chronos",
"R149_Object_268_4_Custom_short": "Object 268/4 Chronos",
"R149_Object_268_4_short": "Object 268/4", "R149_Object_268_4_short": "Object 268/4",
"R154_T-34E-43": "T-34 shielded", "R154_T-34E-43": "T-34 shielded",
"R154_T-34E-43_short": "T-34 shielded", "R154_T-34E-43_short": "T-34 shielded",
@@ -6313,6 +6703,8 @@
"R180_Object_274A_Custom": "Object 274a Leshy", "R180_Object_274A_Custom": "Object 274a Leshy",
"R180_Object_274A_Custom_short": "Object 274a Leshy", "R180_Object_274A_Custom_short": "Object 274a Leshy",
"R180_Object_274A_short": "Object 274a", "R180_Object_274A_short": "Object 274a",
"R181_Object_452K": "Object 452K",
"R181_Object_452K_short": "Object 452K",
"R43_T-70_57": "T-70/57", "R43_T-70_57": "T-70/57",
"R43_T-70_57_short": "T-70/57", "R43_T-70_57_short": "T-70/57",
"R63_ST_IBD": "T-2020", "R63_ST_IBD": "T-2020",
@@ -6357,6 +6749,8 @@
"Renault_T14": "Renault T14", "Renault_T14": "Renault T14",
"Renault_V4": "Renault V4", "Renault_V4": "Renault V4",
"RhB_Waffentrager": "Rhm.-Borsig Waffenträger", "RhB_Waffentrager": "Rhm.-Borsig Waffenträger",
"RhB_Waffentrager_Custom": "Rhm.-B. WT Tarnkappe",
"RhB_Waffentrager_Custom_short": "Rhm.-B. WT Tarnkappe",
"RhB_Waffentrager_short": "Rhm.-B. WT", "RhB_Waffentrager_short": "Rhm.-B. WT",
"Rolls_Royce_Griffon": "Rolls-Royce Griffon", "Rolls_Royce_Griffon": "Rolls-Royce Griffon",
"Rolls_Royce_Griffon-Mk2": "Rolls-Royce Griffon A", "Rolls_Royce_Griffon-Mk2": "Rolls-Royce Griffon A",
@@ -6416,7 +6810,13 @@
"S26_Lancen_C_Custom_short": "Lansen Moose", "S26_Lancen_C_Custom_short": "Lansen Moose",
"S26_Lancen_C_short": "Lansen C", "S26_Lancen_C_short": "Lansen C",
"S31_Strv_K": "Strv K", "S31_Strv_K": "Strv K",
"S31_Strv_K_Custom": "Strv K Konung",
"S31_Strv_K_Custom_short": "Strv K Konung",
"S31_Strv_K_short": "Strv K", "S31_Strv_K_short": "Strv K",
"S32_Bofors_Tornvagn": "Bofors Tornvagn",
"S32_Bofors_Tornvagn_Custom": "Tornvagn Stegosaurus",
"S32_Bofors_Tornvagn_Custom_short": "Tornvagn Stegosaurus",
"S32_Bofors_Tornvagn_short": "Tornvagn",
"S35_captured": "Pz.Kpfw. S35 739 (f)", "S35_captured": "Pz.Kpfw. S35 739 (f)",
"S35_captured_short": "Pz. S35", "S35_captured_short": "Pz. S35",
"SA35_4": "SA35 mle. 4", "SA35_4": "SA35 mle. 4",
@@ -6430,15 +6830,21 @@
"SOFAM_12_58": "SOFAM 12 58", "SOFAM_12_58": "SOFAM 12 58",
"SOFAM_8Gxb": "SOFAM 8 Gxb", "SOFAM_8Gxb": "SOFAM 8 Gxb",
"SOFAM_8Gxb_A": "SOFAM 8 Gxb", "SOFAM_8Gxb_A": "SOFAM 8 Gxb",
"SPA_15TB": "SPA 15TB",
"SPA_17TB": "SPA 17TB",
"SPA_242": "SPA 242", "SPA_242": "SPA 242",
"SPA_343": "SPA 343", "SPA_343": "SPA 343",
"SPA_344": "SPA 344", "SPA_344": "SPA 344",
"SPA_mod_15": "SPA mod. 15",
"SPA_mod_17": "SPA mod. 17",
"STA_1": "STA-1", "STA_1": "STA-1",
"STA_1_Rin": "STA-1 Rin", "STA_1_Rin": "STA-1 Rin",
"STA_1_short": "STA-1", "STA_1_short": "STA-1",
"ST_B1": "STB-1", "ST_B1": "STB-1",
"ST_B1_Custom": "STB-1 Adamant", "ST_B1_Custom": "STB-1 Adamant",
"ST_B1_Custom_short": "STB-1 Adamant", "ST_B1_Custom_short": "STB-1 Adamant",
"ST_B1_Custom_short_v2": "STB-1 Spinosaurus",
"ST_B1_Custom_v2": "STB-1 Spinosaurus",
"ST_B1_short": "STB-1", "ST_B1_short": "STB-1",
"ST_I": "ST-I", "ST_I": "ST-I",
"SU-100": "SU-100", "SU-100": "SU-100",
@@ -6452,7 +6858,7 @@
"SU-152": "SU-152", "SU-152": "SU-152",
"SU-152_01": "SU-152", "SU-152_01": "SU-152",
"SU-152_Custom": "SU-152 Taiga", "SU-152_Custom": "SU-152 Taiga",
"SU-152_Custom_short": "SU-152 Тaiga", "SU-152_Custom_short": "SU-152 Taiga",
"SU-152_mod_1943": "SU-152", "SU-152_mod_1943": "SU-152",
"SU-152_mod_1944": "SU-152M", "SU-152_mod_1944": "SU-152M",
"SU-18": "SU-18", "SU-18": "SU-18",
@@ -6660,6 +7066,8 @@
"T34_hvy": "T34", "T34_hvy": "T34",
"T34_hvy_Custom": "T34 Falcon", "T34_hvy_Custom": "T34 Falcon",
"T34_hvy_Custom_short": "T34 Falcon", "T34_hvy_Custom_short": "T34 Falcon",
"T34_hvy_Custom_short_v2": "T34 H-Combat",
"T34_hvy_Custom_v2": "T34 H-Combat",
"T34_hvy_Freedom": "T34 Independence", "T34_hvy_Freedom": "T34 Independence",
"T34_hvy_Freedom_short": "T34 (1776)", "T34_hvy_Freedom_short": "T34 (1776)",
"T37": "T37", "T37": "T37",
@@ -6669,6 +7077,8 @@
"T42": "T42", "T42": "T42",
"T48": "T48", "T48": "T48",
"T49": "T67", "T49": "T67",
"T49_Fearless": "T49 Fearless",
"T49_Fearless_short": "T49 Fearless",
"T49_Rocket": "T49 ATM", "T49_Rocket": "T49 ATM",
"T49_Rocket_short": "T49 A", "T49_Rocket_short": "T49 A",
"T51D1": "T51D1", "T51D1": "T51D1",
@@ -6677,6 +7087,8 @@
"T57_58": "T57 Heavy Tank", "T57_58": "T57 Heavy Tank",
"T57_58_Custom": "T57 Heavy Tireless", "T57_58_Custom": "T57 Heavy Tireless",
"T57_58_Custom_short": "T57 Heavy Tireless", "T57_58_Custom_short": "T57 Heavy Tireless",
"T57_58_Custom_short_v2": "T57 Heavy Ammoel",
"T57_58_Custom_v2": "T57 Heavy Ammoel",
"T57_58_short": "T57 Heavy", "T57_58_short": "T57 Heavy",
"T62A": "T-62A", "T62A": "T-62A",
"T62A_Custom": "T-62A Fearless", "T62A_Custom": "T-62A Fearless",
@@ -6703,6 +7115,8 @@
"T95": "T95", "T95": "T95",
"T95E2": "T95E2", "T95E2": "T95E2",
"T95E2_short": "T95E2", "T95E2_short": "T95E2",
"T95_Custom": "T95 Turtle",
"T95_Custom_short": "T95 Turtle",
"T99E2": "T99E2", "T99E2": "T99E2",
"TD-12": "TD-12", "TD-12": "TD-12",
"TD-12S": "TD-12S", "TD-12S": "TD-12S",
@@ -6773,6 +7187,7 @@
"V-2-54IS": "V-2-54IS", "V-2-54IS": "V-2-54IS",
"V-2-54IS_2": "V-2-54-IS/2", "V-2-54IS_2": "V-2-54-IS/2",
"V-2-54IS_GuP": "V-2-54 GuP", "V-2-54IS_GuP": "V-2-54 GuP",
"V-2-54SC": "V-2-54SC",
"V-2IS": "V-2IS", "V-2IS": "V-2IS",
"V-2IS_B": "V-2IS", "V-2IS_B": "V-2IS",
"V-2IS_B_2": "V-2IS-2", "V-2IS_B_2": "V-2IS-2",
@@ -6794,6 +7209,7 @@
"V-54-6": "V-54-6", "V-54-6": "V-54-6",
"V-54I": "V-54I", "V-54I": "V-54I",
"V-54K": "V-54K", "V-54K": "V-54K",
"V-54KML": "V-54K-ML",
"V-54K_TU": "V-54K", "V-54K_TU": "V-54K",
"V-55": "V-55", "V-55": "V-55",
"V-55U": "V-14", "V-55U": "V-14",
@@ -6917,8 +7333,10 @@
"W_12_5_B": "W12-5B", "W_12_5_B": "W12-5B",
"W_12_5_P": "W12-5P", "W_12_5_P": "W12-5P",
"Waffentrager_IV": "Waffenträger auf Pz. IV", "Waffentrager_IV": "Waffenträger auf Pz. IV",
"Waffentrager_IV_CL": "Waffenträger Camelot", "Waffentrager_IV_CL": "Waffenträger Ritter",
"Waffentrager_IV_CL_short": "WT Camelot", "Waffentrager_IV_CL_short": "WT Ritter",
"Waffentrager_IV_Custom": "WT auf Pz. IV Darkwing",
"Waffentrager_IV_Custom_short": "WT auf Pz. IV Darkwing",
"Waffentrager_IV_short": "WT auf Pz. IV", "Waffentrager_IV_short": "WT auf Pz. IV",
"Waterspit_1": "Waterspit", "Waterspit_1": "Waterspit",
"Wespe": "Wespe", "Wespe": "Wespe",
@@ -6927,6 +7345,7 @@
"Wespeketten_ausf_B": "Wespe verstärkteketten", "Wespeketten_ausf_B": "Wespe verstärkteketten",
"Wespeturm": "Wespe", "Wespeturm": "Wespe",
"Wright_Continental_R-975C1": "Wright Continental R-975C1", "Wright_Continental_R-975C1": "Wright Continental R-975C1",
"Wright_Continental_R-975C1_BP": "Wright Continental R-975C1 B",
"Wright_Continental_R-975C1_GuP": "Wright Continental R-975C1 GuP", "Wright_Continental_R-975C1_GuP": "Wright Continental R-975C1 GuP",
"Wright_Continental_R-975C1_short": "Wright C. R-975C1", "Wright_Continental_R-975C1_short": "Wright C. R-975C1",
"Wright_Continental_R-975C2": "Wright Continental R-975C2", "Wright_Continental_R-975C2": "Wright Continental R-975C2",
@@ -6940,6 +7359,7 @@
"Wright_Continental_R-975EC2_short": "Wright C. R-975EC2", "Wright_Continental_R-975EC2_short": "Wright C. R-975EC2",
"Wright_Continental_R_975_C4_858": "Wright Continental\\nR-975-C4 (858)", "Wright_Continental_R_975_C4_858": "Wright Continental\\nR-975-C4 (858)",
"Wright_G200_m_781C9GC1": "Wright G200 M781C9GC1", "Wright_G200_m_781C9GC1": "Wright G200 M781C9GC1",
"Wright_G200_m_781C9GC1V2_BP": "Wright G200 M781C9GC1",
"Wright_G200_m_781C9GC5": "Wright G200 M781C9GC5", "Wright_G200_m_781C9GC5": "Wright G200 M781C9GC5",
"Wright_G200_m_781C9GC5_short": "Wright G200\\nM781C9GC5", "Wright_G200_m_781C9GC5_short": "Wright G200\\nM781C9GC5",
"Wright_G200_m_795C9GC1": "Wright G200 M795C9GC1", "Wright_G200_m_795C9GC1": "Wright G200 M795C9GC1",