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.
- wt_resource_tool-0.1.0/PKG-INFO +68 -0
- wt_resource_tool-0.1.0/README.md +49 -0
- wt_resource_tool-0.1.0/pyproject.toml +34 -0
- wt_resource_tool-0.1.0/src/wt_resource_tool/__init__.py +3 -0
- wt_resource_tool-0.1.0/src/wt_resource_tool/_client.py +165 -0
- wt_resource_tool-0.1.0/src/wt_resource_tool/parser/__init__.py +0 -0
- wt_resource_tool-0.1.0/src/wt_resource_tool/parser/player_medal_parser.py +44 -0
- wt_resource_tool-0.1.0/src/wt_resource_tool/parser/player_title_parser.py +51 -0
- wt_resource_tool-0.1.0/src/wt_resource_tool/parser/vehicle_data_parser.py +77 -0
- wt_resource_tool-0.1.0/src/wt_resource_tool/py.typed +0 -0
- wt_resource_tool-0.1.0/src/wt_resource_tool/schema/__init__.py +0 -0
- wt_resource_tool-0.1.0/src/wt_resource_tool/schema/_wt_schema.py +168 -0
|
@@ -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,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
|
|
File without changes
|
|
@@ -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
|
|
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
|