wt-resource-tool 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.3
2
+ Name: wt-resource-tool
3
+ Version: 0.1.0
4
+ Summary:
5
+ Author: axiangcoding
6
+ Author-email: axiangcoding@gmail.com
7
+ Requires-Python: >=3.12,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
12
+ Requires-Dist: httpx (>=0.28.1,<0.29.0)
13
+ Requires-Dist: loguru (>=0.7.3,<0.8.0)
14
+ Requires-Dist: pydantic (>=2.10.6,<3.0.0)
15
+ Requires-Dist: pydantic-settings (>=2.8.1,<3.0.0)
16
+ Requires-Dist: python-dotenv (>=1.1.0,<2.0.0)
17
+ Description-Content-Type: text/markdown
18
+
19
+ # WT Resource Tool
20
+
21
+ > [!warning]
22
+ > This project is still in development, PRs are welcome!
23
+
24
+ As a War Thunder community developer, interested in unpacking data but turned off by the complexity of the results? Try the WT Resource Tool!
25
+
26
+ We did the hard work for you, parsing the data and making it human-readable, so you can focus on what you do best: creating awesome projects for the War Thunder community.
27
+
28
+ ## Not official
29
+
30
+ This project is not affiliated with Gaijin Entertainment.
31
+
32
+ > However, we did get in touch with Gaijin, maybe some interesting things will happen in the future. WHO KNOWS?
33
+
34
+ ## Features
35
+
36
+ - **Easy to use**: Install `wt-resource-tool` via pip and start using it right away.
37
+ - **Human-readable**: All data is parsed and presented in a human-readable format.
38
+ - **Accessible**: Don't need to download entire data to your local environment everytime.
39
+ - **Up-to-date**: We keep the data up-to-date with the [War-Thunder-Datamine](https://github.com/gszabi99/War-Thunder-Datamine) repository. Thanks to the contributors!
40
+
41
+ ## Data
42
+
43
+ - **Player titles**: All player titles in the game, including their localization.
44
+ - **Player medals**: All player medals in the game, including their localization and image at different sizes.
45
+ - **Vehicle data**: All vehicle data in the game
46
+
47
+ > [!note]
48
+ > More data will be added soon!
49
+
50
+ ## Usage
51
+
52
+ ### Use python package `wt-resource-tool`
53
+
54
+ Install the package using pip:
55
+
56
+ > [!warning]
57
+ > This package is not available on PyPi yet. You can install it from source.
58
+
59
+ Then you can use it in your python code. We provide some examples to get you started, take a look at the [playground](playground) folder.
60
+
61
+ ### Use static data in json format
62
+
63
+ You can find data we parsed ahead in json format under `/static` folder.
64
+
65
+ ## Credits
66
+
67
+ Special thanks to [War-Thunder-Datamine](https://github.com/gszabi99/War-Thunder-Datamine) for providing the original data!
68
+
@@ -0,0 +1,49 @@
1
+ # WT Resource Tool
2
+
3
+ > [!warning]
4
+ > This project is still in development, PRs are welcome!
5
+
6
+ As a War Thunder community developer, interested in unpacking data but turned off by the complexity of the results? Try the WT Resource Tool!
7
+
8
+ We did the hard work for you, parsing the data and making it human-readable, so you can focus on what you do best: creating awesome projects for the War Thunder community.
9
+
10
+ ## Not official
11
+
12
+ This project is not affiliated with Gaijin Entertainment.
13
+
14
+ > However, we did get in touch with Gaijin, maybe some interesting things will happen in the future. WHO KNOWS?
15
+
16
+ ## Features
17
+
18
+ - **Easy to use**: Install `wt-resource-tool` via pip and start using it right away.
19
+ - **Human-readable**: All data is parsed and presented in a human-readable format.
20
+ - **Accessible**: Don't need to download entire data to your local environment everytime.
21
+ - **Up-to-date**: We keep the data up-to-date with the [War-Thunder-Datamine](https://github.com/gszabi99/War-Thunder-Datamine) repository. Thanks to the contributors!
22
+
23
+ ## Data
24
+
25
+ - **Player titles**: All player titles in the game, including their localization.
26
+ - **Player medals**: All player medals in the game, including their localization and image at different sizes.
27
+ - **Vehicle data**: All vehicle data in the game
28
+
29
+ > [!note]
30
+ > More data will be added soon!
31
+
32
+ ## Usage
33
+
34
+ ### Use python package `wt-resource-tool`
35
+
36
+ Install the package using pip:
37
+
38
+ > [!warning]
39
+ > This package is not available on PyPi yet. You can install it from source.
40
+
41
+ Then you can use it in your python code. We provide some examples to get you started, take a look at the [playground](playground) folder.
42
+
43
+ ### Use static data in json format
44
+
45
+ You can find data we parsed ahead in json format under `/static` folder.
46
+
47
+ ## Credits
48
+
49
+ Special thanks to [War-Thunder-Datamine](https://github.com/gszabi99/War-Thunder-Datamine) for providing the original data!
@@ -0,0 +1,34 @@
1
+ [project]
2
+ name = "wt-resource-tool"
3
+ version = "0.1.0"
4
+ description = ""
5
+ authors = [{ name = "axiangcoding", email = "axiangcoding@gmail.com" }]
6
+ readme = "README.md"
7
+ requires-python = ">=3.12,<4.0"
8
+ dependencies = [
9
+ "pydantic (>=2.10.6,<3.0.0)",
10
+ "pydantic-settings (>=2.8.1,<3.0.0)",
11
+ "httpx (>=0.28.1,<0.29.0)",
12
+ "python-dotenv (>=1.1.0,<2.0.0)",
13
+ "aiofiles (>=24.1.0,<25.0.0)",
14
+ "loguru (>=0.7.3,<0.8.0)",
15
+ ]
16
+
17
+ [tool.poetry]
18
+ packages = [{ include = "wt_resource_tool", from = "src" }]
19
+
20
+
21
+ [tool.poetry.group.dev.dependencies]
22
+ ruff = "^0.9.10"
23
+ pytest = "^8.3.5"
24
+ ipykernel = "^6.29.5"
25
+ mypy = "^1.15.0"
26
+
27
+ [build-system]
28
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
29
+ build-backend = "poetry.core.masonry.api"
30
+
31
+
32
+ [tool.ruff]
33
+ line-length = 120
34
+ lint.select = ["I", "N", "UP", "B", "A", "ASYNC"]
@@ -0,0 +1,3 @@
1
+ from wt_resource_tool._client import WTResourceTool
2
+
3
+ __all__ = ["WTResourceTool"]
@@ -0,0 +1,165 @@
1
+ import asyncio
2
+ import time
3
+ from tracemalloc import start
4
+ from typing import Literal
5
+
6
+ import httpx
7
+ from loguru import logger
8
+ from pydantic import BaseModel, Field
9
+
10
+ from wt_resource_tool.parser import player_medal_parser, player_title_parser, vehicle_data_parser
11
+ from wt_resource_tool.schema._wt_schema import (
12
+ PlayerMedalDesc,
13
+ PlayerMedalStorage,
14
+ PlayerTitleDesc,
15
+ PlayerTitleStorage,
16
+ Vehicle,
17
+ VehicleStorage,
18
+ )
19
+
20
+ type DataType = Literal["player_title", "player_medal", "vehicle"]
21
+ type DataSource = Literal["github", "github-jsdelivr"] | str
22
+
23
+
24
+ class WTResourceTool(BaseModel):
25
+ """
26
+ A tool to parse and get data about War Thunder.
27
+
28
+ """
29
+
30
+ title_storage: PlayerTitleStorage | None = Field(default=None)
31
+ medal_storage: PlayerMedalStorage | None = Field(default=None)
32
+ vehicle_storage: VehicleStorage | None = Field(default=None)
33
+
34
+ async def load_parsed_data(
35
+ self,
36
+ data_types: list[DataType],
37
+ game_version: str = "latest",
38
+ source: DataSource = "github-jsdelivr",
39
+ ):
40
+ """
41
+ Load pre-parsed data from remote.
42
+ The data is stored in the static folder of the repository.
43
+
44
+ Args:
45
+ data_types (list[DataType]): The data types to load.
46
+ game_version (str): The game version to load. Default is "latest".
47
+ source (DataSource): The source of the data. Default is "github-jsdelivr". It can be "github", "github-jsdelivr" or a custom url.
48
+ """
49
+ start_time = time.time()
50
+ if source == "github":
51
+ resource_url_prefix = (
52
+ "https://raw.githubusercontent.com/axiangcoding/wt-resource-tool/refs/heads/main/static"
53
+ )
54
+ elif source == "github-jsdelivr":
55
+ resource_url_prefix = "https://cdn.jsdelivr.net/gh/axiangcoding/wt-resource-tool/static"
56
+ else:
57
+ if not source.startswith("https:// "):
58
+ raise ValueError("Custom source must be a valid safe url")
59
+ resource_url_prefix = source
60
+ game_version_folder_str = game_version.replace(".", "_")
61
+
62
+ if "player_title" in data_types:
63
+ resource_url = f"{resource_url_prefix}/{game_version_folder_str}/player_title.json"
64
+ logger.debug("Loading player title data from {}", resource_url)
65
+ title_data = await self.__get_data_from_remote(resource_url)
66
+ self.title_storage = PlayerTitleStorage.model_validate_json(title_data)
67
+
68
+ if "player_medal" in data_types:
69
+ resource_url = f"{resource_url_prefix}/{game_version_folder_str}/player_medal.json"
70
+ logger.debug("Loading player medal data from {}", resource_url)
71
+ medal_data = await self.__get_data_from_remote(resource_url)
72
+
73
+ self.medal_storage = PlayerMedalStorage.model_validate_json(medal_data)
74
+
75
+ if "vehicle" in data_types:
76
+ resource_url = f"{resource_url_prefix}/{game_version_folder_str}/vehicle.json"
77
+ logger.debug("Loading vehicle data from {}", resource_url)
78
+ vehicle_data = await self.__get_data_from_remote(resource_url)
79
+ self.vehicle_storage = VehicleStorage.model_validate_json(vehicle_data)
80
+
81
+ end_time = time.time()
82
+ logger.debug(
83
+ "Loaded data in {} seconds",
84
+ round(end_time - start_time, 2),
85
+ )
86
+
87
+ async def parse_and_load_data(
88
+ self,
89
+ data_types: list[DataType],
90
+ local_repo_path: str,
91
+ git_pull_when_empty: bool = False,
92
+ ):
93
+ """
94
+ Parse and load data from local repo.
95
+
96
+ This action may take a long time if repo not exist. Because it needs to clone the repo first.
97
+
98
+ Args:
99
+ data_types (list[DataType]): The data types to load.
100
+ local_repo_path (str): The local repo path.
101
+ git_pull_when_empty (bool): Whether to pull the repo when it is empty. Default is False.
102
+ """
103
+ # TODO check if the repo is empty and pull it if it is empty
104
+ start_time = time.time()
105
+ if "player_title" in data_types:
106
+ logger.debug("Parsing player title data from {}", local_repo_path)
107
+ ms = await asyncio.to_thread(lambda: player_medal_parser.parse_player_medal(local_repo_path))
108
+ self.medal_storage = ms
109
+ if "player_medal" in data_types:
110
+ logger.debug("Parsing player medal data from {}", local_repo_path)
111
+ ts = await asyncio.to_thread(lambda: player_title_parser.parse_player_title(local_repo_path))
112
+ self.title_storage = ts
113
+ if "vehicle" in data_types:
114
+ logger.debug("Parsing vehicle data from {}", local_repo_path)
115
+ vs = await asyncio.to_thread(lambda: vehicle_data_parser.parse_vehicle_data(local_repo_path))
116
+ self.vehicle_storage = vs
117
+ end_time = time.time()
118
+ logger.debug(
119
+ "Parsed data in {} seconds",
120
+ round(end_time - start_time, 2),
121
+ )
122
+
123
+ async def get_loaded_data_version(self) -> dict[DataType, str | None]:
124
+ return {
125
+ "player_title": self.title_storage.game_version if self.title_storage else None,
126
+ "player_medal": self.medal_storage.game_version if self.medal_storage else None,
127
+ "vehicle": self.vehicle_storage.game_version if self.vehicle_storage else None,
128
+ }
129
+
130
+ async def get_title(self, title_id: str) -> PlayerTitleDesc:
131
+ """
132
+ Get title data by id.
133
+
134
+ """
135
+ if self.title_storage is None:
136
+ raise ValueError("No data loaded")
137
+ return self.title_storage.titles_map[title_id]
138
+
139
+ async def get_medal(self, medal_id: str) -> PlayerMedalDesc:
140
+ """
141
+ Get medal data by id.
142
+
143
+ """
144
+ if self.medal_storage is None:
145
+ raise ValueError("No data loaded")
146
+ return self.medal_storage.medals_map[medal_id]
147
+
148
+ async def get_vehicle(self, vehicle_id: str) -> Vehicle:
149
+ """
150
+ Get vehicle data by id.
151
+
152
+ """
153
+ if self.vehicle_storage is None:
154
+ raise ValueError("No data loaded")
155
+ return self.vehicle_storage.vehicles_map[vehicle_id]
156
+
157
+ async def __get_data_from_remote(
158
+ self,
159
+ resource_url: str,
160
+ ) -> str:
161
+ async with httpx.AsyncClient(timeout=60) as client:
162
+ resp = await client.get(resource_url)
163
+ resp.raise_for_status()
164
+ storage_text = resp.text
165
+ return storage_text
@@ -0,0 +1,44 @@
1
+ import csv
2
+ from os import path
3
+
4
+ from wt_resource_tool.schema._wt_schema import PlayerMedalDesc, PlayerMedalStorage
5
+
6
+
7
+ def _get_dt_from_csv(data: csv.DictReader) -> list[PlayerMedalDesc]:
8
+ titles: list[PlayerMedalDesc] = []
9
+ for row in data:
10
+ row1 = row["<ID|readonly|noverify>"]
11
+
12
+ if row1.endswith("/name"):
13
+ mid = row["<ID|readonly|noverify>"].replace("/name", "")
14
+
15
+ td = PlayerMedalDesc(
16
+ id=mid,
17
+ english=row["<English>"],
18
+ french=row["<French>"],
19
+ italian=row["<Italian>"],
20
+ german=row["<German>"],
21
+ spanish=row["<Spanish>"],
22
+ japanese=row["<Japanese>"].replace("\\t", ""),
23
+ chinese=row["<Chinese>"].replace("\\t", ""),
24
+ russian=row["<Russian>"],
25
+ comments=row["<Comments>"],
26
+ max_chars=row["<max_chars>"],
27
+ )
28
+ titles.append(td)
29
+ return titles
30
+
31
+
32
+ def parse_player_medal(repo_path: str) -> PlayerMedalStorage:
33
+ all_medals: list[PlayerMedalDesc] = []
34
+
35
+ with open(path.join(repo_path, "lang.vromfs.bin_u/lang/unlocks_medals.csv"), encoding="utf-8") as f:
36
+ data = csv.DictReader(f, delimiter=";")
37
+ all_medals.extend(_get_dt_from_csv(data))
38
+
39
+ medals_map = {}
40
+ for title in all_medals:
41
+ medals_map[title.id] = title
42
+
43
+ game_version = open(path.join(repo_path, "version"), encoding="utf-8").read()
44
+ return PlayerMedalStorage(medals_map=medals_map, game_version=game_version.strip())
@@ -0,0 +1,51 @@
1
+ import csv
2
+ from os import path
3
+
4
+ from wt_resource_tool.schema._wt_schema import PlayerTitleDesc, PlayerTitleStorage
5
+
6
+
7
+ def _get_dt_from_csv(data: csv.DictReader) -> list[PlayerTitleDesc]:
8
+ titles: list[PlayerTitleDesc] = []
9
+ for row in data:
10
+ row1 = row["<ID|readonly|noverify>"]
11
+
12
+ if row1.startswith("title/") and (not row1.endswith("/desc")):
13
+ mid = row["<ID|readonly|noverify>"].replace("title/", "")
14
+
15
+ td = PlayerTitleDesc(
16
+ id=mid,
17
+ english=row["<English>"],
18
+ french=row["<French>"],
19
+ italian=row["<Italian>"],
20
+ german=row["<German>"],
21
+ spanish=row["<Spanish>"],
22
+ japanese=row["<Japanese>"].replace("\\t", ""),
23
+ chinese=row["<Chinese>"].replace("\\t", ""),
24
+ russian=row["<Russian>"],
25
+ comments=row["<Comments>"],
26
+ max_chars=row["<max_chars>"],
27
+ )
28
+ titles.append(td)
29
+ return titles
30
+
31
+
32
+ def parse_player_title(repo_path: str) -> PlayerTitleStorage:
33
+ all_titles: list[PlayerTitleDesc] = []
34
+ with open(path.join(repo_path, "regional.vromfs.bin_u/lang/regional_titles.csv"), encoding="utf-8") as f:
35
+ data = csv.DictReader(f, delimiter=";")
36
+ all_titles.extend(_get_dt_from_csv(data))
37
+
38
+ with open(path.join(repo_path, "lang.vromfs.bin_u/lang/unlocks_achievements.csv"), encoding="utf-8") as f:
39
+ data = csv.DictReader(f, delimiter=";")
40
+ all_titles.extend(_get_dt_from_csv(data))
41
+
42
+ with open(path.join(repo_path, "regional.vromfs.bin_u/lang/tournaments.csv"), encoding="utf-8") as f:
43
+ data = csv.DictReader(f, delimiter=";")
44
+ all_titles.extend(_get_dt_from_csv(data))
45
+
46
+ title_map = {}
47
+ for title in all_titles:
48
+ title_map[title.id] = title
49
+
50
+ game_version = open(path.join(repo_path, "version"), encoding="utf-8").read()
51
+ return PlayerTitleStorage(titles_map=title_map, game_version=game_version.strip())
@@ -0,0 +1,77 @@
1
+ import json
2
+ from os import path
3
+
4
+ from loguru import logger
5
+
6
+ from wt_resource_tool.schema._wt_schema import Vehicle, VehicleStorage
7
+
8
+
9
+ def parse_vehicle_data(repo_path: str) -> VehicleStorage:
10
+ game_version = open(path.join(repo_path, "version"), encoding="utf-8").read()
11
+
12
+ wpcost_path = path.join(repo_path, "char.vromfs.bin_u", "config", "wpcost.blkx")
13
+ with open(wpcost_path, encoding="utf-8") as f:
14
+ wpcost = f.read()
15
+
16
+ data: dict = json.loads(wpcost)
17
+ convert_key_map = {
18
+ "rank": "rank",
19
+ "economicRankArcade": "economic_rank_arcade",
20
+ "economicRankHistorical": "economic_rank_historical",
21
+ "economicRankSimulation": "economic_rank_simulation",
22
+ "country": "country",
23
+ "unitClass": "unit_class",
24
+ "spawnType": "spawn_type",
25
+ "value": "value",
26
+ "reqExp": "req_exp",
27
+ "trainCost": "train_cost",
28
+ "train2Cost": "train2_cost",
29
+ "train3Cost_gold": "train3_cost_gold",
30
+ "train3Cost_exp": "train3_cost_exp",
31
+ "repairCostArcade": "repair_cost_arcade",
32
+ "repairCostHistorical": "repair_cost_historical",
33
+ "repairCostSimulation": "repair_cost_simulation",
34
+ "repairCostPerMinArcade": "repair_cost_per_min_arcade",
35
+ "repairCostPerMinHistorical": "repair_cost_per_min_historical",
36
+ "repairCostPerMinSimulation": "repair_cost_per_min_simulation",
37
+ "repairCostFullUpgradedArcade": "repair_cost_full_upgraded_arcade",
38
+ "repairCostFullUpgradedHistorical": "repair_cost_full_upgraded_historical",
39
+ "repairCostFullUpgradedSimulation": "repair_cost_full_upgraded_simulation",
40
+ "rewardMulArcade": "reward_mul_arcade",
41
+ "rewardMulHistorical": "reward_mul_historical",
42
+ "rewardMulSimulation": "reward_mul_simulation",
43
+ "expMul": "exp_mul",
44
+ "reqAir": "req_air",
45
+ "reloadTime_cannon": "reload_time_cannon",
46
+ "crewTotalCount": "crew_total_count",
47
+ "primaryWeaponAutoLoader": "primary_weapon_auto_loader",
48
+ "costGold": "cost_gold",
49
+ "turretSpeed": "turret_speed",
50
+ "gift": "gift",
51
+ "researchType": "research_type",
52
+ "economicRankTankHistorical": "economic_rank_tank_historical",
53
+ "economicRankTankSimulation": "economic_rank_tank_simulation",
54
+ }
55
+
56
+ vehicles = {}
57
+ for key in data.keys():
58
+ if not isinstance(data[key], dict):
59
+ logger.warning("key {} is not a dict, skip.", key)
60
+ continue
61
+ try:
62
+ v_data: dict = data[key]
63
+ n_data = {
64
+ "id": key,
65
+ }
66
+
67
+ for k, v in v_data.items():
68
+ if k in convert_key_map:
69
+ n_data[convert_key_map[k]] = v
70
+ else:
71
+ n_data[k] = v
72
+
73
+ vehicles[key] = Vehicle.model_validate(n_data)
74
+ except Exception as e:
75
+ logger.warning("error when parsing vehicle id: {}, skip", key)
76
+ raise e
77
+ return VehicleStorage(vehicles_map=vehicles, game_version=game_version.strip())
File without changes
@@ -0,0 +1,168 @@
1
+ from typing import Literal
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class PlayerTitleDesc(BaseModel):
7
+ id: str
8
+ english: str
9
+ french: str
10
+ italian: str
11
+ german: str
12
+ spanish: str
13
+ russian: str
14
+ # polish: str
15
+ # czech: str
16
+ # turkish: str
17
+ chinese: str
18
+ japanese: str
19
+ # portuguese: str
20
+ # ukrainian: str
21
+ # serbian: str
22
+ # hungarian: str
23
+ # korean: str
24
+ # belarusian: str
25
+ # romanian: str
26
+ # vietnamese: str
27
+ # t_chinese: str
28
+ # h_chinese: str
29
+ comments: str
30
+ max_chars: str
31
+
32
+
33
+ class PlayerTitleStorage(BaseModel):
34
+ titles_map: dict[str, PlayerTitleDesc]
35
+ game_version: str
36
+
37
+
38
+ class PlayerMedalDesc(BaseModel):
39
+ id: str
40
+ english: str
41
+ french: str
42
+ italian: str
43
+ german: str
44
+ spanish: str
45
+ russian: str
46
+ # polish: str
47
+ # czech: str
48
+ # turkish: str
49
+ chinese: str
50
+ japanese: str
51
+ # portuguese: str
52
+ # ukrainian: str
53
+ # serbian: str
54
+ # hungarian: str
55
+ # korean: str
56
+ # belarusian: str
57
+ # romanian: str
58
+ # vietnamese: str
59
+ # t_chinese: str
60
+ # h_chinese: str
61
+ comments: str
62
+ max_chars: str
63
+
64
+ def get_image_url(
65
+ self,
66
+ mode: Literal["normal", "big", "ribbon"] = "normal",
67
+ ) -> str:
68
+ prefix = (
69
+ "https://cdn.jsdelivr.net/gh/gszabi99/War-Thunder-Datamine@refs/heads/master/atlases.vromfs.bin_u/medals"
70
+ )
71
+ if mode == "normal":
72
+ return f"{prefix}/{self.id}.png"
73
+ elif mode == "big":
74
+ return f"{prefix}/{self.id}_big.png"
75
+ elif mode == "ribbon":
76
+ return f"{prefix}/{self.id}_ribbon.png"
77
+
78
+
79
+ class PlayerMedalStorage(BaseModel):
80
+ medals_map: dict[str, PlayerMedalDesc]
81
+ game_version: str
82
+
83
+
84
+ type Country = Literal[
85
+ "country_usa",
86
+ "country_germany",
87
+ "country_ussr",
88
+ "country_britain",
89
+ "country_japan",
90
+ "country_france",
91
+ "country_italy",
92
+ "country_china",
93
+ "country_sweden",
94
+ "country_israel",
95
+ ]
96
+
97
+
98
+ class Vehicle(BaseModel):
99
+ id: str
100
+ rank: int
101
+ economic_rank_arcade: int
102
+ economic_rank_historical: int
103
+ economic_rank_simulation: int
104
+
105
+ economic_rank_tank_historical: int | None = Field(default=None)
106
+ economic_rank_tank_simulation: int | None = Field(default=None)
107
+
108
+ country: Country
109
+ unit_class: str
110
+ spawn_type: str | None = Field(
111
+ default=None,
112
+ )
113
+ value: int
114
+ req_exp: int | None = Field(
115
+ default=None,
116
+ )
117
+ train_cost: int
118
+ train2_cost: int
119
+ train3_cost_gold: int
120
+ train3_cost_exp: int
121
+ repair_cost_arcade: int
122
+ repair_cost_historical: int
123
+ repair_cost_simulation: int
124
+ repair_cost_per_min_arcade: int
125
+ repair_cost_per_min_historical: int
126
+ repair_cost_per_min_simulation: int
127
+ repair_cost_full_upgraded_arcade: int | None = Field(
128
+ default=None,
129
+ )
130
+ repair_cost_full_upgraded_historical: int | None = Field(
131
+ default=None,
132
+ )
133
+ repair_cost_full_upgraded_simulation: int | None = Field(
134
+ default=None,
135
+ )
136
+ reward_mul_arcade: float = Field(
137
+ default=0,
138
+ )
139
+ reward_mul_historical: float
140
+ reward_mul_simulation: float
141
+ exp_mul: float
142
+ req_air: str | None = Field(
143
+ default=None,
144
+ )
145
+ reload_time_cannon: float | None = Field(
146
+ default=None,
147
+ )
148
+ crew_total_count: int | None = Field(default=None)
149
+
150
+ primary_weapon_auto_loader: bool | None = Field(default=None)
151
+
152
+ cost_gold: int | None = Field(default=None)
153
+
154
+ turret_speed: list[float] | None = Field(default=None)
155
+
156
+ gift: str | None = Field(default=None)
157
+
158
+ research_type: str | None = Field(default=None)
159
+
160
+ def get_icon_url(
161
+ self,
162
+ ) -> str:
163
+ return f"https://cdn.jsdelivr.net/gh/gszabi99/War-Thunder-Datamine@refs/heads/master/atlases.vromfs.bin_u/units/{self.id}.png"
164
+
165
+
166
+ class VehicleStorage(BaseModel):
167
+ vehicles_map: dict[str, Vehicle]
168
+ game_version: str