Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a90e9312d | ||
|
|
2c7082fe04 | ||
|
|
518a83ee73 | ||
|
|
25c40bcea2 | ||
|
|
f88ac8daf7 | ||
|
|
c4be6b78b6 | ||
|
|
2cfc65a990 | ||
|
|
a8eb751048 | ||
|
|
f030fd53b1 | ||
|
|
da506083c9 | ||
|
|
8bdc4d9b1b | ||
|
|
e0718d9ebb | ||
|
|
dbf1718d39 | ||
|
|
84dd13c0ac | ||
|
|
fdecef5f9c | ||
|
|
198e800958 | ||
|
|
29bb8ada06 | ||
|
|
662f116afe | ||
|
|
02f0c078a5 | ||
|
|
4dd1113b6d | ||
|
|
e656b83719 | ||
|
|
b90b355f8f | ||
|
|
165a9cd719 | ||
|
|
5b8da36fb8 | ||
|
|
bbe682fa73 | ||
|
|
af08df1526 | ||
|
|
47a60adac7 | ||
|
|
10f3bc0026 | ||
|
|
e7da1d582f | ||
|
|
df70816f7e | ||
|
|
091364d7f3 | ||
|
|
753c06eee8 | ||
|
|
17713243b5 | ||
|
|
4e4f070368 | ||
|
|
4064f163ec | ||
|
|
b911f64847 | ||
|
|
b86b6e09d0 | ||
|
|
d2e7a477bb | ||
|
|
f9c182e68c | ||
|
|
7c497ab681 | ||
|
|
c840086c11 | ||
|
|
8649d09a94 | ||
|
|
c764564a96 | ||
|
|
ad8cbb96b7 | ||
|
|
b409e26ca5 | ||
|
|
fbbfa5307c | ||
|
|
4165533838 | ||
|
|
fb47ab3e64 | ||
|
|
57971453ad | ||
|
|
c8cb490b48 | ||
|
|
232f24181f | ||
|
|
46441d74d8 | ||
|
|
a2274a6f0c | ||
|
|
0eb2827e7b | ||
|
|
41cb3f8511 | ||
|
|
9405dccee9 | ||
|
|
83b0c6abc3 | ||
|
|
bf1621001b | ||
|
|
8a5a4bf696 | ||
|
|
a99cce07dc | ||
|
|
d8c26ba24a | ||
|
|
f0b393c48f | ||
|
|
d0d205bb93 | ||
|
|
aeaf52d3ac | ||
|
|
e05358952a | ||
|
|
c1000e1608 | ||
|
|
46c86c5352 | ||
|
|
507b503560 | ||
|
|
37ca981d33 | ||
|
|
efe6d2b1a5 | ||
|
|
9f79f9c1e8 | ||
|
|
38b4c62777 | ||
|
|
df1bc56a4c | ||
|
|
c3d2b063bc | ||
|
|
1b94748668 | ||
|
|
f4f804727f | ||
|
|
3729072594 | ||
|
|
c174f32e15 | ||
|
|
2443ecde2f | ||
|
|
9c027b3dde | ||
|
|
22d794ac65 | ||
|
|
35073c184e | ||
|
|
4741406e4a | ||
|
|
24107433c6 | ||
|
|
05c6fcfb34 | ||
|
|
be7a16d399 | ||
|
|
c7f781dbb2 | ||
|
|
a66c6ebb27 | ||
|
|
83aeca2e5e | ||
|
|
89a3db05d8 | ||
|
|
6050145b16 | ||
|
|
77c78741b3 | ||
|
|
f35670d6a8 | ||
|
|
3392a57480 | ||
|
|
ed64ee8228 | ||
|
|
4f47d6ce85 | ||
|
|
92d56ac086 | ||
|
|
671b8b60b9 | ||
|
|
ba5a5245d9 | ||
|
|
eee262add7 | ||
|
|
e5eaf3fd75 | ||
|
|
5fdc9044ca | ||
|
|
0924a77776 | ||
|
|
1a46e176f5 | ||
|
|
efb8df9769 | ||
|
|
04e09b84ff | ||
|
|
b52c107af9 | ||
|
|
c5e6f5e2eb | ||
|
|
365c68aa4d | ||
|
|
ff9ec55da6 | ||
|
|
2a2994ae69 | ||
|
|
e3c6497d29 | ||
|
|
bdc013b4c8 | ||
|
|
e20d65d218 | ||
|
|
8b726c12d9 | ||
|
|
da704e6181 | ||
|
|
64d346ccb6 | ||
|
|
c090ad6492 | ||
|
|
8b6610151e | ||
|
|
545b655d4e | ||
|
|
0e6aa734cb | ||
|
|
2bef96475c | ||
|
|
45938c875f | ||
|
|
9d916a588d | ||
|
|
f0295079cc | ||
|
|
6d8387fa9d | ||
|
|
7a18de6650 | ||
|
|
cc6c4faa2d | ||
|
|
4253cd3e1b | ||
|
|
75cf827082 | ||
|
|
73c8807f14 | ||
|
|
9fac6dd40d | ||
|
|
c06c338536 | ||
|
|
9258623931 | ||
|
|
3b02c25973 | ||
|
|
f90768d155 | ||
|
|
d77bd6a5f9 | ||
|
|
6836cb327f | ||
|
|
4373efd0cd | ||
|
|
f13165e77d | ||
|
|
8a5b0c1170 | ||
|
|
f16511a72e | ||
|
|
df0dc4f118 | ||
|
|
92399de5e2 | ||
|
|
8d9dff8d73 | ||
|
|
f91744f2f0 | ||
|
|
e946dfdb69 | ||
|
|
6283ddc57a | ||
|
|
2d66e0ab28 | ||
|
|
c3d8f15a2a | ||
|
|
21a37ec6cc | ||
|
|
de471400ad | ||
|
|
0b16295853 | ||
|
|
63eea81856 | ||
|
|
ba57d61af5 | ||
|
|
3d940f4b47 | ||
|
|
8ef355c574 | ||
|
|
71e1ab1362 | ||
|
|
53dc108356 | ||
|
|
57b3218c5a | ||
|
|
7472e39faf | ||
|
|
034417d19f | ||
|
|
0368e9ceda | ||
|
|
28f365f762 | ||
|
|
40a81f6b1b | ||
|
|
9475d22472 | ||
|
|
9538a03afb | ||
|
|
d5d171bea6 | ||
|
|
a9c9a90ac1 | ||
|
|
f7c562f8b6 | ||
|
|
836cf69f01 | ||
|
|
ba17aae706 | ||
|
|
30ca5c053a | ||
|
|
c36419f5b6 | ||
|
|
1879895db2 | ||
|
|
c964e3c8ec | ||
|
|
2ef6d18f82 | ||
|
|
10b672572b | ||
|
|
2a4efa8cd2 | ||
|
|
5a8435da29 | ||
|
|
3461349a96 | ||
|
|
3791e9df12 | ||
|
|
b3c36aac93 | ||
|
|
c0895409bc | ||
|
|
438b565cbb | ||
|
|
cf1a82e89b | ||
|
|
0506e361b2 | ||
|
|
d6a1357a82 | ||
|
|
a5a1dbd51b | ||
|
|
5201350e44 | ||
|
|
6a84b9d5ae |
No files matched your search
@@ -50,6 +50,7 @@ env:
|
|||||||
PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }}
|
PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }}
|
||||||
PUBLIC_NITROPAY_ID: ${{ secrets.PUBLIC_NITROPAY_ID }}
|
PUBLIC_NITROPAY_ID: ${{ secrets.PUBLIC_NITROPAY_ID }}
|
||||||
PUBLIC_DEBUG_MISSING_I18N: false
|
PUBLIC_DEBUG_MISSING_I18N: false
|
||||||
|
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
download_client:
|
download_client:
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ env:
|
|||||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||||
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
|
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
|
||||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||||
ALLOW_ROBOTS: true
|
ALLOW_ROBOTS: "true"
|
||||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||||
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||||
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
||||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||||
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
|
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
|
||||||
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
|
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
|
||||||
|
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -27,3 +27,5 @@ target
|
|||||||
/.vercel
|
/.vercel
|
||||||
/.astro
|
/.astro
|
||||||
.vercel
|
.vercel
|
||||||
|
/src
|
||||||
|
/packages/bot/src/prisma
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
[submodule "submodules/blitzkit-closed"]
|
[submodule "submodules/blitzkit-closed"]
|
||||||
path = submodules/blitzkit-closed
|
path = submodules/blitzkit-closed
|
||||||
url = https://github.com/tresabhi/blitzkit-closed
|
url = https://github.com/tresabhi/blitzkit-closed
|
||||||
|
[submodule "submodules/CUE4Parse"]
|
||||||
|
path = submodules/CUE4Parse
|
||||||
|
url = https://github.com/FabianFG/CUE4Parse
|
||||||
|
[submodule "submodules/UAssetAPI"]
|
||||||
|
path = submodules/UAssetAPI
|
||||||
|
url = https://github.com/atenfyr/UAssetAPI
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"applypatches",
|
"applypatches",
|
||||||
"artefacts",
|
"artefacts",
|
||||||
"astrojs",
|
"astrojs",
|
||||||
|
"astrolib",
|
||||||
"atgm",
|
"atgm",
|
||||||
"Autoloader",
|
"Autoloader",
|
||||||
"Autoreloader",
|
"Autoreloader",
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
"cacheable",
|
"cacheable",
|
||||||
"Callouts",
|
"Callouts",
|
||||||
"Camo",
|
"Camo",
|
||||||
|
"Carro",
|
||||||
"CCPAOPTOUT",
|
"CCPAOPTOUT",
|
||||||
"CDON",
|
"CDON",
|
||||||
"classname",
|
"classname",
|
||||||
@@ -42,6 +44,7 @@
|
|||||||
"closedkrieg",
|
"closedkrieg",
|
||||||
"cockroackdb",
|
"cockroackdb",
|
||||||
"Coef",
|
"Coef",
|
||||||
|
"Combattimento",
|
||||||
"cooldown",
|
"cooldown",
|
||||||
"corelations",
|
"corelations",
|
||||||
"corsproxy",
|
"corsproxy",
|
||||||
@@ -53,6 +56,7 @@
|
|||||||
"dbid",
|
"dbid",
|
||||||
"Ddpf",
|
"Ddpf",
|
||||||
"Ddsd",
|
"Ddsd",
|
||||||
|
"deadrail",
|
||||||
"debugflags",
|
"debugflags",
|
||||||
"deburr",
|
"deburr",
|
||||||
"decalmask",
|
"decalmask",
|
||||||
@@ -130,9 +134,12 @@
|
|||||||
"outdir",
|
"outdir",
|
||||||
"outfile",
|
"outfile",
|
||||||
"ownedtanks",
|
"ownedtanks",
|
||||||
|
"padder",
|
||||||
|
"partytown",
|
||||||
"PIXELFORMAT",
|
"PIXELFORMAT",
|
||||||
"playerachievements",
|
"playerachievements",
|
||||||
"playerinfo",
|
"playerinfo",
|
||||||
|
"playform",
|
||||||
"pngjs",
|
"pngjs",
|
||||||
"POSITIVEX",
|
"POSITIVEX",
|
||||||
"POSITIVEY",
|
"POSITIVEY",
|
||||||
@@ -181,6 +188,7 @@
|
|||||||
"TEXCOORD",
|
"TEXCOORD",
|
||||||
"tresabhi",
|
"tresabhi",
|
||||||
"tungstentip",
|
"tungstentip",
|
||||||
|
"Turretless",
|
||||||
"TYPELESS",
|
"TYPELESS",
|
||||||
"ubjson",
|
"ubjson",
|
||||||
"Unicum",
|
"Unicum",
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# BlitzKit 1.27.10
|
||||||
|
|
||||||
|
This update changes the URLs across the whole website, removing the "tools" prefix and simplifying other aspects too.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Removed "full" tank names
|
||||||
|
- For example, tanks like the "Carro 45t" had full names like "Carro Combattimento 45t" which remained pretty much unused, so they have been removed
|
||||||
|
- Reduced the shadow below the navigation bar
|
||||||
|
- Removed the "tools" prefix from the URLs; here are the paths:
|
||||||
|
- `/tools/tankopedia` -> `/tanks`
|
||||||
|
- `/tools/tankopedia/12345` -> `/tanks/tank-name`
|
||||||
|
- `/tools/compare` -> `/compare`
|
||||||
|
- `/tools/embed` -> `/embed`
|
||||||
|
- `/tools/embed/embed-name` -> `/embed/embed-name`
|
||||||
|
- `/tools/gallery` -> `/gallery`
|
||||||
|
- `/tools/performance` -> `/performance`
|
||||||
|
- `/tools/session` -> `/session`
|
||||||
|
- `/tools/tier-list` -> `/tier-list`
|
||||||
|
|
||||||
|
## Technical Features
|
||||||
|
|
||||||
|
- The asset mangler now uses http headers and even more heuristics to minimize re-creating unchanged assets
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# BlitzKit 1.27.11
|
||||||
|
|
||||||
|
This update affects both the bot and the website.
|
||||||
|
|
||||||
|
## Technical Changes
|
||||||
|
|
||||||
|
- Added a proper CDN domain to avoid error 429s and any slowdowns
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# BlitzKit 1.27.2
|
||||||
|
|
||||||
|
This update only affects the website.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- The new Improved Vertical Stabilizer equipment which only affects the gun depression found on the Type 63 now works
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# BlitzKit 1.27.3
|
||||||
|
|
||||||
|
This update only affects the bot.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- Added support for 64-bit IDs allowing users with new accounts to link with BlitzKit
|
||||||
|
- This comes at the added cost of resetting the links of all other accounts
|
||||||
|
- Just re-link and you're good to go
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# FreakKit 1.27.4
|
||||||
|
|
||||||
|
It has come to our attention that tanks are very harsh objects. To even it out, we made the website a pretty pretty princess <3
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# BlitzKit 1.27.5
|
||||||
|
|
||||||
|
This update only affects the bot.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Removed `/auction`
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- Fixed a bug that caused `/rating` to disappear
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# BlitzKit 1.27.6
|
||||||
|
|
||||||
|
This update only affects the website.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Added a promotional card for [the official Discord server](https://discord.gg/nDt7AjGJQH) to the homepage
|
||||||
|
- Added a better navbar
|
||||||
|
- The added spaces and padding helps add more room for error when clicking, especially on mobile
|
||||||
|
- The dropdown menu is now a lost smaller, disposing of the need to scroll at all
|
||||||
|
- Added shell filters to the tank search
|
||||||
|
- Reworked the filter buttons and their positions to allow for more space
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- Moved most images from imgur to BlitzKit allowing for users in certain countries to load BlitzKit without getting blocked
|
||||||
|
|
||||||
|
## Technical Features
|
||||||
|
|
||||||
|
- Added Partytown to offload some workload off the main thread, allowing for a more responsive experience
|
||||||
|
- Reworked `robots.txt` to disallow indexing of BlitzKit variants
|
||||||
|
- Improved the SEO further for most pages
|
||||||
|
- Added a few compressor library to speed up load times
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# BlitzKit 1.27.7
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
This update only affects the website.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Added a new foggy-wireframe transition between tabs to smooth out the jarring jumps
|
||||||
|
- Added animations pop-in and out animations on a few elements
|
||||||
|
- The tank name title now shrinks in size as soon as a disturbance is caused
|
||||||
|
- The shell selector, antagonist switcher, and thicknesses range now ease into frame
|
||||||
|
- The Tankopedia search page now loads as a skeleton by default before populating with tanks, allowing for the recently viewed tanks to load without cumulative shift
|
||||||
|
- Something similar was done to the Gallery tool
|
||||||
|
- Gave the meta section of the website some zest with nation-based backdrops
|
||||||
|
- The compare tabs in the Tankopedia are now centered
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- The camera no longer snaps to the initial position when switching between tabs
|
||||||
|
|
||||||
|
## Technical Changes
|
||||||
|
|
||||||
|
- Backported some technologies from BlitzKit Reforged; you will not notice any differences in usage
|
||||||
|
- The popular tanks and gallery indexings have been moved to the asynchronous cron job
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# BlitzKit 1.27.8
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- The website now uses Inter as the font
|
||||||
|
- Added a cool new non-disturbed state for the Tankopedia because why not
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Removed the Discord plug and language changer from the homepage
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# BlitzKit 1.27.9
|
||||||
|
|
||||||
|
BlitzKit's great, but there's far too many numbers. This update adds a few visualizers to help new players understand how things relate numerically.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Added the turret and hull traverse speed visualizer
|
||||||
|
- Added the shell ricochet and normalization angles visualizer
|
||||||
|
- Added the view-range visualizer
|
||||||
|
- Added a search bar to the Tankopedia characteristics
|
||||||
|
- Bumped the decimal places for vehicle dimensions and volume
|
||||||
|
- Made tank ranks softer (the bars beneath the tank characteristics)
|
||||||
|
- Gave the navigation bar better distinction from the rest of the page
|
||||||
|
- Increased the number of characteristics rows to 3 on select devices
|
||||||
|
- Switched to a more contrasting classic style of components across the website (it's subtle, but beautiful)
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Removed units
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
|
||||||
|
- Clarified a few confusing row labels in the Compare tool
|
||||||
|
- Made the Tankopedia hero section dynamically smaller on mobile phones to allow scrolling
|
||||||
|
- Tank ranks no longer reach 0 or go above the number of vehicles in the comparison
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "blitzkit",
|
"name": "blitzkit",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.27.1",
|
"version": "1.27.11",
|
||||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "./types.d.ts",
|
"types": "./types.d.ts",
|
||||||
@@ -40,15 +40,17 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"sharp": "^0.33.5",
|
"googleapis": "^144.0.0",
|
||||||
"lodash-es": "^4.17.21"
|
"lodash-es": "^4.17.21",
|
||||||
|
"quicklime": "^1.2.0",
|
||||||
|
"sharp": "^0.33.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^22.5.4",
|
"@types/node": "^22.14.1",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.5.3",
|
||||||
"prisma": "^6.0.0",
|
"prisma": "^6.6.0",
|
||||||
"typescript": "^5.5.4"
|
"typescript": "^5.8.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,8 +107,8 @@ export const auctionCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
if (!tank) return '';
|
if (!tank) return '';
|
||||||
|
|
||||||
const name = `[${tank.name}](<https://blitzkit.app/tools/tankopedia/${
|
const name = `[${tank.name}](<https://blitzkit.app/tanks/${
|
||||||
tank.id
|
tank.slug
|
||||||
}>)`;
|
}>)`;
|
||||||
const next =
|
const next =
|
||||||
data.next_price === null
|
data.next_price === null
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const debugCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
const list = [
|
const list = [
|
||||||
[strings.bot.commands.debug.body.version, packageJSON.version],
|
[strings.bot.commands.debug.body.version, packageJSON.version],
|
||||||
[
|
[
|
||||||
strings.bot.commands.debug.body.shard,
|
strings.bot.commands.debug.body.shard.label,
|
||||||
client.shard?.ids[0] ?? strings.bot.commands.debug.body.shard.default,
|
client.shard?.ids[0] ?? strings.bot.commands.debug.body.shard.default,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import {
|
import { getAccountInfo, getClanAccountInfo } from '@blitzkit/core';
|
||||||
getAccountInfo,
|
|
||||||
getClanAccountInfo,
|
|
||||||
linkBlitzAndDiscord,
|
|
||||||
} from '@blitzkit/core';
|
|
||||||
import { teal } from '@radix-ui/colors';
|
import { teal } from '@radix-ui/colors';
|
||||||
import { Glow } from '../components/AllStatsOverview/components/HeroStat/components/Glow';
|
import { Glow } from '../components/AllStatsOverview/components/HeroStat/components/Glow';
|
||||||
import { CommandWrapper } from '../components/CommandWrapper';
|
import { CommandWrapper } from '../components/CommandWrapper';
|
||||||
|
import { linkBlitzAndDiscord } from '../core/db/linkBlitzAndDiscord';
|
||||||
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
||||||
import { autocompleteUsername } from '../core/discord/autocompleteUsername';
|
import { autocompleteUsername } from '../core/discord/autocompleteUsername';
|
||||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
)
|
)
|
||||||
.map(
|
.map(
|
||||||
(tank) =>
|
(tank) =>
|
||||||
`[${flags[tank.nation]} ${markdownEscape(unwrap(tank.name))}](<https://blitzkit.app/tools/tankopedia/${tank.id}>)`,
|
`[${flags[tank.nation]} ${markdownEscape(unwrap(tank.name))}](<https://blitzkit.app/tanks/${tank.slug}>)`,
|
||||||
);
|
);
|
||||||
const lines = [title, ...tankList];
|
const lines = [title, ...tankList];
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export function CommandWrapper({ children, fat }: CommandWrapperProps) {
|
|||||||
gap: 16,
|
gap: 16,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
|
backgroundColor: theme.colors.appBackground1,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
background: theme.colors.appBackground1,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ export async function filtersToDescription(
|
|||||||
{ nation, tier, type, class: tankClass, tank }: StatFilters,
|
{ nation, tier, type, class: tankClass, tank }: StatFilters,
|
||||||
locale: Locale,
|
locale: Locale,
|
||||||
) {
|
) {
|
||||||
const { strings } = translator(locale);
|
const { strings, bkLocale } = translator(locale);
|
||||||
const awaitedTankDefinitions = await fetchTankDefinitions();
|
const awaitedTankDefinitions = await fetchTankDefinitions();
|
||||||
const info: string[] = [];
|
const info: string[] = [];
|
||||||
|
|
||||||
// TODO: somehow localize tank names?
|
// TODO: somehow localize tank names?
|
||||||
if (tank) return awaitedTankDefinitions.tanks[tank].name;
|
if (tank) return awaitedTankDefinitions.tanks[tank].name.locales[bkLocale];
|
||||||
if (nation)
|
if (nation)
|
||||||
info.push((strings.common.nations as Record<string, string>)[nation]);
|
info.push((strings.common.nations as Record<string, string>)[nation]);
|
||||||
if (tier) {
|
if (tier) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { idToRegion, usersDatabase } from '@blitzkit/core';
|
import { idToRegion } from '@blitzkit/core';
|
||||||
import { flagUserActivity } from '@blitzkit/core/src/blitzkit/flagUserActivity';
|
import { flagUserActivity } from '../db/flagUserActivity';
|
||||||
|
import { usersDatabase } from '../db/users';
|
||||||
|
|
||||||
export async function getBlitzFromDiscord(discordId: bigint) {
|
export async function getBlitzFromDiscord(discordId: bigint) {
|
||||||
const unique = await usersDatabase.discordUser.findUnique({
|
const unique = await usersDatabase.discordUser.findUnique({
|
||||||
@@ -8,6 +9,8 @@ export async function getBlitzFromDiscord(discordId: bigint) {
|
|||||||
|
|
||||||
if (unique === null) return null;
|
if (unique === null) return null;
|
||||||
|
|
||||||
await flagUserActivity(unique.blitz_id);
|
const id = Number(unique.blitz_id);
|
||||||
return { id: unique.blitz_id, region: idToRegion(unique.blitz_id) };
|
|
||||||
|
await flagUserActivity(id);
|
||||||
|
return { id, region: idToRegion(id) };
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { usersDatabase } from '@blitzkit/core';
|
import { usersDatabase } from '../db/users';
|
||||||
import { client } from '../discord/client';
|
import { client } from '../discord/client';
|
||||||
|
|
||||||
const EXIT_EVENTS = [
|
const EXIT_EVENTS = [
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { usersDatabase } from '@blitzkit/core';
|
import { usersDatabase } from './users';
|
||||||
|
|
||||||
export async function flagUserActivity(blitzId: number) {
|
export async function flagUserActivity(blitzId: number) {
|
||||||
await usersDatabase.user.upsert({
|
await usersDatabase.user.upsert({
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { usersDatabase } from '@blitzkit/core';
|
|
||||||
import { flagUserActivity } from './flagUserActivity';
|
import { flagUserActivity } from './flagUserActivity';
|
||||||
|
import { usersDatabase } from './users';
|
||||||
|
|
||||||
export async function linkBlitzAndDiscord(discordId: bigint, blitzId: number) {
|
export async function linkBlitzAndDiscord(discordId: bigint, blitzId: number) {
|
||||||
await usersDatabase.discordUser.upsert({
|
await usersDatabase.discordUser.upsert({
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '../../prisma';
|
||||||
|
|
||||||
export const usersDatabase = new PrismaClient();
|
export const usersDatabase = new PrismaClient();
|
||||||
@@ -3,7 +3,8 @@ import { Locale } from 'discord.js';
|
|||||||
import { SUPPORTED_LOCALES_DISCORD } from '../localization/strings/constants';
|
import { SUPPORTED_LOCALES_DISCORD } from '../localization/strings/constants';
|
||||||
import { translator } from '../localization/translator';
|
import { translator } from '../localization/translator';
|
||||||
|
|
||||||
const validNameRegex = /^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u;
|
const validNameRegex =
|
||||||
|
/^[\p{Ll}\p{Lm}\p{Lo}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+$/u;
|
||||||
|
|
||||||
export function localizationObject(
|
export function localizationObject(
|
||||||
path: (strings: BlitzKitStrings) => string,
|
path: (strings: BlitzKitStrings) => string,
|
||||||
@@ -14,7 +15,8 @@ export function localizationObject(
|
|||||||
(localizations, locale) => {
|
(localizations, locale) => {
|
||||||
const { strings } = translator(locale);
|
const { strings } = translator(locale);
|
||||||
const translation = literalsFunc(path(strings), literals);
|
const translation = literalsFunc(path(strings), literals);
|
||||||
const isValid = !strict || validNameRegex.test(translation);
|
const isValid =
|
||||||
|
(!strict || validNameRegex.test(translation)) && translation.length > 0;
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ export function translator(localeRaw: Locale) {
|
|||||||
SUPPORTED_LOCALES_DISCORD.includes(localeRaw)
|
SUPPORTED_LOCALES_DISCORD.includes(localeRaw)
|
||||||
? localeRaw
|
? localeRaw
|
||||||
: DEFAULT_LOCALE_DISCORD;
|
: DEFAULT_LOCALE_DISCORD;
|
||||||
const strings = STRINGS[SUPPORTED_LOCALES_DISCORD_MAP_INVERSE[locale]];
|
const bkLocale = SUPPORTED_LOCALES_DISCORD_MAP_INVERSE[locale];
|
||||||
const unwrap = unwrapper(SUPPORTED_LOCALES_DISCORD_MAP_INVERSE[locale]);
|
const strings = STRINGS[bkLocale];
|
||||||
|
const unwrap = unwrapper(bkLocale);
|
||||||
|
|
||||||
return { locale, strings, unwrap };
|
return { locale, strings, unwrap, bkLocale };
|
||||||
}
|
}
|
||||||
@@ -19,11 +19,11 @@ import { Writeable } from '../../../types/writable';
|
|||||||
export async function handleChatInputCommand(
|
export async function handleChatInputCommand(
|
||||||
interaction: ChatInputCommandInteraction<CacheType>,
|
interaction: ChatInputCommandInteraction<CacheType>,
|
||||||
) {
|
) {
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
const awaitedCommands = await commands;
|
const awaitedCommands = await commands;
|
||||||
const registry = awaitedCommands[interaction.commandName];
|
const registry = awaitedCommands[interaction.commandName];
|
||||||
|
|
||||||
await interaction.deferReply();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const returnable = await registry.handler(interaction);
|
const returnable = await registry.handler(interaction);
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
SlashCommandSubcommandsOnlyBuilder,
|
SlashCommandSubcommandsOnlyBuilder,
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import { aboutCommand } from '../../commands/about';
|
import { aboutCommand } from '../../commands/about';
|
||||||
import { auctionCommand } from '../../commands/auction';
|
|
||||||
import { breakdownCommand } from '../../commands/breakdown';
|
import { breakdownCommand } from '../../commands/breakdown';
|
||||||
import { debugCommand } from '../../commands/debug';
|
import { debugCommand } from '../../commands/debug';
|
||||||
import { evolutionCommand } from '../../commands/evolution';
|
import { evolutionCommand } from '../../commands/evolution';
|
||||||
@@ -80,7 +79,7 @@ interface CommandRegistryExplicit extends CommandRegistryBase {
|
|||||||
export type CommandRegistry = CommandRegistryImplicit | CommandRegistryExplicit;
|
export type CommandRegistry = CommandRegistryImplicit | CommandRegistryExplicit;
|
||||||
|
|
||||||
export const COMMANDS_RAW: Promise<CommandRegistry>[] = [
|
export const COMMANDS_RAW: Promise<CommandRegistry>[] = [
|
||||||
auctionCommand,
|
// auctionCommand,
|
||||||
permissionsCommand,
|
permissionsCommand,
|
||||||
debugCommand,
|
debugCommand,
|
||||||
aboutCommand,
|
aboutCommand,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const interval = setInterval(async () => {
|
|||||||
const usernames = awaitedDiscoveredIdsDefinitions.count;
|
const usernames = awaitedDiscoveredIdsDefinitions.count;
|
||||||
|
|
||||||
manager.broadcastEval(
|
manager.broadcastEval(
|
||||||
(client, { servers, users, channels, tanks, usernames }) => {
|
async (client, { servers, users, channels, tanks, usernames }) => {
|
||||||
const bios = [
|
const bios = [
|
||||||
`Living in ${servers.toLocaleString()} servers`,
|
`Living in ${servers.toLocaleString()} servers`,
|
||||||
`Serving ${users.toLocaleString()} users`,
|
`Serving ${users.toLocaleString()} users`,
|
||||||
@@ -62,6 +62,13 @@ const interval = setInterval(async () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for (const guild of client.guilds.cache.values()) {
|
||||||
|
try {
|
||||||
|
const botMember = await guild.members.fetch(client.user.id);
|
||||||
|
await botMember.setNickname('BlitzKit');
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ context: { servers, channels, users, tanks, usernames } },
|
{ context: { servers, channels, users, tanks, usernames } },
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const theme = {
|
|||||||
spaces: createSpaces(),
|
spaces: createSpaces(),
|
||||||
|
|
||||||
colors: {
|
colors: {
|
||||||
...createColors(slateDark),
|
...createColors(mauveDark),
|
||||||
...createColors(slateDarkA, 'alpha'),
|
...createColors(slateDarkA, 'alpha'),
|
||||||
...createColors(slate, 'light'),
|
...createColors(slate, 'light'),
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/plugin-throttling": "^8.2.0",
|
"@octokit/plugin-throttling": "^8.2.0",
|
||||||
"@octokit/rest": "^20.1.1",
|
"@octokit/rest": "^20.1.1",
|
||||||
"@prisma/client": "^5.19.1",
|
"@prisma/client": "^6.6.0",
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"crc": "^4.3.2",
|
"crc": "^4.3.2",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { assertSecret, type BlitzGlossary } from '@blitzkit/core';
|
import { assertSecret } from '../blitzkit';
|
||||||
|
import { BlitzGlossary } from '../types';
|
||||||
|
|
||||||
export async function fetchGlossary(locale: string) {
|
export async function fetchGlossary(locale: string) {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
@@ -4,6 +4,7 @@ export * from './classes';
|
|||||||
export * from './constants';
|
export * from './constants';
|
||||||
export * from './equipmentPrice';
|
export * from './equipmentPrice';
|
||||||
export * from './fetchBlitz';
|
export * from './fetchBlitz';
|
||||||
|
export * from './fetchGlossary';
|
||||||
export * from './generateStats';
|
export * from './generateStats';
|
||||||
export * from './getAccountAchievements';
|
export * from './getAccountAchievements';
|
||||||
export * from './getAccountInfo';
|
export * from './getAccountInfo';
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { assertSecret } from './assertSecret';
|
import { assertSecret } from './assertSecret';
|
||||||
|
|
||||||
|
const repo = assertSecret(import.meta.env.PUBLIC_ASSET_REPO);
|
||||||
|
const branch = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH);
|
||||||
|
const fastUser = assertSecret(import.meta.env.PUBLIC_ASSET_FAST_USER);
|
||||||
|
const fastRepo = assertSecret(import.meta.env.PUBLIC_ASSET_FAST_REPO);
|
||||||
|
|
||||||
export function asset(path: string) {
|
export function asset(path: string) {
|
||||||
return `https://raw.githubusercontent.com/${assertSecret(
|
return branch === 'main'
|
||||||
import.meta.env.PUBLIC_ASSET_REPO,
|
? `https://${fastUser}.github.io/${fastRepo}/${path}`
|
||||||
)}/${assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH)}/${path}`;
|
: `https://raw.githubusercontent.com/${repo}/${branch}/${path}`;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { fetchPB } from '../protobuf';
|
||||||
|
import { Gallery } from '../protos';
|
||||||
|
import { asset } from './asset';
|
||||||
|
|
||||||
|
export function fetchGallery() {
|
||||||
|
return fetchPB(asset('definitions/gallery.pb'), Gallery);
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ export * from './discoveredIdDefinitions';
|
|||||||
export * from './emblemURL';
|
export * from './emblemURL';
|
||||||
export * from './equipmentDefinitions';
|
export * from './equipmentDefinitions';
|
||||||
export * from './eventManager';
|
export * from './eventManager';
|
||||||
// export * from './flagUserActivity';
|
|
||||||
export * from './gameDefinitions';
|
export * from './gameDefinitions';
|
||||||
export * from './getArchivedLatestSeasonNumber';
|
export * from './getArchivedLatestSeasonNumber';
|
||||||
// export * from './getArchivedRatingInfo';
|
// export * from './getArchivedRatingInfo';
|
||||||
@@ -21,7 +20,6 @@ export * from './getArchivedRatingLeaderboard';
|
|||||||
// export * from './getArchivedRatingMidnightLeaderboard';
|
// export * from './getArchivedRatingMidnightLeaderboard';
|
||||||
export * from './getTimeDaysAgo';
|
export * from './getTimeDaysAgo';
|
||||||
export * from './isExplosive';
|
export * from './isExplosive';
|
||||||
export * from './linkBlitzAndDiscord';
|
|
||||||
export * from './mapDefinitions';
|
export * from './mapDefinitions';
|
||||||
export * from './modelDefinitions';
|
export * from './modelDefinitions';
|
||||||
export * from './normalizeBoundingBox';
|
export * from './normalizeBoundingBox';
|
||||||
@@ -44,6 +42,8 @@ export * from './unwrapBlitzkitResponse';
|
|||||||
export * from './votes';
|
export * from './votes';
|
||||||
// export * from './withCORSProxy';
|
// export * from './withCORSProxy';
|
||||||
export * from './createDefaultProvisions';
|
export * from './createDefaultProvisions';
|
||||||
|
export * from './gallery';
|
||||||
export * from './metaSortTank';
|
export * from './metaSortTank';
|
||||||
|
export * from './popularTanks';
|
||||||
export * from './romanize';
|
export * from './romanize';
|
||||||
export * from './youtubers';
|
export * from './youtubers';
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { fetchPB } from '../protobuf';
|
||||||
|
import { PopularTanks } from '../protos';
|
||||||
|
import { asset } from './asset';
|
||||||
|
|
||||||
|
export function fetchPopularTanks() {
|
||||||
|
return fetchPB(asset('definitions/popular-tanks.pb'), PopularTanks);
|
||||||
|
}
|
||||||
@@ -15,19 +15,25 @@ export async function fetchTankNames() {
|
|||||||
tankDefinitionsArray.map(async (tank) => {
|
tankDefinitionsArray.map(async (tank) => {
|
||||||
const searchableNameDeburr: I18nString = { locales: {} };
|
const searchableNameDeburr: I18nString = { locales: {} };
|
||||||
|
|
||||||
Object.entries(tank.name_full ?? tank.name).forEach(([key, value]) => {
|
Object.entries(tank.name).forEach(([key, value]) => {
|
||||||
searchableNameDeburr.locales[key] = deburr(value);
|
searchableNameDeburr.locales[key] = deburr(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: tank.id,
|
id: tank.id,
|
||||||
name: tank.name,
|
name: tank.name,
|
||||||
nameFull: tank.name_full,
|
searchableName: tank.name,
|
||||||
searchableName: tank.name_full ?? tank.name,
|
|
||||||
searchableNameDeburr,
|
searchableNameDeburr,
|
||||||
camouflages: tank.camouflages
|
camouflages: tank.camouflages
|
||||||
?.map((id) => camouflageDefinitions.camouflages[id]?.name)
|
?.map((id) =>
|
||||||
|
SUPPORTED_LOCALES.map(
|
||||||
|
(locale) =>
|
||||||
|
camouflageDefinitions.camouflages[id]?.name.locales[locale],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.flat()
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
.map(deburr)
|
||||||
.join(' '),
|
.join(' '),
|
||||||
treeType: tank.type,
|
treeType: tank.type,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export * from './users';
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
export * from './blitz';
|
export * from './blitz';
|
||||||
export * from './blitzkit';
|
export * from './blitzkit';
|
||||||
export * from './databases';
|
|
||||||
export * from './imgur';
|
export * from './imgur';
|
||||||
export * from './math';
|
export * from './math';
|
||||||
export * from './patreon';
|
export * from './patreon';
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
syntax = "proto2";
|
||||||
|
|
||||||
|
package blitzkit;
|
||||||
|
|
||||||
|
import "packages/core/src/protos/i18n.proto";
|
||||||
|
|
||||||
|
message Gallery {
|
||||||
|
repeated Avatar avatars = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Avatar {
|
||||||
|
required string id = 1;
|
||||||
|
required I18nString name = 2;
|
||||||
|
required string extension = 3;
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-ts_proto v2.2.2
|
||||||
|
// protoc v5.28.2
|
||||||
|
// source: packages/core/src/protos/gallery.proto
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
import { BinaryReader, BinaryWriter } from '@bufbuild/protobuf/wire';
|
||||||
|
import { I18nString, createBaseI18nString } from './i18n';
|
||||||
|
|
||||||
|
export const protobufPackage = 'blitzkit';
|
||||||
|
|
||||||
|
export interface Gallery {
|
||||||
|
avatars: Avatar[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Avatar {
|
||||||
|
id: string;
|
||||||
|
name: I18nString;
|
||||||
|
extension: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBaseGallery(): Gallery {
|
||||||
|
return { avatars: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Gallery: MessageFns<Gallery> = {
|
||||||
|
encode(
|
||||||
|
message: Gallery,
|
||||||
|
writer: BinaryWriter = new BinaryWriter(),
|
||||||
|
): BinaryWriter {
|
||||||
|
for (const v of message.avatars) {
|
||||||
|
Avatar.encode(v!, writer.uint32(10).fork()).join();
|
||||||
|
}
|
||||||
|
return writer;
|
||||||
|
},
|
||||||
|
|
||||||
|
decode(input: BinaryReader | Uint8Array, length?: number): Gallery {
|
||||||
|
const reader =
|
||||||
|
input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||||
|
let end = length === undefined ? reader.len : reader.pos + length;
|
||||||
|
const message = createBaseGallery();
|
||||||
|
while (reader.pos < end) {
|
||||||
|
const tag = reader.uint32();
|
||||||
|
switch (tag >>> 3) {
|
||||||
|
case 1: {
|
||||||
|
if (tag !== 10) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.avatars.push(Avatar.decode(reader, reader.uint32()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((tag & 7) === 4 || tag === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reader.skip(tag & 7);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
|
||||||
|
fromJSON(object: any): Gallery {
|
||||||
|
return {
|
||||||
|
avatars: globalThis.Array.isArray(object?.avatars)
|
||||||
|
? object.avatars.map((e: any) => Avatar.fromJSON(e))
|
||||||
|
: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
toJSON(message: Gallery): unknown {
|
||||||
|
const obj: any = {};
|
||||||
|
if (message.avatars?.length) {
|
||||||
|
obj.avatars = message.avatars.map((e) => Avatar.toJSON(e));
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
},
|
||||||
|
|
||||||
|
create<I extends Exact<DeepPartial<Gallery>, I>>(base?: I): Gallery {
|
||||||
|
return Gallery.fromPartial(base ?? ({} as any));
|
||||||
|
},
|
||||||
|
fromPartial<I extends Exact<DeepPartial<Gallery>, I>>(object: I): Gallery {
|
||||||
|
const message = createBaseGallery();
|
||||||
|
message.avatars = object.avatars?.map((e) => Avatar.fromPartial(e)) || [];
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function createBaseAvatar(): Avatar {
|
||||||
|
return { id: '', name: createBaseI18nString(), extension: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Avatar: MessageFns<Avatar> = {
|
||||||
|
encode(
|
||||||
|
message: Avatar,
|
||||||
|
writer: BinaryWriter = new BinaryWriter(),
|
||||||
|
): BinaryWriter {
|
||||||
|
if (message.id !== '') {
|
||||||
|
writer.uint32(10).string(message.id);
|
||||||
|
}
|
||||||
|
if (message.name !== undefined) {
|
||||||
|
I18nString.encode(message.name, writer.uint32(18).fork()).join();
|
||||||
|
}
|
||||||
|
if (message.extension !== '') {
|
||||||
|
writer.uint32(26).string(message.extension);
|
||||||
|
}
|
||||||
|
return writer;
|
||||||
|
},
|
||||||
|
|
||||||
|
decode(input: BinaryReader | Uint8Array, length?: number): Avatar {
|
||||||
|
const reader =
|
||||||
|
input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||||
|
let end = length === undefined ? reader.len : reader.pos + length;
|
||||||
|
const message = createBaseAvatar();
|
||||||
|
while (reader.pos < end) {
|
||||||
|
const tag = reader.uint32();
|
||||||
|
switch (tag >>> 3) {
|
||||||
|
case 1: {
|
||||||
|
if (tag !== 10) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.id = reader.string();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
if (tag !== 18) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.name = I18nString.decode(reader, reader.uint32());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
if (tag !== 26) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.extension = reader.string();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((tag & 7) === 4 || tag === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reader.skip(tag & 7);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
|
||||||
|
fromJSON(object: any): Avatar {
|
||||||
|
return {
|
||||||
|
id: globalThis.String(assertSet('Avatar.id', object.id)),
|
||||||
|
name: I18nString.fromJSON(assertSet('Avatar.name', object.name)),
|
||||||
|
extension: globalThis.String(
|
||||||
|
assertSet('Avatar.extension', object.extension),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
toJSON(message: Avatar): unknown {
|
||||||
|
const obj: any = {};
|
||||||
|
if (message.id !== '') {
|
||||||
|
obj.id = message.id;
|
||||||
|
}
|
||||||
|
if (message.name !== undefined) {
|
||||||
|
obj.name = I18nString.toJSON(message.name);
|
||||||
|
}
|
||||||
|
if (message.extension !== '') {
|
||||||
|
obj.extension = message.extension;
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
},
|
||||||
|
|
||||||
|
create<I extends Exact<DeepPartial<Avatar>, I>>(base?: I): Avatar {
|
||||||
|
return Avatar.fromPartial(base ?? ({} as any));
|
||||||
|
},
|
||||||
|
fromPartial<I extends Exact<DeepPartial<Avatar>, I>>(object: I): Avatar {
|
||||||
|
const message = createBaseAvatar();
|
||||||
|
message.id = object.id ?? '';
|
||||||
|
message.name =
|
||||||
|
object.name !== undefined && object.name !== null
|
||||||
|
? I18nString.fromPartial(object.name)
|
||||||
|
: createBaseI18nString();
|
||||||
|
message.extension = object.extension ?? '';
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type Builtin =
|
||||||
|
| Date
|
||||||
|
| Function
|
||||||
|
| Uint8Array
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
export type DeepPartial<T> = T extends Builtin
|
||||||
|
? T
|
||||||
|
: T extends globalThis.Array<infer U>
|
||||||
|
? globalThis.Array<DeepPartial<U>>
|
||||||
|
: T extends ReadonlyArray<infer U>
|
||||||
|
? ReadonlyArray<DeepPartial<U>>
|
||||||
|
: T extends { $case: string; value: unknown }
|
||||||
|
? { $case: T['$case']; value?: DeepPartial<T['value']> }
|
||||||
|
: T extends {}
|
||||||
|
? { [K in keyof T]?: DeepPartial<T[K]> }
|
||||||
|
: Partial<T>;
|
||||||
|
|
||||||
|
type KeysOfUnion<T> = T extends T ? keyof T : never;
|
||||||
|
export type Exact<P, I extends P> = P extends Builtin
|
||||||
|
? P
|
||||||
|
: P & { [K in keyof P]: Exact<P[K], I[K]> } & {
|
||||||
|
[K in Exclude<keyof I, KeysOfUnion<P>>]: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isSet(value: any): boolean {
|
||||||
|
return value !== null && value !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertSet<T>(field: string, value: T | undefined): T {
|
||||||
|
if (!isSet(value)) {
|
||||||
|
throw new TypeError(`Required field ${field} is not set`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageFns<T> {
|
||||||
|
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||||
|
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||||
|
fromJSON(object: any): T;
|
||||||
|
toJSON(message: T): unknown;
|
||||||
|
create<I extends Exact<DeepPartial<T>, I>>(base?: I): T;
|
||||||
|
fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T;
|
||||||
|
}
|
||||||
@@ -3,10 +3,12 @@ export * from './average_definitions.ts';
|
|||||||
export * from './camouflage_definitions.ts';
|
export * from './camouflage_definitions.ts';
|
||||||
export * from './consumable_definitions.ts';
|
export * from './consumable_definitions.ts';
|
||||||
export * from './equipment_definitions.ts';
|
export * from './equipment_definitions.ts';
|
||||||
|
export * from './gallery.ts';
|
||||||
export * from './game_definitions.ts';
|
export * from './game_definitions.ts';
|
||||||
export * from './i18n.ts';
|
export * from './i18n.ts';
|
||||||
export * from './map_definitions.ts';
|
export * from './map_definitions.ts';
|
||||||
export * from './model_definitions.ts';
|
export * from './model_definitions.ts';
|
||||||
|
export * from './popular_tanks.ts';
|
||||||
export * from './provision_definitions.ts';
|
export * from './provision_definitions.ts';
|
||||||
export * from './rating_leaderboard.ts';
|
export * from './rating_leaderboard.ts';
|
||||||
export * from './reviews.ts';
|
export * from './reviews.ts';
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
syntax = "proto2";
|
||||||
|
|
||||||
|
package blitzkit;
|
||||||
|
|
||||||
|
message PopularTanks {
|
||||||
|
repeated PopularTank tanks = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PopularTank {
|
||||||
|
required uint32 id = 1;
|
||||||
|
required uint32 views = 2; // views in the last 7 days
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
// Code generated by protoc-gen-ts_proto. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// protoc-gen-ts_proto v2.2.2
|
||||||
|
// protoc v5.28.2
|
||||||
|
// source: packages/core/src/protos/popular_tanks.proto
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
|
||||||
|
|
||||||
|
export const protobufPackage = "blitzkit";
|
||||||
|
|
||||||
|
export interface PopularTanks {
|
||||||
|
tanks: PopularTank[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PopularTank {
|
||||||
|
id: number;
|
||||||
|
/** views in the last 7 days */
|
||||||
|
views: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBasePopularTanks(): PopularTanks {
|
||||||
|
return { tanks: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PopularTanks: MessageFns<PopularTanks> = {
|
||||||
|
encode(message: PopularTanks, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||||
|
for (const v of message.tanks) {
|
||||||
|
PopularTank.encode(v!, writer.uint32(10).fork()).join();
|
||||||
|
}
|
||||||
|
return writer;
|
||||||
|
},
|
||||||
|
|
||||||
|
decode(input: BinaryReader | Uint8Array, length?: number): PopularTanks {
|
||||||
|
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||||
|
let end = length === undefined ? reader.len : reader.pos + length;
|
||||||
|
const message = createBasePopularTanks();
|
||||||
|
while (reader.pos < end) {
|
||||||
|
const tag = reader.uint32();
|
||||||
|
switch (tag >>> 3) {
|
||||||
|
case 1: {
|
||||||
|
if (tag !== 10) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.tanks.push(PopularTank.decode(reader, reader.uint32()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((tag & 7) === 4 || tag === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reader.skip(tag & 7);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
|
||||||
|
fromJSON(object: any): PopularTanks {
|
||||||
|
return {
|
||||||
|
tanks: globalThis.Array.isArray(object?.tanks) ? object.tanks.map((e: any) => PopularTank.fromJSON(e)) : [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
toJSON(message: PopularTanks): unknown {
|
||||||
|
const obj: any = {};
|
||||||
|
if (message.tanks?.length) {
|
||||||
|
obj.tanks = message.tanks.map((e) => PopularTank.toJSON(e));
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
},
|
||||||
|
|
||||||
|
create<I extends Exact<DeepPartial<PopularTanks>, I>>(base?: I): PopularTanks {
|
||||||
|
return PopularTanks.fromPartial(base ?? ({} as any));
|
||||||
|
},
|
||||||
|
fromPartial<I extends Exact<DeepPartial<PopularTanks>, I>>(object: I): PopularTanks {
|
||||||
|
const message = createBasePopularTanks();
|
||||||
|
message.tanks = object.tanks?.map((e) => PopularTank.fromPartial(e)) || [];
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function createBasePopularTank(): PopularTank {
|
||||||
|
return { id: 0, views: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PopularTank: MessageFns<PopularTank> = {
|
||||||
|
encode(message: PopularTank, writer: BinaryWriter = new BinaryWriter()): BinaryWriter {
|
||||||
|
if (message.id !== 0) {
|
||||||
|
writer.uint32(8).uint32(message.id);
|
||||||
|
}
|
||||||
|
if (message.views !== 0) {
|
||||||
|
writer.uint32(16).uint32(message.views);
|
||||||
|
}
|
||||||
|
return writer;
|
||||||
|
},
|
||||||
|
|
||||||
|
decode(input: BinaryReader | Uint8Array, length?: number): PopularTank {
|
||||||
|
const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
|
||||||
|
let end = length === undefined ? reader.len : reader.pos + length;
|
||||||
|
const message = createBasePopularTank();
|
||||||
|
while (reader.pos < end) {
|
||||||
|
const tag = reader.uint32();
|
||||||
|
switch (tag >>> 3) {
|
||||||
|
case 1: {
|
||||||
|
if (tag !== 8) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.id = reader.uint32();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
if (tag !== 16) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
message.views = reader.uint32();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((tag & 7) === 4 || tag === 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reader.skip(tag & 7);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
|
||||||
|
fromJSON(object: any): PopularTank {
|
||||||
|
return {
|
||||||
|
id: globalThis.Number(assertSet("PopularTank.id", object.id)),
|
||||||
|
views: globalThis.Number(assertSet("PopularTank.views", object.views)),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
toJSON(message: PopularTank): unknown {
|
||||||
|
const obj: any = {};
|
||||||
|
if (message.id !== 0) {
|
||||||
|
obj.id = Math.round(message.id);
|
||||||
|
}
|
||||||
|
if (message.views !== 0) {
|
||||||
|
obj.views = Math.round(message.views);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
},
|
||||||
|
|
||||||
|
create<I extends Exact<DeepPartial<PopularTank>, I>>(base?: I): PopularTank {
|
||||||
|
return PopularTank.fromPartial(base ?? ({} as any));
|
||||||
|
},
|
||||||
|
fromPartial<I extends Exact<DeepPartial<PopularTank>, I>>(object: I): PopularTank {
|
||||||
|
const message = createBasePopularTank();
|
||||||
|
message.id = object.id ?? 0;
|
||||||
|
message.views = object.views ?? 0;
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
|
||||||
|
|
||||||
|
export type DeepPartial<T> = T extends Builtin ? T
|
||||||
|
: T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>>
|
||||||
|
: T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>>
|
||||||
|
: T extends { $case: string; value: unknown } ? { $case: T["$case"]; value?: DeepPartial<T["value"]> }
|
||||||
|
: T extends {} ? { [K in keyof T]?: DeepPartial<T[K]> }
|
||||||
|
: Partial<T>;
|
||||||
|
|
||||||
|
type KeysOfUnion<T> = T extends T ? keyof T : never;
|
||||||
|
export type Exact<P, I extends P> = P extends Builtin ? P
|
||||||
|
: P & { [K in keyof P]: Exact<P[K], I[K]> } & { [K in Exclude<keyof I, KeysOfUnion<P>>]: never };
|
||||||
|
|
||||||
|
function isSet(value: any): boolean {
|
||||||
|
return value !== null && value !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertSet<T>(field: string, value: T | undefined): T {
|
||||||
|
if (!isSet(value)) {
|
||||||
|
throw new TypeError(`Required field ${field} is not set`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageFns<T> {
|
||||||
|
encode(message: T, writer?: BinaryWriter): BinaryWriter;
|
||||||
|
decode(input: BinaryReader | Uint8Array, length?: number): T;
|
||||||
|
fromJSON(object: any): T;
|
||||||
|
toJSON(message: T): unknown;
|
||||||
|
create<I extends Exact<DeepPartial<T>, I>>(base?: I): T;
|
||||||
|
fromPartial<I extends Exact<DeepPartial<T>, I>>(object: I): T;
|
||||||
|
}
|
||||||
@@ -10,16 +10,16 @@ message TankDefinitions {
|
|||||||
|
|
||||||
message TankDefinition {
|
message TankDefinition {
|
||||||
required uint32 id = 1;
|
required uint32 id = 1;
|
||||||
map<uint32, uint32> roles = 2;
|
required string slug = 2;
|
||||||
required bool fixed_camouflage = 4;
|
map<uint32, uint32> roles = 3;
|
||||||
repeated int32 camouflages = 5;
|
required bool fixed_camouflage = 5;
|
||||||
repeated int32 ancestors = 6;
|
repeated int32 camouflages = 6;
|
||||||
repeated int32 successors = 7;
|
repeated int32 ancestors = 7;
|
||||||
repeated Crew crew = 8;
|
repeated int32 successors = 8;
|
||||||
required uint32 health = 9;
|
repeated Crew crew = 9;
|
||||||
required string nation = 10;
|
required uint32 health = 10;
|
||||||
required I18nString name = 11;
|
required string nation = 11;
|
||||||
optional I18nString name_full = 12;
|
required I18nString name = 12;
|
||||||
required TankType type = 13;
|
required TankType type = 13;
|
||||||
required uint32 max_consumables = 14;
|
required uint32 max_consumables = 14;
|
||||||
required uint32 max_provisions = 15;
|
required uint32 max_provisions = 15;
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"default": "en",
|
||||||
|
"supported": [
|
||||||
|
{ "locale": "en" },
|
||||||
|
{ "locale": "es" },
|
||||||
|
{ "locale": "fr" },
|
||||||
|
{ "locale": "ja" },
|
||||||
|
{ "locale": "pl" },
|
||||||
|
{ "locale": "pt" },
|
||||||
|
{ "locale": "ru" },
|
||||||
|
{ "locale": "uk" },
|
||||||
|
{ "locale": "zh", "blitz": "zh_Hans_SG" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -11,7 +11,9 @@
|
|||||||
"mm": "%s1mm",
|
"mm": "%s1mm",
|
||||||
"deg": "%s1°",
|
"deg": "%s1°",
|
||||||
"hp": "%s1hp",
|
"hp": "%s1hp",
|
||||||
"x": "x%s1"
|
"x": "x%s1",
|
||||||
|
"percentage": "%s1%",
|
||||||
|
"deg_s": "%s1°/s"
|
||||||
},
|
},
|
||||||
|
|
||||||
"gun_types": {
|
"gun_types": {
|
||||||
@@ -739,6 +741,11 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"website": {
|
"website": {
|
||||||
|
"redirect": {
|
||||||
|
"title": "301: moved permanently",
|
||||||
|
"body": "This page has moved. Redirecting..."
|
||||||
|
},
|
||||||
|
|
||||||
"settings": {
|
"settings": {
|
||||||
"dev_mode": "Development mode",
|
"dev_mode": "Development mode",
|
||||||
"language": "Language"
|
"language": "Language"
|
||||||
@@ -756,6 +763,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"tank_search": {
|
"tank_search": {
|
||||||
|
"clear_filters": "Clear filters",
|
||||||
"search_bar_hint": "Search tanks...",
|
"search_bar_hint": "Search tanks...",
|
||||||
"recent": "Recently viewed",
|
"recent": "Recently viewed",
|
||||||
"select_all": "Select all",
|
"select_all": "Select all",
|
||||||
@@ -824,6 +832,12 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"home": {
|
"home": {
|
||||||
|
"discord": {
|
||||||
|
"join_short": "Join cool people!",
|
||||||
|
"join_long": "Join super duper cool people!",
|
||||||
|
"action": "Discord"
|
||||||
|
},
|
||||||
|
|
||||||
"hero": {
|
"hero": {
|
||||||
"credit": "By %s1 ❤️"
|
"credit": "By %s1 ❤️"
|
||||||
},
|
},
|
||||||
@@ -838,8 +852,32 @@
|
|||||||
"seo_description": "Tools for everything World of Tanks Blitz"
|
"seo_description": "Tools for everything World of Tanks Blitz"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"docs": {
|
||||||
|
"patches": {
|
||||||
|
"name": "BlitzKit Changelogs",
|
||||||
|
"seo_description": "Changelogs for BlitzKit Discord bot and website"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"tools": {
|
"tools": {
|
||||||
|
"guess": {
|
||||||
|
"name": "Guess the tank",
|
||||||
|
"button": "Guess away",
|
||||||
|
"description": "Silhouette mini-game",
|
||||||
|
"seo_description": "Play a tank guessing game with a silhouette in World of Tanks Blitz",
|
||||||
|
"search": {
|
||||||
|
"placeholder": "Type to guess...",
|
||||||
|
"skip": "Skip",
|
||||||
|
"guess": "Guess",
|
||||||
|
"next": "Next",
|
||||||
|
"no_results": "No results"
|
||||||
|
},
|
||||||
|
"stats": "%s1 / %s2 guessed correctly"
|
||||||
|
},
|
||||||
|
|
||||||
"tankopedia": {
|
"tankopedia": {
|
||||||
|
"search": "Search a stat...",
|
||||||
|
|
||||||
"name": "Tankopedia",
|
"name": "Tankopedia",
|
||||||
"button": "Find tanks",
|
"button": "Find tanks",
|
||||||
"description": "Blitz tank encyclopedia",
|
"description": "Blitz tank encyclopedia",
|
||||||
@@ -847,6 +885,32 @@
|
|||||||
"tank_seo_title": "%s1 - Tier %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)",
|
"tank_seo_title": "%s1 - Tier %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)",
|
||||||
"tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
|
"tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
|
||||||
|
|
||||||
|
"visualizers": {
|
||||||
|
"rotator": {
|
||||||
|
"hull": "Hull",
|
||||||
|
"turret": "Turret",
|
||||||
|
"hard": "Hard",
|
||||||
|
"medium": "Medium",
|
||||||
|
"soft": "Soft",
|
||||||
|
"effective": "Effective"
|
||||||
|
},
|
||||||
|
|
||||||
|
"ricochet": {
|
||||||
|
"angle": "Angle",
|
||||||
|
"normalization": "Normalization",
|
||||||
|
"effective_angle": "Effective angle",
|
||||||
|
"armor": "Armor",
|
||||||
|
"no_ricochet": "Explosive shells don't ricochet"
|
||||||
|
},
|
||||||
|
|
||||||
|
"view_range": {
|
||||||
|
"camo": "Camo",
|
||||||
|
"percentage": "%s1%",
|
||||||
|
"ghost_factory": "Ghost Factory",
|
||||||
|
"dead_rail": "Dead Rail"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"sandbox": {
|
"sandbox": {
|
||||||
"poses": {
|
"poses": {
|
||||||
"hull_down": "Hull down",
|
"hull_down": "Hull down",
|
||||||
@@ -996,7 +1060,7 @@
|
|||||||
"characteristics": {
|
"characteristics": {
|
||||||
"title": "Characteristics",
|
"title": "Characteristics",
|
||||||
"compare": {
|
"compare": {
|
||||||
"label": "Compare against",
|
"label": "Compare",
|
||||||
"class": "Tier %s1 %s2",
|
"class": "Tier %s1 %s2",
|
||||||
"tier": "All of tier %s1",
|
"tier": "All of tier %s1",
|
||||||
"all": "All tanks"
|
"all": "All tanks"
|
||||||
@@ -1033,7 +1097,7 @@
|
|||||||
"explosionRadius": "Explosion radius",
|
"explosionRadius": "Explosion radius",
|
||||||
"shellVelocity": "Shell velocity",
|
"shellVelocity": "Shell velocity",
|
||||||
"aimTime": "Aim time",
|
"aimTime": "Aim time",
|
||||||
"dispersion": "Still",
|
"dispersion": "Dispersion still",
|
||||||
"dispersionMoving": "Moving",
|
"dispersionMoving": "Moving",
|
||||||
"dispersionHullTraversing": "Hull traversing",
|
"dispersionHullTraversing": "Hull traversing",
|
||||||
"dispersionTurretTraversing": "Turret traversing",
|
"dispersionTurretTraversing": "Turret traversing",
|
||||||
@@ -1047,28 +1111,28 @@
|
|||||||
"gunRearElevation": "Rear elevation",
|
"gunRearElevation": "Rear elevation",
|
||||||
"azimuthLeft": "Azimuth left",
|
"azimuthLeft": "Azimuth left",
|
||||||
"azimuthRight": "Azimuth right",
|
"azimuthRight": "Azimuth right",
|
||||||
"speedForwards": "Forwards",
|
"speedForwards": "Speed forwards",
|
||||||
"speedBackwards": "Backwards",
|
"speedBackwards": "Backwards",
|
||||||
"hardTerrainCoefficient": "Hard",
|
"hardTerrainCoefficient": "Terrain coefficient hard",
|
||||||
"hardTerrainCoefficientRaw": "Hard",
|
"hardTerrainCoefficientRaw": "Raw terrain coefficient hard",
|
||||||
"mediumTerrainCoefficient": "Medium",
|
"mediumTerrainCoefficient": "Medium",
|
||||||
"mediumTerrainCoefficientRaw": "Medium",
|
"mediumTerrainCoefficientRaw": "Medium",
|
||||||
"softTerrainCoefficient": "Soft",
|
"softTerrainCoefficient": "Soft",
|
||||||
"softTerrainCoefficientRaw": "Soft",
|
"softTerrainCoefficientRaw": "Soft",
|
||||||
"enginePower": "Engine power",
|
"enginePower": "Engine power",
|
||||||
"powerToWeightRatioHardTerrain": "Hard",
|
"powerToWeightRatioHardTerrain": "Power to weight ratio hard",
|
||||||
"powerToWeightRatioMediumTerrain": "Medium",
|
"powerToWeightRatioMediumTerrain": "Medium",
|
||||||
"powerToWeightRatioSoftTerrain": "Soft",
|
"powerToWeightRatioSoftTerrain": "Soft",
|
||||||
"weight": "Weight",
|
"weight": "Weight",
|
||||||
"turretTraverseSpeed": "Turret traverse speed",
|
"turretTraverseSpeed": "Turret traverse speed",
|
||||||
"gunTraverseSpeed": "Gun traverse speed",
|
"gunTraverseSpeed": "Gun traverse speed",
|
||||||
"hullTraverseHardTerrain": "Hard",
|
"hullTraverseHardTerrain": "Hull traverse hard",
|
||||||
"hullTraverseMediumTerrain": "Medium",
|
"hullTraverseMediumTerrain": "Medium",
|
||||||
"hullTraverseSoftTerrain": "Soft",
|
"hullTraverseSoftTerrain": "Soft",
|
||||||
"health": "Health",
|
"health": "Health",
|
||||||
"fireChance": "Fire chance",
|
"fireChance": "Fire chance",
|
||||||
"viewRange": "View range",
|
"viewRange": "View range",
|
||||||
"camouflageStill": "Still",
|
"camouflageStill": "Camouflage still",
|
||||||
"camouflageMoving": "Moving",
|
"camouflageMoving": "Moving",
|
||||||
"camouflageShootingStill": "Still",
|
"camouflageShootingStill": "Still",
|
||||||
"camouflageShootingMoving": "Moving",
|
"camouflageShootingMoving": "Moving",
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { assertSecret } from '@blitzkit/core';
|
import { assertSecret } from '@blitzkit/core';
|
||||||
import { existsSync } from 'fs';
|
|
||||||
import { mkdir, writeFile } from 'fs/promises';
|
import { mkdir, writeFile } from 'fs/promises';
|
||||||
import { parse as parsePath } from 'path';
|
import { parse as parsePath } from 'path';
|
||||||
import ProgressBar from 'progress';
|
import ProgressBar from 'progress';
|
||||||
@@ -57,9 +56,12 @@ while (true) {
|
|||||||
console.warn(`Failed to make directory "${dir}"`);
|
console.warn(`Failed to make directory "${dir}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isDvpl = path.endsWith('.dvpl');
|
||||||
|
const buffer = Buffer.from(data);
|
||||||
|
|
||||||
await writeFile(
|
await writeFile(
|
||||||
`${DATA}/${path}`,
|
`${DATA}/${path}${isDvpl ? '' : '.dvpl'}`,
|
||||||
new Uint8Array(writeDVPL(Buffer.from(data))),
|
new Uint8Array(isDvpl ? buffer : writeDVPL(buffer)),
|
||||||
);
|
);
|
||||||
|
|
||||||
bar.tick();
|
bar.tick();
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
SUPPORTED_LOCALES,
|
SUPPORTED_LOCALES,
|
||||||
} from '@blitzkit/i18n';
|
} from '@blitzkit/i18n';
|
||||||
import { readdir } from 'fs/promises';
|
import { readdir } from 'fs/promises';
|
||||||
|
import { deburr } from 'lodash-es';
|
||||||
import { parse as parsePath } from 'path';
|
import { parse as parsePath } from 'path';
|
||||||
import { Vector3Tuple } from 'three';
|
import { Vector3Tuple } from 'three';
|
||||||
import { parse as parseYaml } from 'yaml';
|
import { parse as parseYaml } from 'yaml';
|
||||||
@@ -48,6 +49,23 @@ import { DATA } from './constants';
|
|||||||
import { Avatar } from './skillIcons';
|
import { Avatar } from './skillIcons';
|
||||||
import { TankParameters } from './tankIcons';
|
import { TankParameters } from './tankIcons';
|
||||||
|
|
||||||
|
const nonAlphanumericRegex = /[^a-z0-9]/g;
|
||||||
|
const multipleDashesRegex = /--+/g;
|
||||||
|
const trailingDashRegex = /-$/g;
|
||||||
|
const leadingDashRegex = /^-/g;
|
||||||
|
|
||||||
|
const nationSlugDiscriminators = {
|
||||||
|
china: 'cn',
|
||||||
|
european: 'eu',
|
||||||
|
france: 'fr',
|
||||||
|
germany: 'de',
|
||||||
|
japan: 'jp',
|
||||||
|
other: 'ot',
|
||||||
|
uk: 'gb',
|
||||||
|
usa: 'us',
|
||||||
|
ussr: 'ru',
|
||||||
|
};
|
||||||
|
|
||||||
function parseResearchCost(raw: number | string) {
|
function parseResearchCost(raw: number | string) {
|
||||||
if (typeof raw === 'number') {
|
if (typeof raw === 'number') {
|
||||||
return {
|
return {
|
||||||
@@ -502,7 +520,7 @@ const blitzShellKindToBlitzkit: Record<ShellKind, ShellType> = {
|
|||||||
HIGH_EXPLOSIVE: ShellType.HE,
|
HIGH_EXPLOSIVE: ShellType.HE,
|
||||||
HOLLOW_CHARGE: ShellType.HEAT,
|
HOLLOW_CHARGE: ShellType.HEAT,
|
||||||
};
|
};
|
||||||
export const botPattern = /^.+((tutorial_bot(\d+)?)|(TU(R?)))$/;
|
export const botPattern = /^.+((tutorial_bot(\d+)?)|(TU))$/;
|
||||||
const blitzModuleTypeToBlitzkit: Record<keyof BlitzModuleType, ModuleType> = {
|
const blitzModuleTypeToBlitzkit: Record<keyof BlitzModuleType, ModuleType> = {
|
||||||
chassis: ModuleType.TRACKS,
|
chassis: ModuleType.TRACKS,
|
||||||
engine: ModuleType.ENGINE,
|
engine: ModuleType.ENGINE,
|
||||||
@@ -703,6 +721,93 @@ export async function definitions() {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const slugRequesters = new Map<string, { id: number; key: string }[]>();
|
||||||
|
const idToNation: Record<number, string> = {};
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
nations.map(async (nation) => {
|
||||||
|
const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>(
|
||||||
|
`${DATA}/XML/item_defs/vehicles/${nation}/list.xml`,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const tankKey in tankList.root) {
|
||||||
|
if (botPattern.test(tankKey)) continue;
|
||||||
|
|
||||||
|
const tank = tankList.root[tankKey];
|
||||||
|
const tankId = toUniqueId(nation, tank.id);
|
||||||
|
const name = (
|
||||||
|
(tank.shortUserString
|
||||||
|
? getString(tank.shortUserString)
|
||||||
|
: undefined) ?? getString(tank.userString)
|
||||||
|
).locales.en;
|
||||||
|
|
||||||
|
let slug = deburr(name).toLowerCase();
|
||||||
|
slug = slug.replaceAll(nonAlphanumericRegex, '-');
|
||||||
|
slug = slug.replaceAll(multipleDashesRegex, '-');
|
||||||
|
slug = slug.replaceAll(trailingDashRegex, '');
|
||||||
|
slug = slug.replaceAll(leadingDashRegex, '');
|
||||||
|
|
||||||
|
idToNation[tankId] = nation;
|
||||||
|
|
||||||
|
if (slugRequesters.has(slug)) {
|
||||||
|
slugRequesters.get(slug)!.push({ id: tankId, key: tankKey });
|
||||||
|
} else {
|
||||||
|
slugRequesters.set(slug, [{ id: tankId, key: tankKey }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const slugs = new Map<number, string>();
|
||||||
|
|
||||||
|
slugRequesters.forEach((requesters, slug) => {
|
||||||
|
if (requesters.length === 1) {
|
||||||
|
slugs.set(requesters[0].id, slug);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn(
|
||||||
|
`Multiple tanks share slug ${slug}: ${requesters.map(({ key }) => key).join(', ')}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (requesters.length !== 2) {
|
||||||
|
throw new Error('Unresolvable number of duplicates :(');
|
||||||
|
}
|
||||||
|
|
||||||
|
const nonCanonical = requesters.find(({ key }) => key.endsWith('TUR'));
|
||||||
|
|
||||||
|
if (nonCanonical === undefined) {
|
||||||
|
console.log('Using nations to discriminate');
|
||||||
|
// both are non-tutorial tanks, will have to discriminate using nation
|
||||||
|
|
||||||
|
requesters.forEach(({ id, key }) => {
|
||||||
|
const nation = idToNation[id];
|
||||||
|
const discriminator =
|
||||||
|
nationSlugDiscriminators[
|
||||||
|
nation as keyof typeof nationSlugDiscriminators
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log(`Solution: ${key} -> ${slug}-${discriminator}`);
|
||||||
|
slugs.set(id, `${slug}-${discriminator}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('Using tutorial bot suffix to discriminate');
|
||||||
|
|
||||||
|
const canonical = requesters.find(({ key }) => !key.endsWith('TUR'));
|
||||||
|
|
||||||
|
if (canonical === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
'Two tutorial bots share the same slug? The world is truly broken.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Solution: ${canonical.key} -> ${slug}`);
|
||||||
|
slugs.set(canonical.id, slug);
|
||||||
|
console.log(`Solution: ${nonCanonical.key} -> ${slug}-tur`);
|
||||||
|
slugs.set(nonCanonical.id, `${slug}-tur`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
nations.map(async (nation) => {
|
nations.map(async (nation) => {
|
||||||
const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>(
|
const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>(
|
||||||
@@ -787,6 +892,13 @@ export async function definitions() {
|
|||||||
const hullArmor: Armor = { spaced: [], thickness: {} };
|
const hullArmor: Armor = { spaced: [], thickness: {} };
|
||||||
const equipment = tankDefinition.root.optDevicePreset;
|
const equipment = tankDefinition.root.optDevicePreset;
|
||||||
tankStringIdMap[`${nation}:${tankKey}`] = tankId;
|
tankStringIdMap[`${nation}:${tankKey}`] = tankId;
|
||||||
|
const slug = slugs.get(tankId);
|
||||||
|
|
||||||
|
if (slug === undefined) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not find slug for ${nation}/${tankKey} (${tankId})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (tank.sellPrice) {
|
if (tank.sellPrice) {
|
||||||
tankPrice = {
|
tankPrice = {
|
||||||
@@ -903,7 +1015,7 @@ export async function definitions() {
|
|||||||
(tank.shortUserString
|
(tank.shortUserString
|
||||||
? getString(tank.shortUserString)
|
? getString(tank.shortUserString)
|
||||||
: undefined) ?? getString(tank.userString),
|
: undefined) ?? getString(tank.userString),
|
||||||
name_full: getString(tank.userString),
|
slug,
|
||||||
nation,
|
nation,
|
||||||
type: tankTags.includes('collectible')
|
type: tankTags.includes('collectible')
|
||||||
? TankType.COLLECTOR
|
? TankType.COLLECTOR
|
||||||
@@ -939,13 +1051,6 @@ export async function definitions() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
tankDefinitions.tanks[tankId].name ===
|
|
||||||
tankDefinitions.tanks[tankId].name_full
|
|
||||||
) {
|
|
||||||
delete tankDefinitions.tanks[tankId].name_full;
|
|
||||||
}
|
|
||||||
|
|
||||||
modelDefinitions.models[tankId] = {
|
modelDefinitions.models[tankId] = {
|
||||||
armor: hullArmor,
|
armor: hullArmor,
|
||||||
turret_origin: vector3TupleToBlitzkit(turretOrigin),
|
turret_origin: vector3TupleToBlitzkit(turretOrigin),
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { Avatar, encodePBBuffer, fetchGlossary, Gallery } from '@blitzkit/core';
|
||||||
|
import locales from '@blitzkit/i18n/locales.json';
|
||||||
|
import { extname } from 'path';
|
||||||
|
import ProgressBar from 'progress';
|
||||||
|
import { commitAssets } from '../core/github/commitAssets';
|
||||||
|
import { FileChange } from '../core/github/commitMultipleFiles';
|
||||||
|
|
||||||
|
export async function glossary() {
|
||||||
|
console.log('Building glossary...');
|
||||||
|
|
||||||
|
const avatars: Record<string, { avatar: Avatar; url: string }> = {};
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
locales.supported.map(async (supported) => {
|
||||||
|
const glossary = await fetchGlossary(supported.blitz ?? supported.locale);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Found ${Object.keys(glossary).length} things for ${supported.locale}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const key in glossary) {
|
||||||
|
const glossaryEntry = glossary[key];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!key.startsWith('avatar') ||
|
||||||
|
key.endsWith('_part') ||
|
||||||
|
glossaryEntry.image_url === null
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key in avatars) {
|
||||||
|
avatars[key].avatar.name.locales[supported.locale] =
|
||||||
|
glossaryEntry.title;
|
||||||
|
} else {
|
||||||
|
const extension = extname(glossaryEntry.image_url);
|
||||||
|
|
||||||
|
avatars[key] = {
|
||||||
|
url: glossaryEntry.image_url,
|
||||||
|
avatar: {
|
||||||
|
id: key,
|
||||||
|
name: { locales: { [supported.locale]: glossaryEntry.title } },
|
||||||
|
extension,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const changes: FileChange[] = [];
|
||||||
|
const gallery: Gallery = { avatars: [] };
|
||||||
|
const totalCount = Object.keys(avatars).length;
|
||||||
|
|
||||||
|
const bar = new ProgressBar(
|
||||||
|
`Fetching avatars of ${totalCount} things :bar`,
|
||||||
|
totalCount,
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(avatars).map(async ([key, { url, avatar }]) => {
|
||||||
|
if (!(locales.default in avatars[key].avatar.name.locales)) {
|
||||||
|
console.warn(`Avatar ${key} has no ${locales.default} name`);
|
||||||
|
bar.tick();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gallery.avatars.push(avatar);
|
||||||
|
|
||||||
|
const content = await fetch(url).then((response) =>
|
||||||
|
response.arrayBuffer(),
|
||||||
|
);
|
||||||
|
|
||||||
|
changes.push({
|
||||||
|
path: `gallery/avatars/${key}${avatar.extension}`,
|
||||||
|
content: Buffer.from(content),
|
||||||
|
});
|
||||||
|
|
||||||
|
bar.tick();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
changes.push({
|
||||||
|
path: 'definitions/gallery.pb',
|
||||||
|
content: encodePBBuffer(Gallery, gallery),
|
||||||
|
});
|
||||||
|
|
||||||
|
await commitAssets('glossary', changes);
|
||||||
|
}
|
||||||
@@ -18,7 +18,10 @@ import { definitions } from './definitions';
|
|||||||
import { equipmentIcons } from './equipmentIcons';
|
import { equipmentIcons } from './equipmentIcons';
|
||||||
import { flags } from './flags';
|
import { flags } from './flags';
|
||||||
import { gameModeBanners } from './gameModeBanners';
|
import { gameModeBanners } from './gameModeBanners';
|
||||||
|
import { glossary } from './glossary';
|
||||||
|
import { migration } from './migration';
|
||||||
import { moduleIcons } from './moduleIcons';
|
import { moduleIcons } from './moduleIcons';
|
||||||
|
import { popularTanks } from './popularTanks';
|
||||||
import { shellIcons } from './shellIcons';
|
import { shellIcons } from './shellIcons';
|
||||||
import { skillIcons } from './skillIcons';
|
import { skillIcons } from './skillIcons';
|
||||||
import { tankArmors } from './tankArmors';
|
import { tankArmors } from './tankArmors';
|
||||||
@@ -28,6 +31,7 @@ import { tankModels } from './tankModels';
|
|||||||
if (!targets && !allTargets) throw new Error('No target(s) specified');
|
if (!targets && !allTargets) throw new Error('No target(s) specified');
|
||||||
|
|
||||||
const methods = [
|
const methods = [
|
||||||
|
migration,
|
||||||
definitions,
|
definitions,
|
||||||
tankModels,
|
tankModels,
|
||||||
tankArmors,
|
tankArmors,
|
||||||
@@ -43,6 +47,8 @@ const methods = [
|
|||||||
boosterIcons,
|
boosterIcons,
|
||||||
tankIcons,
|
tankIcons,
|
||||||
blitzkitTankIcons,
|
blitzkitTankIcons,
|
||||||
|
popularTanks,
|
||||||
|
glossary,
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const method of methods) {
|
for (const method of methods) {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { toUniqueId } from '@blitzkit/core';
|
||||||
|
import { readdir } from 'fs/promises';
|
||||||
|
import { readXMLDVPL } from '../core/blitz/readXMLDVPL';
|
||||||
|
import { commitAssets } from '../core/github/commitAssets';
|
||||||
|
import { DATA } from './constants';
|
||||||
|
import { VehicleDefinitionList } from './definitions';
|
||||||
|
|
||||||
|
export async function migration() {
|
||||||
|
console.log('Building migration map...');
|
||||||
|
|
||||||
|
const map: Record<string, number> = {};
|
||||||
|
const nations = await readdir(`${DATA}/XML/item_defs/vehicles`).then(
|
||||||
|
(nations) => nations.filter((nation) => nation !== 'common'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
nations.map(async (nation) => {
|
||||||
|
const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>(
|
||||||
|
`${DATA}/XML/item_defs/vehicles/${nation}/list.xml`,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const tankKey in tankList.root) {
|
||||||
|
const tank = tankList.root[tankKey];
|
||||||
|
const tankId = toUniqueId(nation, tank.id);
|
||||||
|
const davaKey = `${nation}:${tankKey}`;
|
||||||
|
|
||||||
|
map[davaKey] = tankId;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await commitAssets('migration', [
|
||||||
|
{
|
||||||
|
path: 'definitions/migration.json',
|
||||||
|
content: Buffer.from(JSON.stringify(map)),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import {
|
||||||
|
assertSecret,
|
||||||
|
encodePBBuffer,
|
||||||
|
fetchTankDefinitions,
|
||||||
|
PopularTanks,
|
||||||
|
} from '@blitzkit/core';
|
||||||
|
import { google } from 'googleapis';
|
||||||
|
import { commitAssets } from '../core/github/commitAssets';
|
||||||
|
|
||||||
|
export async function popularTanks() {
|
||||||
|
const tankDefinitions = await fetchTankDefinitions();
|
||||||
|
|
||||||
|
const auth = await google.auth.getClient({
|
||||||
|
keyFile: import.meta.env.GOOGLE_APPLICATION_CREDENTIALS,
|
||||||
|
scopes: ['https://www.googleapis.com/auth/analytics.readonly'],
|
||||||
|
});
|
||||||
|
const analytics = google.analyticsdata({ version: 'v1beta', auth });
|
||||||
|
const report = await analytics.properties.runReport({
|
||||||
|
property: `properties/${assertSecret(import.meta.env.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID)}`,
|
||||||
|
requestBody: {
|
||||||
|
dateRanges: [{ startDate: '7daysAgo', endDate: 'today' }],
|
||||||
|
dimensions: [{ name: 'pagePath' }],
|
||||||
|
metrics: [{ name: 'screenPageViews' }],
|
||||||
|
orderBys: [{ metric: { metricName: 'screenPageViews' }, desc: true }],
|
||||||
|
dimensionFilter: {
|
||||||
|
filter: {
|
||||||
|
fieldName: 'pagePath',
|
||||||
|
stringFilter: {
|
||||||
|
matchType: 'BEGINS_WITH',
|
||||||
|
value: '/tanks/',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!report.data.rows) {
|
||||||
|
throw new Error('No rows in report');
|
||||||
|
}
|
||||||
|
|
||||||
|
const tanks = report.data.rows
|
||||||
|
.filter(
|
||||||
|
(row) =>
|
||||||
|
row.dimensionValues &&
|
||||||
|
row.dimensionValues[0].value &&
|
||||||
|
row.dimensionValues[0].value !== '/tanks/' &&
|
||||||
|
row.metricValues &&
|
||||||
|
row.metricValues[0].value,
|
||||||
|
)
|
||||||
|
.map((row) => ({
|
||||||
|
id: Number(
|
||||||
|
row.dimensionValues![0].value!.match(
|
||||||
|
/\/tools\/tankopedia\/(\d+)\/?/,
|
||||||
|
)?.[1],
|
||||||
|
),
|
||||||
|
views: Number(row.metricValues![0].value!),
|
||||||
|
}))
|
||||||
|
.filter((row) => {
|
||||||
|
if (!(row.id in tankDefinitions.tanks)) {
|
||||||
|
console.warn('Skipping unknown popular tank', row.id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return row.id in tankDefinitions.tanks;
|
||||||
|
})
|
||||||
|
.slice(0, 8)
|
||||||
|
.map(({ id, views }) => ({ id, views }));
|
||||||
|
const popularTanks: PopularTanks = { tanks };
|
||||||
|
|
||||||
|
await commitAssets('popular tanks', [
|
||||||
|
{
|
||||||
|
path: 'definitions/popular-tanks.pb',
|
||||||
|
content: encodePBBuffer(PopularTanks, popularTanks),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -166,28 +166,9 @@ export async function extractModel(data: string, path: string) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'TransformComponent': {
|
case 'TransformComponent': {
|
||||||
const translation = new Vector3();
|
node.setTranslation(component['tc.localTranslation']);
|
||||||
const rotation = new Quaternion();
|
node.setRotation(component['tc.localRotation']);
|
||||||
const scale = new Vector3();
|
node.setScale(component['tc.localScale']);
|
||||||
|
|
||||||
new Matrix4()
|
|
||||||
.multiplyMatrices(
|
|
||||||
new Matrix4().compose(
|
|
||||||
new Vector3().fromArray(component['tc.worldTranslation']),
|
|
||||||
new Quaternion().fromArray(component['tc.worldRotation']),
|
|
||||||
new Vector3().fromArray(component['tc.worldScale']),
|
|
||||||
),
|
|
||||||
new Matrix4().compose(
|
|
||||||
new Vector3().fromArray(component['tc.localTranslation']),
|
|
||||||
new Quaternion().fromArray(component['tc.localRotation']),
|
|
||||||
new Vector3().fromArray(component['tc.localScale']),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.decompose(translation, rotation, scale);
|
|
||||||
|
|
||||||
node.setTranslation(translation.toArray());
|
|
||||||
node.setRotation(rotation.toArray() as Vector4Tuple);
|
|
||||||
node.setScale(scale.toArray());
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { TextureMutation } from './constants';
|
|||||||
|
|
||||||
export async function readTexture(path: string, mutation?: TextureMutation) {
|
export async function readTexture(path: string, mutation?: TextureMutation) {
|
||||||
const ddsTexturePath = path.replace('.tex', '.dx11.dds');
|
const ddsTexturePath = path.replace('.tex', '.dx11.dds');
|
||||||
const isDds = existsSync(ddsTexturePath);
|
const isDds = existsSync(`${ddsTexturePath}.dvpl`);
|
||||||
const resolvedTexturePath = isDds
|
const resolvedTexturePath = isDds
|
||||||
? ddsTexturePath
|
? ddsTexturePath
|
||||||
: ddsTexturePath.replace('.dds', '.pvr');
|
: ddsTexturePath.replace('.dds', '.pvr');
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { assertSecret } from '@blitzkit/core';
|
import { assertSecret } from '@blitzkit/core';
|
||||||
|
import { chunk } from 'lodash-es';
|
||||||
import { commitMultipleFiles, FileChange } from './commitMultipleFiles';
|
import { commitMultipleFiles, FileChange } from './commitMultipleFiles';
|
||||||
|
|
||||||
export async function commitAssets(message: string, changes: FileChange[]) {
|
export async function commitAssets(message: string, changes: FileChange[]) {
|
||||||
@@ -6,10 +7,19 @@ export async function commitAssets(message: string, changes: FileChange[]) {
|
|||||||
|
|
||||||
if (changes.length === 0) return;
|
if (changes.length === 0) return;
|
||||||
|
|
||||||
|
const chunks = chunk(changes, 64);
|
||||||
|
let chunkIndex = 0;
|
||||||
|
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
console.log(`Committing chunk ${chunkIndex + 1}/${chunks.length}`);
|
||||||
|
|
||||||
await commitMultipleFiles(
|
await commitMultipleFiles(
|
||||||
assertSecret(import.meta.env.PUBLIC_ASSET_REPO),
|
assertSecret(import.meta.env.PUBLIC_ASSET_REPO),
|
||||||
assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
|
assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
|
||||||
`${message} - ${new Date().toDateString()}`,
|
`${message} - ${new Date().toDateString()} (${chunkIndex + 1}/${chunks.length})`,
|
||||||
changes,
|
chunk,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
chunkIndex++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,8 @@ export interface FileChange {
|
|||||||
content: Buffer;
|
content: Buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const heuristicFormats = ['glb', 'webp', 'jpg', 'png'];
|
||||||
|
|
||||||
export async function commitMultipleFiles(
|
export async function commitMultipleFiles(
|
||||||
repoRaw: string,
|
repoRaw: string,
|
||||||
branch: string,
|
branch: string,
|
||||||
@@ -21,32 +23,69 @@ export async function commitMultipleFiles(
|
|||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`https://raw.githubusercontent.com/${blobPath}`,
|
`https://raw.githubusercontent.com/${blobPath}`,
|
||||||
);
|
);
|
||||||
|
let isNew: boolean;
|
||||||
|
let isDifferent: boolean;
|
||||||
|
let diff: number;
|
||||||
|
|
||||||
if (response.status === 404) {
|
if (response.status === 404) {
|
||||||
console.log(
|
isNew = true;
|
||||||
`🟢 (+${change.content.length.toLocaleString()}B) ${blobPath}`,
|
diff = change.content.length;
|
||||||
);
|
isDifferent = true;
|
||||||
changes.push(change);
|
} else if (
|
||||||
|
response.headers.has('Content-Length') &&
|
||||||
|
parseInt(response.headers.get('Content-Length')!) !==
|
||||||
|
change.content.length
|
||||||
|
) {
|
||||||
|
isNew = false;
|
||||||
|
diff =
|
||||||
|
change.content.length -
|
||||||
|
parseInt(response.headers.get('Content-Length')!);
|
||||||
|
isDifferent = true;
|
||||||
} else if (response.status === 200) {
|
} else if (response.status === 200) {
|
||||||
|
isNew = false;
|
||||||
const buffer = Buffer.from(await response.arrayBuffer());
|
const buffer = Buffer.from(await response.arrayBuffer());
|
||||||
|
|
||||||
// we discard blob if they're the same size; it's unlikely their
|
if (buffer.equals(change.content)) {
|
||||||
// contents will be different; I love playing russian roulette!
|
diff = 0;
|
||||||
const equal = buffer.length === change.content.length || buffer.equals(change.content);
|
isDifferent = false;
|
||||||
if (!equal) {
|
} else {
|
||||||
const diff = change.content.length - buffer.length;
|
diff = change.content.length - buffer.length;
|
||||||
|
isDifferent = true;
|
||||||
console.log(
|
|
||||||
`🟡 (${diff > 0 ? '+' : ''}${diff.toLocaleString()}B) ${blobPath}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
changes.push(change);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unexpected status code ${response.status} for ${blobPath}`,
|
`Unexpected status code ${response.status} for ${blobPath}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heuristic: if it's a large blob like a .glb, a diff = 0 might end up
|
||||||
|
* being a false positive with isDifferent = true. So, it's okay to
|
||||||
|
* assume it's unchanged.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
diff === 0 &&
|
||||||
|
heuristicFormats.some((format) => change.path.endsWith(`.${format}`))
|
||||||
|
) {
|
||||||
|
isDifferent = false;
|
||||||
|
isNew = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNew) {
|
||||||
|
console.log(
|
||||||
|
`🟢 (+${change.content.length.toLocaleString()}B) ${blobPath}`,
|
||||||
|
);
|
||||||
|
changes.push(change);
|
||||||
|
} else if (isDifferent) {
|
||||||
|
console.log(
|
||||||
|
`🟡 (${diff > 0 ? '+' : ''}${diff.toLocaleString()}B) ${blobPath}`,
|
||||||
|
);
|
||||||
|
changes.push(change);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`🔵 (${change.content.length.toLocaleString()}B) ${blobPath}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { writeFile } from 'fs/promises';
|
|||||||
const ROOT = '@blitzkit/i18n/strings';
|
const ROOT = '@blitzkit/i18n/strings';
|
||||||
const WORKING_ROOT = '../../packages/i18n/strings';
|
const WORKING_ROOT = '../../packages/i18n/strings';
|
||||||
|
|
||||||
const defaultStrings = await import(`${ROOT}/${DEFAULT_LOCALE}.json`);
|
const defaultStrings = require(`${ROOT}/${DEFAULT_LOCALE}.json`).default;
|
||||||
const nonDefaultLocales = SUPPORTED_LOCALES.filter(
|
const nonDefaultLocales = SUPPORTED_LOCALES.filter(
|
||||||
(locale) => locale !== DEFAULT_LOCALE,
|
(locale) => locale !== DEFAULT_LOCALE,
|
||||||
);
|
);
|
||||||
@@ -19,9 +19,13 @@ function prune(defaultStrings: TranslationTree, strings: TranslationTree) {
|
|||||||
if (typeof defaultStrings[key] === typeof strings[key]) {
|
if (typeof defaultStrings[key] === typeof strings[key]) {
|
||||||
if (typeof strings[key] === 'string') {
|
if (typeof strings[key] === 'string') {
|
||||||
if (
|
if (
|
||||||
|
// are equivalent regardless of %s flavor
|
||||||
(defaultStrings[key] as string).replaceAll(/%s\d/g, '%s') ===
|
(defaultStrings[key] as string).replaceAll(/%s\d/g, '%s') ===
|
||||||
(strings[key] as string).replaceAll(/%s\d/g, '%s') ||
|
(strings[key] as string).replaceAll(/%s\d/g, '%s') ||
|
||||||
strings[key] === ''
|
// string is empty
|
||||||
|
strings[key] === '' ||
|
||||||
|
// string has old flavor of %s
|
||||||
|
strings[key].match(/%s([^\d]|$)/g) !== null
|
||||||
) {
|
) {
|
||||||
delete strings[key];
|
delete strings[key];
|
||||||
}
|
}
|
||||||
@@ -42,7 +46,7 @@ function prune(defaultStrings: TranslationTree, strings: TranslationTree) {
|
|||||||
for (const locale of nonDefaultLocales) {
|
for (const locale of nonDefaultLocales) {
|
||||||
console.log(`Pruning ${locale}...`);
|
console.log(`Pruning ${locale}...`);
|
||||||
|
|
||||||
const strings = await import(`${ROOT}/${locale}.json`);
|
const strings = require(`${ROOT}/${locale}.json`).default;
|
||||||
const pruned = prune(defaultStrings, strings);
|
const pruned = prune(defaultStrings, strings);
|
||||||
|
|
||||||
writeFile(`${WORKING_ROOT}/${locale}.json`, JSON.stringify(pruned, null, 2));
|
writeFile(`${WORKING_ROOT}/${locale}.json`, JSON.stringify(pruned, null, 2));
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
|
import partytown from '@astrojs/partytown';
|
||||||
import react from '@astrojs/react';
|
import react from '@astrojs/react';
|
||||||
import sitemap from '@astrojs/sitemap';
|
import sitemap from '@astrojs/sitemap';
|
||||||
import { SUPPORTED_LOCALES } from '@blitzkit/i18n';
|
import { SUPPORTED_LOCALES } from '@blitzkit/i18n';
|
||||||
@@ -20,7 +21,7 @@ export default defineConfig({
|
|||||||
outDir: '../../dist/website',
|
outDir: '../../dist/website',
|
||||||
prefetch: true,
|
prefetch: true,
|
||||||
|
|
||||||
integrations: [react(), sitemap()],
|
integrations: [react(), sitemap(), partytown()],
|
||||||
vite: {
|
vite: {
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
@@ -10,8 +10,11 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@astrojs/partytown": "^2.1.4",
|
||||||
|
"@astrolib/seo": "^1.0.0-beta.8",
|
||||||
"@blitzkit/core": "workspace:*",
|
"@blitzkit/core": "workspace:*",
|
||||||
"@blitzkit/i18n": "workspace:*",
|
"@blitzkit/i18n": "workspace:*",
|
||||||
|
"@fontsource/inter": "^5.2.5",
|
||||||
"@nanostores/persistent": "^0.10.2",
|
"@nanostores/persistent": "^0.10.2",
|
||||||
"@nanostores/react": "^0.7.3",
|
"@nanostores/react": "^0.7.3",
|
||||||
"@nivo/bar": "^0.87.0",
|
"@nivo/bar": "^0.87.0",
|
||||||
@@ -23,11 +26,11 @@
|
|||||||
"@react-three/fiber": "^8.17.10",
|
"@react-three/fiber": "^8.17.10",
|
||||||
"compare-versions": "^6.1.1",
|
"compare-versions": "^6.1.1",
|
||||||
"echarts": "^5.5.1",
|
"echarts": "^5.5.1",
|
||||||
"googleapis": "^144.0.0",
|
|
||||||
"immer": "^10.1.1",
|
"immer": "^10.1.1",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"markdown-to-jsx": "^7.5.0",
|
"markdown-to-jsx": "^7.5.0",
|
||||||
"nanostores": "^0.11.3",
|
"nanostores": "^0.11.3",
|
||||||
|
"quicklime": "^1.2.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-device-detect": "^2.2.3",
|
"react-device-detect": "^2.2.3",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@@ -40,10 +43,13 @@
|
|||||||
"@astrojs/check": "^0.9.4",
|
"@astrojs/check": "^0.9.4",
|
||||||
"@astrojs/react": "^3.6.2",
|
"@astrojs/react": "^3.6.2",
|
||||||
"@astrojs/sitemap": "^3.2.1",
|
"@astrojs/sitemap": "^3.2.1",
|
||||||
|
"@playform/compress": "^0.1.9",
|
||||||
|
"@playform/inline": "^0.1.2",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
"@types/three": "^0.160.0",
|
"@types/three": "^0.160.0",
|
||||||
"astro": "^4.16.7"
|
"astro": "^4.16.7",
|
||||||
|
"astro-compressor": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
After Width: | Height: | Size: 490 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 242 KiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 365 KiB |
|
After Width: | Height: | Size: 771 KiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 211 KiB |
|
After Width: | Height: | Size: 379 KiB |
|
After Width: | Height: | Size: 371 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 32 KiB |