Compare commits

..
89 Commits
Author SHA1 Message Date
tresabhi 1551cfe2b9 add tool 2026-08-13 12:51:17 -05:00
tresabhi f355cb9f76 proper concurrency 2026-08-13 12:34:53 -05:00
tresabhi e42576b005 proper concurrency 2026-08-13 12:30:48 -05:00
tresabhi f68198475e install clis 2026-08-13 12:15:55 -05:00
tresabhi 896ae20dce remove pages 2026-08-13 12:06:55 -05:00
tresabhi b8fdc5424d logs 2026-08-13 12:02:49 -05:00
tresabhi 204c3eacc1 i give up 2026-08-13 12:01:16 -05:00
tresabhi 4d0f21e10b zip download 2026-08-13 11:05:01 -05:00
tresabhi d3202e8607 steam downloader 2026-08-13 10:56:15 -05:00
tresabhi a438eb704f smarter max rate 2026-08-13 10:29:11 -05:00
tresabhi 1569de4475 Merge branch 'main' of https://github.com/blitzkit/blitzkit 2026-08-12 20:33:40 -05:00
tresabhi 0f022f8295 chunking based on fetch rate 2026-08-12 20:33:38 -05:00
TrèsAbhi 75c21a5129 New Crowdin updates (#261) 2026-08-12 20:25:55 -05:00
tresabhi 5092d43e8e fix has pbr 2026-08-12 13:41:59 -05:00
TrèsAbhi 56d5d5e956 Delete CNAME 2026-08-12 13:10:52 -05:00
TrèsAbhi 70eca3293b Create CNAME 2026-08-12 13:10:42 -05:00
tresabhi 4f7ee54ef7 logs 2026-08-12 12:17:20 -05:00
TrèsAbhi 269df9586d Merge pull request #265 from blitzkit/infrastructure
Infrastructure
2026-08-12 11:47:24 -05:00
tresabhi 3152c18840 simplify robots.txt 2026-08-12 11:46:45 -05:00
tresabhi 52630596b8 finalize workflows 2026-08-12 11:44:26 -05:00
tresabhi 7e184b8be0 eh whatever, this'll make it into the next update 2026-08-12 11:36:55 -05:00
tresabhi 877ba9408d some more recording 2026-08-11 20:29:50 -05:00
tresabhi 8dc9a6b46d start of recording 2026-08-11 20:11:12 -05:00
tresabhi b3e439669b start of performance 2026-08-11 20:03:57 -05:00
tresabhi 08c909fbe2 rename ids to data-ids 2026-08-11 19:46:26 -05:00
tresabhi a44e2e9866 fix tank modes not switching 2026-08-11 19:45:09 -05:00
tresabhi bae2f253bc screenshot friendly contact shadows 2026-08-11 19:37:57 -05:00
tresabhi d485a463dd orange ground 2026-08-11 14:07:57 -05:00
tresabhi 3da1ed4d6c ground and sky color 2026-08-11 11:30:36 -05:00
tresabhi 051e9ec74a ground with shadows 2026-08-11 10:58:35 -05:00
tresabhi 486171f4db shadow focus and res 2026-08-11 10:52:33 -05:00
tresabhi 72de42a9ab performant ground 2026-08-11 10:38:36 -05:00
tresabhi 404e6625c0 remmove lghts 2026-08-11 10:35:41 -05:00
tresabhi 32e4bf3fd2 fading out fragment 2026-08-11 10:16:14 -05:00
tresabhi 0d1088fefb non pbr lihging 2026-08-11 10:01:34 -05:00
tresabhi 1a17d607fb move light target up 2026-08-11 09:56:58 -05:00
tresabhi dcda627518 dvpl free dir 2026-08-11 09:45:17 -05:00
tresabhi c130da7f0e heheheha 2026-08-10 17:50:48 -05:00
tresabhi e2df4d35b8 flip lighitng and add metalness 2026-08-10 16:58:40 -05:00
tresabhi 7739dbd0a3 faster lighting 2026-08-10 16:42:35 -05:00
tresabhi 9b7aa13d61 fix occlusion baking 2026-08-10 10:39:31 -05:00
TrèsAbhi 84ecaec5d3 base 2026-08-09 22:20:53 -05:00
tresabhi 01a5f1bd47 5hr 55min 2026-08-09 22:18:36 -05:00
tresabhi de0433ca02 Merge branch 'infrastructure' of https://github.com/blitzkit/blitzkit into infrastructure 2026-08-09 22:17:43 -05:00
TrèsAbhi cf76ae7136 add dispatch 2026-08-09 22:13:07 -05:00
tresabhi ea21636c4e abstract away some code 2026-08-09 20:26:39 -05:00
tresabhi dafa834d28 some script cleanup 2026-08-09 20:10:04 -05:00
tresabhi f1a1c78588 remove stub 2026-08-09 20:08:08 -05:00
tresabhi 14d16e5daf shell files raw debug 2026-08-09 19:50:53 -05:00
tresabhi b52c1fc2d9 ok cool 2026-08-09 16:12:26 -05:00
tresabhi f45b24f7ab 5hr 30min runtime 2026-08-09 15:05:06 -05:00
tresabhi 6ae175e5e0 reset author 2026-08-09 15:02:04 -05:00
tresabhi 0df27c38a1 ids token 2026-08-09 14:58:25 -05:00
tresabhi e27cc3f6ed github actions bot 2026-08-09 14:51:26 -05:00
tresabhi 968c3957d0 60s, just for testing 2026-08-09 14:46:35 -05:00
tresabhi 577d2323a8 checkout recursive 2026-08-09 14:45:12 -05:00
tresabhi 625c6cc649 workflow for discovering ids 2026-08-09 14:44:16 -05:00
tresabhi 9bc753b512 i was going to add verification but eh, no right now... 2026-08-09 14:39:05 -05:00
tresabhi fb34e6ce2f shell file paths list 2026-08-09 14:12:46 -05:00
tresabhi f882ea29e2 total saved 2026-08-09 14:06:40 -05:00
tresabhi 62f68f1f10 add manifests 2026-08-09 13:55:15 -05:00
tresabhi 8c9a41d72b add getStaticPaths for 404 page 2026-08-09 13:35:21 -05:00
tresabhi 5bda754b6e branch names 2026-08-08 20:56:45 -05:00
tresabhi c5bb537bf2 shell paths debug 2026-08-08 20:49:39 -05:00
tresabhi c59779bedb move 404 to pages chunk 2026-08-08 20:48:06 -05:00
tresabhi 5059e382b3 some missing aliases 2026-08-08 20:45:09 -05:00
tresabhi 953570461c safer exit 2026-08-08 14:09:48 -05:00
tresabhi 6d527a7170 save on exit 2026-08-08 14:08:35 -05:00
tresabhi ffd74de79f quiet all 2026-08-08 14:02:20 -05:00
tresabhi 7c9ef9e5e0 quiet git add 2026-08-08 13:59:47 -05:00
tresabhi cd10d9ab4a some stats 2026-08-08 13:57:35 -05:00
tresabhi 578d4a3ff2 max ids 2026-08-08 13:52:07 -05:00
tresabhi 0cd01fa258 try streak detector 2026-08-08 13:42:16 -05:00
tresabhi 21b37ced8d ids update 2026-08-08T18:33:52.481Z 2026-08-08 13:33:52 -05:00
tresabhi 81856276b0 seed basing 2026-08-08 13:15:04 -05:00
tresabhi 389052c692 1 sec bro 2026-08-08 13:01:29 -05:00
tresabhi 09edd39a06 more! 2026-08-08 12:28:45 -05:00
tresabhi 5c3077033e more modern way of handling id discovery 2026-08-07 14:33:10 -05:00
tresabhi ef84d5b3b5 don't log paths 2026-08-07 13:34:56 -05:00
tresabhi d29f91afbc don't fail fast for now 2026-08-07 13:33:28 -05:00
tresabhi ab87282103 use PUBLIC_ENVIRONMENT 2026-08-07 13:33:05 -05:00
tresabhi 408417ca68 bruh i am ignorant 2026-08-07 13:30:25 -05:00
tresabhi b171ce2011 beta environment as default for now 2026-08-07 12:46:49 -05:00
tresabhi 58d0d92d4f on push 2026-08-07 12:36:10 -05:00
tresabhi 754f39f623 environment based deploys 2026-08-07 12:34:58 -05:00
tresabhi 10c8ac95a4 move api 2026-08-06 20:10:29 -05:00
tresabhi b367866a22 revert debug env 2026-08-06 20:02:09 -05:00
tresabhi b26c5137f3 aliases 2026-08-06 20:01:50 -05:00
tresabhi 13cda7aec0 environment configs 2026-08-06 19:37:07 -05:00
153 changed files with 1631 additions and 6735 deletions

No files matched your search

-55
View File
@@ -1,55 +0,0 @@
name: Averages
on:
workflow_dispatch:
schedule:
- cron: "0 8 * * *"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: "true"
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
token: ${{ secrets.GH_TOKEN }}
- uses: oven-sh/setup-bun@v2
- uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GH_TOKEN }}
- run: bun run setup
- name: Build averages
env:
PUBLIC_ASSET_BRANCH: main
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
run: bun run build:averages --production
+45
View File
@@ -0,0 +1,45 @@
name: Discover IDs
on:
push:
branches: production
schedule:
- cron: 0 */6 * * *
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
discover:
name: Discover IDs
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: production
submodules: recursive
token: ${{ secrets.GH_TOKEN }}
- name: Setup bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install
- name: Setup Git
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Discover IDs
run: bun run discover-ids
env:
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
GH_TOKEN: ${{ secrets.IDS_TOKEN }}
-38
View File
@@ -1,38 +0,0 @@
name: Reviews
on:
workflow_dispatch:
schedule:
- cron: 0 0 * * 2
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
token: ${{ secrets.GH_TOKEN }}
- uses: oven-sh/setup-bun@v2
- uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GH_TOKEN }}
- run: bun run setup
- name: Find reviews
run: bun run build:reviews
@@ -2,22 +2,73 @@ name: Website
on:
push:
workflow_dispatch:
schedule:
- cron: "0 4 * * *"
workflow_dispatch:
inputs:
branch:
description: Branch
type: choice
required: true
default: production
options:
- production
- main
environment:
description: Environment
type: choice
required: true
default: production
options:
- development
- beta
- production
- preview
- opentest
client_provider:
description: Client provider
type: choice
required: true
default: steam
options:
- steam
- zip
- wgpkg
client_source:
description: Client source
type: string
required: true
default: 444200/windows
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
group: ${{ github.workflow }}-${{ inputs.environment || github.ref }}
cancel-in-progress: true
env:
# client
STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }}
STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }}
CLIENT_PROVIDER: ${{ inputs.client_provider || 'steam' }}
CLIENT_SOURCE: ${{ inputs.client_source || '444200/windows' }}
CLIENT_DIR: ../../temp/game
# blitzkit
PUBLIC_BRANCH: main
PUBLIC_ENVIRONMENT: ${{ inputs.environment || 'production' }}
PUBLIC_PROMOTE_OPENTEST: false
PUBLIC_STATIC_ASSET_BASE: https://assets.blitzkit.app
PUBLIC_GUIDE_WEBSITE: https://guidesblitz.com
@@ -26,7 +77,6 @@ env:
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
GAME_DIR: ../../temp/game
# google
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
@@ -34,12 +84,6 @@ env:
# wargaming
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
# steam
STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }}
STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }}
STEAM_APP: 444200
STEAM_OS: windows
jobs:
build:
name: Build website
@@ -47,15 +91,9 @@ jobs:
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
include:
- chunk: default
node: blitzkit/blitzkit.github.io
path: dist/website
- chunk: api
node: blitzkit/api
path: dist/website/api
chunk: [default, api]
steps:
- name: Free disk space
@@ -64,6 +102,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ inputs.branch || 'production' }}
submodules: recursive
token: ${{ secrets.GH_TOKEN }}
@@ -75,44 +114,16 @@ jobs:
with:
repo-token: ${{ secrets.GH_TOKEN }}
- name: Configure pages
uses: actions/configure-pages@v6
- name: Setup CLIs
run: |
sudo apt-get update
sudo apt-get install -y aria2 libarchive-tools
- name: Install dependencies
run: bun install
- name: Setup DepotDownloader
run: |
REPO_USER="SteamRE"
REPO_NAME="DepotDownloader"
TEMP="temp"
OUT="$TEMP/$REPO_NAME"
ZIP="$TEMP/$REPO_NAME.zip"
URL=$(
curl -s "https://api.github.com/repos/$REPO_USER/$REPO_NAME/releases/latest" \
| jq -r '.assets[]
| select(.name | contains("linux-x64"))
| .browser_download_url' \
| head -n 1
)
mkdir -p "$TEMP"
curl -L "$URL" -o "$ZIP"
unzip -p "$ZIP" "$REPO_NAME" > "$OUT"
rm "$ZIP"
chmod +x "$OUT"
- name: Download game
run: |
TEMP="temp"
DEPOT_DOWNLOADER="$TEMP/DepotDownloader"
GAME="$TEMP/game"
$DEPOT_DOWNLOADER -os $STEAM_OS -app $STEAM_APP -username $STEAM_USERNAME -password $STEAM_PASSWORD -dir $GAME -max-downloads 256
- name: Download Client
run: bun run download-client
- name: Build
run: bun run --bun build
@@ -124,18 +135,24 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: chunk-${{ matrix.chunk }}
path: ${{ matrix.path }}
- name: Save artifact ID
run: |
echo "${{ steps.upload.outputs.artifact-id }}"
path: dist/website
- name: Trigger deployment
if: ${{ github.ref == 'refs/heads/production' }}
env:
TOKEN: ${{ secrets.GH_NODE_DEPLOY_TOKEN }}
ARTIFACT_ID: ${{ steps.upload.outputs.artifact-id }}
TARGET_REPO: ${{ matrix.node }}
CHUNK: ${{ matrix.chunk }}
run: |
ENV_FILE="environments/${PUBLIC_ENVIRONMENT}.json"
TARGET_REPO=$(jq -r ".nodes.\"$CHUNK\"" "$ENV_FILE")
if [ "$TARGET_REPO" == "null" ] || [ -z "$TARGET_REPO" ]; then
exit 1
fi
curl \
-X POST \
-H "Accept: application/vnd.github+json" \
@@ -146,7 +163,7 @@ jobs:
\"client_payload\": {
\"source_repo\": \"${{ github.repository }}\",
\"run_id\": \"${{ github.run_id }}\",
\"artifact_id\": \"$ARTIFACT_ID\",
\"chunk\": \"${{ matrix.chunk }}\"
\"artifact_id\": \"${{ steps.upload.outputs.artifact-id }}\",
\"chunk\": \"$CHUNK\"
}
}"
+1 -1
View File
@@ -1,7 +1,7 @@
node_modules
_node_modules
package-lock.json
*.env
.env
/test-results/
/playwright-report/
/playwright/.cache/
+15 -17
View File
@@ -24,20 +24,18 @@ If you intend to contribute to the code, note that you may need to check out sen
## Environment Variables
| Name | Source | BlitzKit Default | Memo |
| ------------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `PUBLIC_WARGAMING_APPLICATION_ID` | [Wargaming Developer's Room](https://developers.wargaming.net/) | | Application ID needed to communicate with Wargaming APIs. |
| `PUBLIC_PROMOTE_OPENTEST` | | `false` | Optional boolean dictating whether the website advertises [the opentest subdomain of BlitzKit](https://opentest.blitzkit.app/). |
| `ALLOW_ROBOTS` | | `true` | Toggles between `Allow: /` and `Disallow: /` in the website's `/robots.txt`. This is usually `false` for all BlitzKit's subdomains. |
| `WOTB_GLOSSARY`, `WOTB_GLOSSARY_DOMAIN`, `WOTB_GLOSSARY_PATH` | | | |
| `PUBLIC_GOOGLE_TAG_MANAGER_ID` | [Google Tag Manager](https://tagmanager.google.com/) | `G-GL2JVHCGPQ` | Google Tag Manager's unique identifier for analytical purposes. |
| `PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID` | [Google Analytics](https://analytics.google.com/) | `430711767` | Google Analytics' unique property ID for [BlitzKit's apex domain](https://blitzkit.app/) and subdomains. |
| `GOOGLE_APPLICATION_CREDENTIALS` | [Google Cloud](https://cloud.google.com/) | | Google Cloud's API key, primarily used for ranking popular tanks and finding relevant tank review videos in Tankopedia. |
| `PUBLIC_DISCORD_SERVER_ID` | [BlitzKit Discord Server](https://discord.gg/nDt7AjGJQH) | `734786591205359697` | BlitzKit Discord Server's id. |
| `PUBLIC_ASSET_BASE` | [blitzkit/assets](https://github.com/blitzkit/assets) | `https://blitzkit.github.io/assets` | The root domain and path that serves the external assets like tank models and icons. |
| `PUBLIC_BRANCH` | | `main` | The branch that the website is operating on. This is completely nominal. |
| `PUBLIC_GUIDE_WEBSITE` | [GuidesBlitz](https://guidesblitz.com/) | `https://guidesblitz.com` | The website that serves the encyclopedia-like content in BlitzKit. |
| `SERVICING` | | `false` | Wether the website is down for servicing. |
| `GH_TOKEN` | [GitHub Fine-Grained Tokens](https://github.com/settings/personal-access-tokens) | | The GitHub API key used for checking out private submodules and pushing content to the asset repository. |
| `DATABASE_URL` | [CockroachDB](https://www.cockroachlabs.com/) | | The CockroachDB-SQL database URL used to link Wargaming and Discord users. |
| `WOTB_DLC_CDN` | | | |
| Name | Source | BlitzKit Default | Memo |
| ------------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `PUBLIC_WARGAMING_APPLICATION_ID` | [Wargaming Developer's Room](https://developers.wargaming.net/) | | Application ID needed to communicate with Wargaming APIs. |
| `PUBLIC_PROMOTE_OPENTEST` | | `false` | Optional boolean dictating whether the website advertises [the opentest subdomain of BlitzKit](https://opentest.blitzkit.app/). |
| `WOTB_GLOSSARY`, `WOTB_GLOSSARY_DOMAIN`, `WOTB_GLOSSARY_PATH` | | | |
| `PUBLIC_GOOGLE_TAG_MANAGER_ID` | [Google Tag Manager](https://tagmanager.google.com/) | `G-GL2JVHCGPQ` | Google Tag Manager's unique identifier for analytical purposes. |
| `PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID` | [Google Analytics](https://analytics.google.com/) | `430711767` | Google Analytics' unique property ID for [BlitzKit's apex domain](https://blitzkit.app/) and subdomains. |
| `GOOGLE_APPLICATION_CREDENTIALS` | [Google Cloud](https://cloud.google.com/) | | Google Cloud's API key, primarily used for ranking popular tanks and finding relevant tank review videos in Tankopedia. |
| `PUBLIC_DISCORD_SERVER_ID` | [BlitzKit Discord Server](https://discord.gg/nDt7AjGJQH) | `734786591205359697` | BlitzKit Discord Server's id. |
| `PUBLIC_ASSET_BASE` | [blitzkit/assets](https://github.com/blitzkit/assets) | `https://blitzkit.github.io/assets` | The root domain and path that serves the external assets like tank models and icons. |
| `PUBLIC_GUIDE_WEBSITE` | [GuidesBlitz](https://guidesblitz.com/) | `https://guidesblitz.com` | The website that serves the encyclopedia-like content in BlitzKit. |
| `SERVICING` | | `false` | Wether the website is down for servicing. |
| `GH_TOKEN` | [GitHub Fine-Grained Tokens](https://github.com/settings/personal-access-tokens) | | The GitHub API key used for checking out private submodules and pushing content to the asset repository. |
| `DATABASE_URL` | [CockroachDB](https://www.cockroachlabs.com/) | | The CockroachDB-SQL database URL used to link Wargaming and Discord users. |
| `WOTB_DLC_CDN` | | | |
+44 -21
View File
@@ -30,7 +30,7 @@
"@octokit/plugin-throttling": "^11.0.1",
"@octokit/rest": "^22.0.0",
"@types/adm-zip": "^0.5.7",
"@types/bun": "^1.3.1",
"@types/bun": "^1.3.14",
"@types/lodash-es": "^4.17.12",
"@types/lz4js": "^0.2.1",
"@types/three": "^0.180.0",
@@ -63,7 +63,7 @@
"@gltf-transform/functions": "^4.2.1",
"@octokit/plugin-throttling": "^11.0.2",
"@octokit/rest": "^22.0.0",
"@types/bun": "^1.3.0",
"@types/bun": "^1.3.14",
"@types/decompress": "^4.2.7",
"@types/lodash-es": "^4.17.12",
"@types/lz4js": "^0.2.1",
@@ -78,6 +78,7 @@
"fast-xml-parser": "^5.2.5",
"googleapis": "^162.0.0",
"lodash-es": "^4.17.21",
"lz4-napi": "^2.9.1",
"progress": "^2.0.3",
"puppeteer": "^24.22.3",
"sharp": "^0.34.4",
@@ -144,6 +145,40 @@
},
},
"packages": {
"@antoniomuso/lz4-napi-android-arm-eabi": ["@antoniomuso/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-KcRvkSOymDvxFDT9AGFBc7g+J/Xv4qExMMHhtxjZ45qE2sEIVCXoSFmyLDiVVKztkVzoV24VXPPRLOa4WQOi2Q=="],
"@antoniomuso/lz4-napi-android-arm64": ["@antoniomuso/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-BNiddtI+5Z83w1jIufTFhTPrtxMVKb0UPezTq47XIE/7vkB6y5mHSWSlcHqEIFFIg2/PVdqyqec1CXv0bkpCPA=="],
"@antoniomuso/lz4-napi-darwin-arm64": ["@antoniomuso/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-nhLu+8g8Q6Yqd0rxRn4JkCMJBUs6mByiyT3Mhha2EgorsA2txx18JKm9qLhnXpDgFGWc94tLEVHqzDk0tLD9yA=="],
"@antoniomuso/lz4-napi-darwin-x64": ["@antoniomuso/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-DqkN1D/IrqOaUBppCP7BrF9j5Usb6RqFyz16kf3SHPDxLG7pmemgwXmGWpcNrKQfp4Shx1VkbFWA61gMyE1+uw=="],
"@antoniomuso/lz4-napi-freebsd-x64": ["@antoniomuso/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ORtZ27l+j0yDgPMgt9q/j10LCchtKS44gFohEWl8fokgr5rzDAYAtMyQZzHV8sqE2LUWHAU3Gk6Wh9s1GRl9ow=="],
"@antoniomuso/lz4-napi-linux-arm-gnueabihf": ["@antoniomuso/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-GBZgQ/rY0AzjZA4jOKFXmLQKVPGXglVZ3U7SLcCu2nmuHTO73vsqiPeAHnYu7/org1km7yw3ii6XFlBRzyNKIA=="],
"@antoniomuso/lz4-napi-linux-arm64-gnu": ["@antoniomuso/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-9lvGts14KHNT4pc9lKYUPI1PRdBkfbyT4HCjyDk8fJJzEG9ryvLvyhTeFr/IVpJOFmkSodZw1nxJMQDT5+zvlQ=="],
"@antoniomuso/lz4-napi-linux-arm64-musl": ["@antoniomuso/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-SsidKyLRHGB/rrgic+N7ZxFjjl+Cbav2sbuSj4AJsc37oVhyjzbiI5EEL9Rl+zL4ajL0d3utG3UbIUooThhykQ=="],
"@antoniomuso/lz4-napi-linux-ppc64-gnu": ["@antoniomuso/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1Joz9RmBLiLIfI5Gk/wmJI/WwQy74prKDhLcCPFoa/FcTOJavJAopAzs1MQdnM7nGklEPOpKZWmKHZz1yQ4LMQ=="],
"@antoniomuso/lz4-napi-linux-riscv64-gnu": ["@antoniomuso/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-3z6jSvhEpnSMlubcHwSn6W8Xziq5LYSuIcSpSo7wTEOopnr6GNnKcH2uTPFsRiqC/aC2TLpm17ktXbYTrYL7jg=="],
"@antoniomuso/lz4-napi-linux-s390x-gnu": ["@antoniomuso/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-wtQg/ikF1JutKdWfUdGXvR0LG+eG49mS01luwJXG6sg40DX737T3BAsbjM9Fccvif5jQwC+KmzP8+tvxy2eMtQ=="],
"@antoniomuso/lz4-napi-linux-x64-gnu": ["@antoniomuso/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-Vw7vb3i1Q6QU2sI5GdgZdi4cVwN2xZ44Gw/WawIxCno1gX39VIF/j8T1pYwS7Dc67NeoA37LsYIPb3h49CiRRA=="],
"@antoniomuso/lz4-napi-linux-x64-musl": ["@antoniomuso/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-oZVrvOPjnI7c07v1lx6HMLFi8xlW9WS0pHmkaisldybNUWmNI6vR44KoqBctGCFplvF5YVMK2xPs3W04UGzF8w=="],
"@antoniomuso/lz4-napi-openharmony-arm64": ["@antoniomuso/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-mkDQv6C0JbMbCDn070Dv67SWfkDy8uD6DkqUT373LHjdaEXj/0eK2gAVykKyPdnC1RBhONgUCaV4HQOp63oH8Q=="],
"@antoniomuso/lz4-napi-win32-arm64-msvc": ["@antoniomuso/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-EBsN9YzV3xpSPr6h/t6ntBetpdtO0nZw9o4sZ1vNOfLJ8FMdHxHiY7FonzhEyTnc4N3x3yMqPudD6zLVy/oNXg=="],
"@antoniomuso/lz4-napi-win32-ia32-msvc": ["@antoniomuso/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-Fqe2JmcfjvVrC+BHjHPzoiIzNgjHpQvDO7duMbJh5fW9VvF7pKkjRAPDwhtFhkU/fBLVQywq5WbsFqtJ3pYG/w=="],
"@antoniomuso/lz4-napi-win32-x64-msvc": ["@antoniomuso/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-N6bAJ4pcYfbiuysHNyEl6uVnYxl0CBboPeA6Iz1GQWJb6mp8J/z4+E8bTvasMuRhIhEW5F6X+oJIwmOwSzHoIQ=="],
"@apideck/better-ajv-errors": ["@apideck/[email protected]", "", { "dependencies": { "json-schema": "^0.4.0", "jsonpointer": "^5.0.0", "leven": "^3.1.0" }, "peerDependencies": { "ajv": ">=8" } }, "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA=="],
"@astrojs/check": ["@astrojs/[email protected]", "", { "dependencies": { "@astrojs/language-server": "^2.15.0", "chokidar": "^4.0.1", "kleur": "^4.1.5", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "bin": { "astro-check": "dist/bin.js" } }, "sha512-IOheHwCtpUfvogHHsvu0AbeRZEnjJg3MopdLddkJE70mULItS/Vh37BHcI00mcOJcH1vhD3odbpvWokpxam7xA=="],
@@ -456,11 +491,11 @@
"@fontsource/inter": ["@fontsource/[email protected]", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
"@gltf-transform/core": ["@gltf-transform/core@4.2.1", "", { "dependencies": { "property-graph": "^3.0.0" } }, "sha512-qKhrQ29KJ9K6sz7xGoGRllqPBlROjPJMQPvCLBOfgMPH3S/Ph5E0e6fD5WmwTY5QPyBsDMRqKd1Vl0ncThTXGw=="],
"@gltf-transform/core": ["@gltf-transform/core@4.4.2", "", { "dependencies": { "property-graph": "^4.1.0" } }, "sha512-qsWKwNSwK+2s834Mt4xbYcyHqCrgNFP7hIv5s487JxebngRfDgelpghNF+kSswGb2/NuapasfK3UViFoSJJoMg=="],
"@gltf-transform/extensions": ["@gltf-transform/[email protected]", "", { "dependencies": { "@gltf-transform/core": "^4.4.2", "ktx-parse": "^1.1.0" } }, "sha512-HJH1FM+edC5eNvl6xO0SOXJ/j/3oDoIpSu150OTdJaLBoM3TgCCGIfh4wyhgWAqZrkvgHKVGiZKxcKV5LkgPCQ=="],
"@gltf-transform/functions": ["@gltf-transform/functions@4.2.1", "", { "dependencies": { "@gltf-transform/core": "^4.2.1", "@gltf-transform/extensions": "^4.2.1", "ktx-parse": "^1.0.1", "ndarray": "^1.0.19", "ndarray-lanczos": "^0.3.0", "ndarray-pixels": "^5.0.1" } }, "sha512-hFCI80N6zt7yHlqXbzlYEYQbFwK+2OOZNvacrRtrWiCWKI0k1Ad5+nZFsmuIZZV03aXb5XG6UF7JINT3wETS1Q=="],
"@gltf-transform/functions": ["@gltf-transform/functions@4.4.2", "", { "dependencies": { "@gltf-transform/core": "^4.4.2", "@gltf-transform/extensions": "^4.4.2", "ktx-parse": "^1.1.0", "ndarray": "^1.0.19", "ndarray-lanczos": "^0.3.0", "ndarray-pixels": "^5.0.1" } }, "sha512-dclXgv9TshMaWBqPDUYd4xTwBQ2PpuR8p0Y9pokrRzGQDUPXRP6lTDzbqT0UmEmxFSvRyPJjvOWUmzeiRafpvw=="],
"@img/colour": ["@img/[email protected]", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="],
@@ -860,7 +895,7 @@
"@types/babel__traverse": ["@types/[email protected]", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
"@types/bun": ["@types/[email protected].9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
"@types/bun": ["@types/[email protected].14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
"@types/bytebuffer": ["@types/[email protected]", "", { "dependencies": { "@types/long": "^3.0.0", "@types/node": "*" } }, "sha512-lV4YLiolMdD4upDmr4vnfiwV/FN9Jg33eNTSFMkHqyMKqTIAn5TmFTYsARwXwUXeFU7yzRpECmWV1SOhIvdkRQ=="],
@@ -1076,7 +1111,7 @@
"buffer-from": ["[email protected]", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"bun-types": ["[email protected].9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
"bun-types": ["[email protected].14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
"bytebuffer": ["[email protected]", "", { "dependencies": { "long": "~3" } }, "sha512-IuzSdmADppkZ6DlpycMkm8l9zeEq16fWtLvunEwFiYciR/BHo4E8/xs5piFquG+Za8OWmMqHF8zuRviz2LHvRQ=="],
@@ -1664,6 +1699,8 @@
"lru-cache": ["[email protected]", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"lz4-napi": ["[email protected]", "", { "optionalDependencies": { "@antoniomuso/lz4-napi-android-arm-eabi": "2.9.1", "@antoniomuso/lz4-napi-android-arm64": "2.9.1", "@antoniomuso/lz4-napi-darwin-arm64": "2.9.1", "@antoniomuso/lz4-napi-darwin-x64": "2.9.1", "@antoniomuso/lz4-napi-freebsd-x64": "2.9.1", "@antoniomuso/lz4-napi-linux-arm-gnueabihf": "2.9.1", "@antoniomuso/lz4-napi-linux-arm64-gnu": "2.9.1", "@antoniomuso/lz4-napi-linux-arm64-musl": "2.9.1", "@antoniomuso/lz4-napi-linux-ppc64-gnu": "2.9.1", "@antoniomuso/lz4-napi-linux-riscv64-gnu": "2.9.1", "@antoniomuso/lz4-napi-linux-s390x-gnu": "2.9.1", "@antoniomuso/lz4-napi-linux-x64-gnu": "2.9.1", "@antoniomuso/lz4-napi-linux-x64-musl": "2.9.1", "@antoniomuso/lz4-napi-openharmony-arm64": "2.9.1", "@antoniomuso/lz4-napi-win32-arm64-msvc": "2.9.1", "@antoniomuso/lz4-napi-win32-ia32-msvc": "2.9.1", "@antoniomuso/lz4-napi-win32-x64-msvc": "2.9.1" } }, "sha512-VjY1TAUQa3bo8j4vCcpQ0RBXh4mbC1OFHWH9rpOvx8qLYwaZTEzwxW/648ufD2cmz5/pFfPLDkX6N8+TqI9BYQ=="],
"lz4js": ["[email protected]", "", {}, "sha512-gY2Ia9Lm7Ep8qMiuGRhvUq0Q7qUereeldZPP1PMEJxPtEWHJLqw9pgX68oHajBH0nzJK4MaZEA/YNV3jT8u8Bg=="],
"lzma": ["[email protected]", "", { "bin": { "lzma.js": "bin/lzma.js" } }, "sha512-DcfiawQ1avYbW+hsILhF38IKAlnguc/fjHrychs9hdxe4qLykvhT5VTGNs5YRWgaNePh7NTxGD4uv4gKsRomCQ=="],
@@ -1930,7 +1967,7 @@
"prompts": ["[email protected]", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
"property-graph": ["property-graph@3.0.0", "", {}, "sha512-TnzxUsttmGtw+OiU0LDw+0FlMbJ8vV8pOjyDI7+Kdni4Tj0hW5BFh7TatQu7Y68hcvvFmiFOHilKShsA4R82fA=="],
"property-graph": ["property-graph@4.1.0", "", {}, "sha512-AvPcP7XECNWy4LGmFQ77k7un4lSKM4eS29PTvW4ck95uYeLxXPWJM7hLuBqK91FaHqCcgJvIUCuNJjjxKE7VKQ=="],
"property-information": ["[email protected]", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
@@ -2524,16 +2561,6 @@
"@babel/preset-env/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@blitzkit/scripts/@gltf-transform/extensions": ["@gltf-transform/[email protected]", "", { "dependencies": { "@gltf-transform/core": "^4.2.1", "ktx-parse": "^1.0.1" } }, "sha512-ieHSJU9qvb3ucWDxgHFcff+C7fLtFPa6G0Wtc/ki7GsN6Rh7o9eoyqi7Ggj2LO4i3ynCwLLPb/onbJKw2fBtsA=="],
"@blitzkit/website/@gltf-transform/core": ["@gltf-transform/[email protected]", "", { "dependencies": { "property-graph": "^4.1.0" } }, "sha512-qsWKwNSwK+2s834Mt4xbYcyHqCrgNFP7hIv5s487JxebngRfDgelpghNF+kSswGb2/NuapasfK3UViFoSJJoMg=="],
"@blitzkit/website/@gltf-transform/functions": ["@gltf-transform/[email protected]", "", { "dependencies": { "@gltf-transform/core": "^4.4.2", "@gltf-transform/extensions": "^4.4.2", "ktx-parse": "^1.1.0", "ndarray": "^1.0.19", "ndarray-lanczos": "^0.3.0", "ndarray-pixels": "^5.0.1" } }, "sha512-dclXgv9TshMaWBqPDUYd4xTwBQ2PpuR8p0Y9pokrRzGQDUPXRP6lTDzbqT0UmEmxFSvRyPJjvOWUmzeiRafpvw=="],
"@gltf-transform/extensions/@gltf-transform/core": ["@gltf-transform/[email protected]", "", { "dependencies": { "property-graph": "^4.1.0" } }, "sha512-qsWKwNSwK+2s834Mt4xbYcyHqCrgNFP7hIv5s487JxebngRfDgelpghNF+kSswGb2/NuapasfK3UViFoSJJoMg=="],
"@gltf-transform/functions/@gltf-transform/extensions": ["@gltf-transform/[email protected]", "", { "dependencies": { "@gltf-transform/core": "^4.2.1", "ktx-parse": "^1.0.1" } }, "sha512-ieHSJU9qvb3ucWDxgHFcff+C7fLtFPa6G0Wtc/ki7GsN6Rh7o9eoyqi7Ggj2LO4i3ynCwLLPb/onbJKw2fBtsA=="],
"@jimp/core/file-type": ["[email protected]", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
"@octokit/endpoint/@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
@@ -2714,10 +2741,6 @@
"yaml-language-server/yaml": ["[email protected]", "", {}, "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA=="],
"@blitzkit/website/@gltf-transform/core/property-graph": ["[email protected]", "", {}, "sha512-AvPcP7XECNWy4LGmFQ77k7un4lSKM4eS29PTvW4ck95uYeLxXPWJM7hLuBqK91FaHqCcgJvIUCuNJjjxKE7VKQ=="],
"@gltf-transform/extensions/@gltf-transform/core/property-graph": ["[email protected]", "", {}, "sha512-AvPcP7XECNWy4LGmFQ77k7un4lSKM4eS29PTvW4ck95uYeLxXPWJM7hLuBqK91FaHqCcgJvIUCuNJjjxKE7VKQ=="],
"@octokit/endpoint/@octokit/types/@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="],
"@octokit/graphql/@octokit/types/@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="],
+40
View File
@@ -0,0 +1,40 @@
# BlitzKit 1.31.0
This update massively upgrades BlitzKit's graphical fidelity (while making it faster) and updates the way BlitzKit deploys updates, speeding up future versions.
## Features
- Improved the performance of the Tank Sandbox in Tankopedia
- Reduced the number of lights by 25%
- Reduced the resolution of shadows by 50%
- Improved the graphical fidelity of tanks in the Tankopedia
- Added soft contact shadows below tanks
- Added sky and ground reflected lighting to the top and bottom of tanks
- Removed the fog that used to darken tanks when zoomed far away
- Made BlitzKitLand™ fade out circularly to hide the sharp edges
- Pointed the lights 1.5m above the ground
- This genuinely makes BlitzKit almost photo-realistic in my opinion
- Non-PBR tanks are now lit up brighter to accommodate for their dark textures
## Fixes
- Fixed a bug where the ambient occlusion map (a measure of how dark a part of a tank gets due to low light bounces) was applied to the color of the tank directly, making many tanks appear dark even in direct light
- Tanks with chinese characters now generate urls through transliteration
## Known Issues
- The flags found behind the selected tank in the tech tree section of the Tankopedia is offset incorrectly
## Technical Features
- BlitzKit can now calculate and store performance statistics and account details for up 10,000,000,000 players
- This will help make the BlitzKit Performance tool more accurate, and play well with an upcoming, unannounced major feature
## Technical Changes
- The `production` branch now deploys updates to `blitzkit.app` instead of the `main` branch
- `robots.txt` now disables indexing of BlitzKit variants based on the environment
- The website is now compiled in "chunks" of which the `api` chunk now takes on the task of mangling what was previously stored in `assets3`
- `assets3` will be removed in `1.31.1`, please migrate to the new `api` if you use any BlitzKit assets
- Removed the `bot` and `app` packages
- Removed Prisma
+3
View File
@@ -0,0 +1,3 @@
# BlitzKit 1.31.1
This update adds capabilities to release BlitzKit OpenTest and Preview.
+17
View File
@@ -0,0 +1,17 @@
{
"robots": false,
"canonical": "https://blitzkit.app",
"paths": {
"default": null,
"api": null
},
"nodes": {
"default": "blitzkit/deploy-node-beta.blitzkit.app",
"api": "blitzkit/deploy-node-api.beta.blitzkit.app"
},
"aliases": {
"api": "https://api.beta.blitzkit.app",
"static": "https://static.beta.blitzkit.app"
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"robots": false,
"canonical": "https://blitzkit.app",
"paths": {
"default": null,
"api": "api"
},
"nodes": {},
"aliases": {
"api": "/api",
"static": "https://static.blitzkit.app"
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"robots": false,
"canonical": "https://blitzkit.app",
"paths": {
"default": null,
"api": null
},
"nodes": {
"default": "blitzkit/deploy-node-opentest.blitzkit.app",
"api": "blitzkit/deploy-node-api.opentest.blitzkit.app"
},
"aliases": {
"api": "https://api.opentest.blitzkit.app",
"static": "https://static.opentest.blitzkit.app"
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"robots": false,
"canonical": "https://blitzkit.app",
"paths": {
"default": null,
"api": null
},
"nodes": {
"default": "blitzkit/deploy-node-preview.blitzkit.app",
"api": "blitzkit/deploy-node-api.preview.blitzkit.app"
},
"aliases": {
"api": "https://api.preview.blitzkit.app",
"static": "https://static.preview.blitzkit.app"
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"robots": true,
"canonical": "https://blitzkit.app",
"paths": {
"default": null,
"api": null
},
"nodes": {
"default": "blitzkit/deploy-node-blitzkit.app",
"api": "blitzkit/deploy-node-api.blitzkit.app"
},
"aliases": {
"api": "https://api.blitzkit.app",
"static": "https://static.blitzkit.app"
}
}
+5 -2
View File
@@ -1,12 +1,15 @@
{
"name": "blitzkit",
"version": "1.31.0",
"version": "1.31.1",
"license": "BlitzKit License",
"private": true,
"scripts": {
"dev": "cd packages/website && bun run dev",
"build": "cd packages/website && bun run build",
"build:averages": "cd packages/scripts && bun run build:averages"
"discover-ids": "cd packages/scripts && bun run discover-ids",
"build-protos": "cd packages/scripts && bun run build-protos",
"record-performance": "cd packages/scripts && bun run record-performance",
"download-client": "cd packages/scripts && bun run download-client"
},
"type": "module",
"workspaces": [
+1 -1
View File
@@ -9,7 +9,7 @@
"@octokit/plugin-throttling": "^11.0.1",
"@octokit/rest": "^22.0.0",
"@types/adm-zip": "^0.5.7",
"@types/bun": "^1.3.1",
"@types/bun": "^1.3.14",
"@types/lodash-es": "^4.17.12",
"@types/lz4js": "^0.2.1",
"@types/three": "^0.180.0",
@@ -1,5 +0,0 @@
import { LEAGUES } from "./rating";
export function getLeagueFromScore(score: number) {
return LEAGUES.find(({ minScore }) => score >= minScore)!;
}
-42
View File
@@ -1,42 +0,0 @@
import { context } from "../blitzkit/context";
import { patientFetch } from "../blitzkit/patientFetch";
import type { RatingLeague, RatingReward } from "./rating";
import type { Region } from "./regions";
import { regionToRegionSubdomain } from "./regionToRegionSubdomain";
export type RatingInfo =
| {
detail: undefined;
title: string;
icon: string | null;
start_at: string;
finish_at: string;
current_season: number;
updated_at: string;
count: number;
rewards: RatingReward[];
leagues: RatingLeague[];
}
| { detail: { error: string } };
const cache: Partial<Record<Region, RatingInfo>> = {};
export async function getRatingInfo(region: Region) {
if (cache[region]) return cache[region]!;
const response = await patientFetch(
context === "website"
? `/api/${region}/rating/current/info`
: `https://${regionToRegionSubdomain(
region
)}.wotblitz.com/en/api/rating-leaderboards/season/`,
undefined
);
const data = (await response.json()) as RatingInfo;
cache[region] = data;
return data;
}
@@ -1,29 +0,0 @@
import { context } from "../blitzkit/context";
import { patientFetch } from "../blitzkit/patientFetch";
import type { RatingPlayer } from "../statistics";
import type { LeagueTop } from "./rating";
import type { Region } from "./regions";
import { regionToRegionSubdomain } from "./regionToRegionSubdomain";
export async function getRatingLeague(
region: Region,
league: number,
usePatientFetcher = false
) {
const fetcher = (url: string) =>
usePatientFetcher
? patientFetch(url, undefined, { cache: "no-store" })
: fetch(url, { cache: "no-store" });
const response = await fetcher(
context === "website"
? `/api/${region}/rating/current/league/${league}`
: `https://${regionToRegionSubdomain(
region
)}.wotblitz.com/en/api/rating-leaderboards/league/${league}/top/`
);
const json = await response.json();
return context === "website"
? (json as RatingPlayer[])
: (json as LeagueTop).result;
}
@@ -1,27 +0,0 @@
import { patientFetch } from "../blitzkit/patientFetch";
import { withCORSProxy } from "../blitzkit/withCORSProxy";
import type { RatingNeighbors } from "./rating";
import type { Region } from "./regions";
import { regionToRegionSubdomain } from "./regionToRegionSubdomain";
export async function getRatingNeighbors(
region: Region,
id: number,
count: number,
usePatientFetcher = false
) {
const fetcher = (url: string) =>
usePatientFetcher
? patientFetch(url, undefined, { cache: "no-store" })
: fetch(url, { cache: "no-store" });
const response = await fetcher(
withCORSProxy(
`https://${regionToRegionSubdomain(
region
)}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${count}`
)
);
const json = (await response.json()) as RatingNeighbors;
return json;
}
-6
View File
@@ -10,13 +10,8 @@ export * from "./getAccountAchievements";
export * from "./getAccountInfo";
export * from "./getClanAccountInfo";
export * from "./getClanInfo";
export * from "./getLeagueFromScore";
export * from "./getRatingInfo";
export * from "./getRatingLeague";
export * from "./getRatingNeighbors";
export * from "./getTankStats";
export * from "./idToRegion";
export * from "./isOnGoingRatingSeason";
export * from "./isValidBlitzId";
export * from "./nations";
export * from "./normalizeExtra";
@@ -28,6 +23,5 @@ export * from "./regions";
export * from "./regionSubdomainToRegion";
export * from "./regionToRegionSubdomain";
export * from "./searchClansAcrossRegions";
export * from "./searchCurrentRatingPlayers";
export * from "./searchPlayersAcrossRegions";
export * from "./toUniqueId";
@@ -1,6 +0,0 @@
import { getRatingInfo } from "./getRatingInfo";
export async function isOnGoingRatingSeason() {
const ratingInfo = await getRatingInfo("com");
return ratingInfo.detail === undefined;
}
@@ -1,40 +0,0 @@
import { context } from "../blitzkit/context";
import { type Region } from "./regions";
import { regionToRegionSubdomain } from "./regionToRegionSubdomain";
type CurrentRatingPlayerSearch = Record<
number,
{
spa_id: number;
nickname: string;
clan_tag: string;
} & (
| {
skip: true;
}
| {
skip: false;
mmr: number;
season_number: number;
calibrationBattlesLeft: number;
number: number;
updated_at: string;
neighbors: [];
}
)
>;
export async function searchCurrentRatingPlayers(region: Region, name: string) {
const encodedSearch = encodeURIComponent(name);
const response = await fetch(
context === "website"
? `/api/${region}/rating/current/search/${encodedSearch}`
: `https://${regionToRegionSubdomain(
region
)}.wotblitz.com/en/api/rating-leaderboards/search/?prefix=${encodedSearch}`,
{ cache: "no-store" }
);
const accountList = (await response.json()) as CurrentRatingPlayerSearch;
return accountList;
}
+13
View File
@@ -0,0 +1,13 @@
import { environmentConfig } from "@blitzkit/core";
export function alias(alias: string, path: string) {
const environment = environmentConfig();
if (!(alias in environment.aliases)) {
throw new Error(`Invalid alias "${alias}"`);
}
const base = environment.aliases[alias];
return `${base}${path}`;
}
+27 -9
View File
@@ -1,3 +1,4 @@
import { alias } from "@blitzkit/core";
import { fetchPB } from "../../protobuf";
import {
CamouflageDefinitions,
@@ -5,6 +6,7 @@ import {
EquipmentDefinitions,
Gallery,
GameDefinitions,
MapDefinitions,
ModelDefinitions,
ProvisionDefinitions,
SkillDefinitions,
@@ -14,38 +16,54 @@ import { BlitzKitAPI } from "./base";
export class ClientBlitzKitAPI extends BlitzKitAPI {
gameDefinitions() {
return fetchPB("/api/definitions/game.pb", GameDefinitions);
return fetchPB(alias("api", "/definitions/game.pb"), GameDefinitions);
}
consumableDefinitions() {
return fetchPB("/api/definitions/consumables.pb", ConsumableDefinitions);
return fetchPB(
alias("api", "/definitions/consumables.pb"),
ConsumableDefinitions,
);
}
tankDefinitions() {
return fetchPB("/api/definitions/tanks.pb", TankDefinitions);
return fetchPB(alias("api", "/definitions/tanks.pb"), TankDefinitions);
}
camouflageDefinitions() {
return fetchPB("/api/definitions/camouflage.pb", CamouflageDefinitions);
return fetchPB(
alias("api", "/definitions/camouflage.pb"),
CamouflageDefinitions,
);
}
provisionDefinitions() {
return fetchPB("/api/definitions/provisions.pb", ProvisionDefinitions);
return fetchPB(
alias("api", "/definitions/provisions.pb"),
ProvisionDefinitions,
);
}
modelDefinitions() {
return fetchPB("/api/definitions/models.pb", ModelDefinitions);
return fetchPB(alias("api", "/definitions/models.pb"), ModelDefinitions);
}
skillDefinitions() {
return fetchPB("/api/definitions/skills.pb", SkillDefinitions);
return fetchPB(alias("api", "/definitions/skills.pb"), SkillDefinitions);
}
equipmentDefinitions() {
return fetchPB("/api/definitions/equipment.pb", EquipmentDefinitions);
return fetchPB(
alias("api", "/definitions/equipment.pb"),
EquipmentDefinitions,
);
}
galleryDefinitions() {
return fetchPB("/api/definitions/gallery.pb", Gallery);
return fetchPB(alias("api", "/definitions/gallery.pb"), Gallery);
}
mapDefinitions() {
return fetchPB(alias("api", "/definitions/maps.pb"), MapDefinitions);
}
}
@@ -1,8 +1,8 @@
import { Avatar, fetchGlossary, Gallery } from "@blitzkit/core";
import { ServerBlitzKitAPI9 } from "@blitzkit/core/src/blitzkit/api/server/9_gameDefinitions";
import locales from "@blitzkit/i18n/locales.json";
import { extname } from "path";
import { Cache } from "./0_base";
import { ServerBlitzKitAPI9 } from "./9_gameDefinitions";
export abstract class ServerBlitzKitAPI10 extends ServerBlitzKitAPI9 {
@Cache()
+47
View File
@@ -0,0 +1,47 @@
import { assertSecret } from "@blitzkit/core";
interface EnvironmentConfig {
robots: boolean;
canonical: string;
paths: Record<string, string | null>;
nodes: Record<string, string>;
aliases: Record<string, string>;
}
let hasReadEnvironments = false;
const environments: Record<string, EnvironmentConfig> = {};
/**
* This is set up this way to avoid errors in runtimes where
* `import.meta.glob` does not exist.
*/
function readEnvironments() {
if (hasReadEnvironments) return;
hasReadEnvironments = true;
const glob = import.meta.glob("../../../../environments/*.json", {
eager: true,
import: "default",
}) as Record<string, EnvironmentConfig>;
const root = "../../../../environments";
for (const key in glob) {
const trimmed = key.slice(root.length + 1, -5);
environments[trimmed] = glob[key];
delete environments[key];
}
}
export function environmentConfig() {
readEnvironments();
const environment = assertSecret(import.meta.env.PUBLIC_ENVIRONMENT);
if (!(environment in environments)) {
throw new Error(`Invalid environment "${environment}"`);
}
return environments[environment];
}
@@ -1,49 +0,0 @@
import { getRatingInfo } from "../blitz/getRatingInfo";
import { assertSecret } from "./assertSecret";
interface GitHubTrees {
tree: { path: string; url: string }[];
}
let cachedLatestArchivedSeasonNumber: number | null = null;
export async function getArchivedLatestSeasonNumber() {
if (cachedLatestArchivedSeasonNumber !== null) {
return cachedLatestArchivedSeasonNumber;
}
const ratingInfo = await getRatingInfo("com");
if (ratingInfo.detail) {
const regionsURL = (
(await fetch(
`https://api.github.com/repos/${assertSecret(
import.meta.env.PUBLIC_ASSET_REPO
)}/git/trees/main`
).then((response) => response.json())) as GitHubTrees
).tree.find(({ path }) => path === "regions")!.url;
const comURL = (
(await fetch(regionsURL).then((response) =>
response.json()
)) as GitHubTrees
).tree.find(({ path }) => path === "com")!.url;
const ratingURL = (
(await fetch(comURL).then((response) => response.json())) as {
tree: [{ url: string }];
}
).tree[0].url;
const latestSeason = Math.max(
...(
(await fetch(ratingURL).then((response) => response.json())) as {
tree: { path: string }[];
}
).tree.map(({ path }) => Number(path))
);
cachedLatestArchivedSeasonNumber = latestSeason;
} else {
cachedLatestArchivedSeasonNumber = ratingInfo.current_season - 1;
}
return cachedLatestArchivedSeasonNumber;
}
@@ -1,30 +0,0 @@
// import { RatingInfo, Region, asset } from '@blitzkit/core';
// const cache: Record<
// Region,
// Record<
// number,
// RatingInfo & {
// detail: undefined;
// }
// >
// > = {
// com: {},
// eu: {},
// asia: {},
// };
// export async function getArchivedRatingInfo(region: Region, season: number) {
// if (!cache[region][season]) {
// const info = await fetch(
// asset(`regions/${region}/rating/${season}/info.cdon.lz4`),
// )
// .then((response) => response.arrayBuffer())
// .then((buffer) =>
// superDecompress<RatingInfo & { detail: undefined }>(buffer),
// );
// cache[region][season] = info;
// }
// return cache[region][season];
// }
+2
View File
@@ -1,3 +1,4 @@
export * from "./alias";
export * from "./assertSecret";
export * from "./availableProvisions";
export * from "./averageDefinitions";
@@ -7,6 +8,7 @@ export * from "./createDefaultSkills";
export * from "./degressiveStat";
export * from "./discoveredIdDefinitions";
export * from "./emblemURL";
export * from "./environment";
export * from "./eventManager";
export * from "./getTimeDaysAgo";
export * from "./isExplosive";
+3 -1
View File
@@ -1,5 +1,7 @@
import { alias } from "@blitzkit/core";
export const BLITZKIT_TANK_ICON_SIZE = { width: 40 * 16, height: 30 * 16 };
export function tankIcon(id: number, size: "big" | "small" = "big") {
return `/api/tanks/${id}/icons/${size}.webp`;
return alias("api", `/tanks/${id}/icons/${size}.webp`);
}
+10 -2
View File
@@ -46,7 +46,15 @@ export abstract class AbstractVFS {
return buffer;
}
abstract dir(path: string): Promise<string[]>;
protected abstract _dir(path: string): Promise<string[]>;
async dir(path: string) {
const files = await this._dir(path);
return files.map((file) =>
file.endsWith(".dvpl") ? file.slice(0, -5) : file,
);
}
async text(path: string) {
const file = await this.file(path);
@@ -63,7 +71,7 @@ export abstract class AbstractVFS {
return this.xmlParser.parse(file) as Type;
}
async _csv(text: string, options: Options = {}) {
protected async _csv(text: string, options: Options = {}) {
const parsed = parseCSV(text, options);
return parsed;
}
+1 -1
View File
@@ -23,7 +23,7 @@ export class LocalVFS extends AbstractVFS {
return array;
}
async dir(path: string) {
async _dir(path: string) {
try {
return await readdir(`${this.base}/${path}`);
} catch (_) {
+1 -1
View File
@@ -35,7 +35,7 @@ export class MixedVFS extends AbstractVFS {
throw new Error(`File not found: ${path}`);
}
async dir(path: string) {
async _dir(path: string) {
const files = new Set<string>();
for (const vfs of this.vfses) {
+1 -1
View File
@@ -89,7 +89,7 @@ export class SteamVFS extends AbstractVFS {
return new Uint8Array(downloaded.file);
}
async dir(path: string) {
async _dir(path: string) {
const parentSegments = path.split("/").length;
const children = new Set<string>();
+1 -1
View File
@@ -33,7 +33,7 @@ export class ZipVFS extends AbstractVFS {
return new Uint8Array(arrayBuffer);
}
async dir(path: string) {
async _dir(path: string) {
const parentSegments = path.split("/").length;
const children = new Set<string>();
-51
View File
@@ -1,51 +0,0 @@
import { times } from 'lodash-es';
import { ReadStream, WriteStream } from './buffer';
export class DidsReadStream extends ReadStream {
dids() {
this.magic();
this.header();
return this.body();
}
magic() {
const magic = this.utf8(4);
if (magic !== 'DIDS') {
throw new Error(`Invalid DIDS magic number: "${magic}"`);
}
}
header() {
return {
version: this.uint16(),
};
}
body() {
const count = this.uint32();
return times(count, () => this.uint32());
}
}
export class DidsWriteStream extends WriteStream {
dids(ids: number[]) {
this.magic();
this.header();
this.body(ids);
return this;
}
magic() {
this.utf8('DIDS');
}
header() {
this.uint16(1);
}
body(ids: number[]) {
this.uint32(ids.length);
ids.forEach((id) => this.uint32(id));
}
}
+9 -11
View File
@@ -1,11 +1,9 @@
export * from './buffer';
export * from './bufferToBigInt';
export * from './dds';
export * from './dids';
export * from './pvr';
export * from './rts';
export * from './sc2';
export * from './scg';
export * from './scpg';
export * from './windows';
export * from "./buffer";
export * from "./bufferToBigInt";
export * from "./dds";
export * from "./pvr";
export * from "./rts";
export * from "./sc2";
export * from "./scg";
export * from "./scpg";
export * from "./windows";
+5 -4
View File
@@ -43,10 +43,11 @@
},
"branches": {
"local": "dev",
"dev": "beta",
"opentest": "opentest",
"preview": "preview"
"beta": "Beta",
"development": "Development",
"opentest": "OpenTest",
"preview": "Preview",
"production": "Production"
},
"locales": {
+5 -473
View File
@@ -37,10 +37,11 @@
"d_1": "Ayer"
},
"branches": {
"local": "DEV",
"dev": "Beta",
"opentest": "Prueba Abierta",
"preview": "Avance"
"beta": "Beta",
"development": "Development",
"opentest": "Prueba abierta",
"preview": "Preview",
"production": "Production"
},
"locales": {
"en": "Ingles",
@@ -184,467 +185,6 @@
"cumulative_battle_life_time": "Tiempo total de batalla"
}
},
"bot": {
"common": {
"errors": {
"clan_not_found": "# Could not find clan\nCouldn't find \"{search}\". Try selecting a clan from the search result.",
"player_not_found": "# Could not find user\nI couldn't find user \"{search}\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.",
"player_not_linked": "# Usa `/vincular` para empezar\n¡No sé quién eres, pero puedes decírmelo hoy!",
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"{search}\".",
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"{search}\". Try typing the name of the tank instead of a number.",
"no_tank_stats": "# No hay estadísticas de tanque disponibles\nEste jugador no tiene estadísticas disponibles. Este podría no ser el jugador que estás buscando.",
"get_help": "Servidor de ayuda de Discord",
"uncaught_error": "# ¡Blitzkrieg sufrió un error!\nLo sentimos mucho. Siéntete libre de [unirte al servidor oficial de Discord](https://discord.gg/nDt7AjGJQH) para obtener ayuda.",
"preview": "# Este comando es una vista previa\n\nSolo puedes acceder a este comando en [el servidor oficial de Discord](https://discord.gg/nDt7AjGJQH). Generalmente este comando estará disponible pronto.",
"query_too_small": "# Tu búsqueda es demasiado corta\nDebe ser por lo menos de 3 caracteres de largo.",
"query_too_long": "# Tu búsqueda es demasiado larga\nDebe ser como mucho de 100 caracteres de largo."
},
"wn8": {
"name": "WN8",
"types": {
"very_bad": "Muy Malo",
"bad": "Malo",
"below_average": "Debajo del Promedio",
"average": "Promedio",
"above_average": "Superior al Promedio",
"good": "Bueno",
"very_good": "Muy Bueno",
"great": "Excelente",
"unicum": "Unicum",
"super_unicum": "Super Unicum"
}
},
"subcommands": {
"today": {
"name": "Hoy",
"description": "Hoy"
},
"30": {
"name": "30",
"description": "30 días"
},
"60": {
"name": "60",
"description": "60 días"
},
"90": {
"name": "90",
"description": "90 días"
},
"career": {
"name": "carrera",
"description": "Carrera"
},
"custom": {
"name": "personalizado",
"description": "Rango personalizado",
"options": {
"start": {
"name": "comenzar",
"description": "Comenzar en cantidad de días pasados desde hoy"
},
"end": {
"name": "fin",
"description": "Terminar en cantidad de días pasados desde hoy"
}
}
}
},
"options": {
"clan": {
"name": "clan",
"description": "El nombre o etiqueta del clan"
},
"tank": {
"name": "tanque",
"description": "El tanque al cual conseguirle estadísticas"
},
"username": {
"name": "nombre de usuario",
"description": "El nombre de usuario de Blitz"
},
"filters": {
"nation": {
"name": "nación",
"description": "Nación"
},
"class": {
"name": "clase",
"description": "Clase"
},
"type": {
"name": "tipo",
"description": "Tipo"
}
},
"region": {
"name": "región",
"description": "Región"
},
"tier": {
"name": "tier",
"description": "Nivel",
"choices": {
"tier": "Tier {tier_1} - {tier_2}"
}
}
},
"periods": {
"career": "Carrera",
"today": "Hoy",
"days": "{days} days",
"custom": "{day_1} - {day_2} days"
},
"filters": {
"none": "Sin filtros",
"tier": "Tier {tier}"
},
"no_data": {
"battles_in_period": "No hay batallas jugadas en este período",
"battles_in_season": "No hay batallas jugadas en esta temporada",
"players_in_period": "No hay jugadores en este período",
"tanks_found": "No se encontraron tanques"
},
"buttons": {
"refresh": "Actualizar"
},
"hero_stats": {
"battles": "Batallas",
"winrate": "Tasa de victorias",
"damage": "Daño",
"damage_ratio": "Relación de daño",
"tier": "Nivel",
"survival": "Supervivencia",
"accuracy": "Precisión",
"kills": "Bajas"
}
},
"commands": {
"dead": "# Stat Commands Are Dead\n\nThe BlitzKit Discord bot used to use BlitzStars to fetch periodic statistics. BlitzStars has enabled aggressive Cloudflare protections; hence, disabling BlitzKit from using BlitzStar's API. This has impacted the following commands:\n\n- `/stats`\n- `/today`\n- `/breakdown`\n- `/evolution`\n- `/full-stats`\n\nPlease use [Aftermath](<https://amth.one/invite>) as an alternative which is completely independent of BlitzStars. BlitzKit-hosted stat commands will return in the near future.",
"replay": {
"name": "repetición",
"description": "Mostrar resultados de la repetición",
"options": {
"file": {
"name": "file",
"description": "Replay file"
}
}
},
"research": {
"name": "desarollar",
"description": "Enumera los gastos por investigar una línea",
"options": {
"target_tank": {
"name": "tanque objetivo",
"description": "El último tanque a investigar en la línea"
},
"starting_tank": {
"name": "tanque de inicio",
"description": "El tanque inicial en la línea (se deduce automáticamente si no se proporciona)"
}
},
"body": {
"subtitle": "Investigar",
"total": "Total",
"research": "Research: {cost}",
"upgrades": "Upgrades: {cost}",
"purchase": "Purchase: {cost}",
"equipment": "Equipment: {cost}",
"estimation": "# ¡Esto es una estimación!\n\nEsta lista podría incluir tanques ya investigados debido a la información errónea que proporciona Wargaming. Si puedes, prueba proporcionando un `tanque-inicial` de la línea al usar el comando."
},
"errors": {
"non_tech_tree": "# {tank} is not on the tech tree\n\nYou cannot research a premium/collector tank.",
"tier_1": "# {tank} is a tier I\n\nTier I tanks are always unlocked for free for all players.",
"already_researched": "# {tank} is already researched\n\nTry providing a `starting-tank` when using the command to view the totals for a line you have already researched.",
"start_end_equal": "# {tank} was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
"tanks_not_on_line": "# {tank_1} and {tank_2} are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
"unordered": "# {tank_1} higher tiered to {tank_2}\n\nMake sure to provide {tank_2} as the `starting-tank` instead as it is the lower tiered tank."
}
},
"about": {
"name": "acerca",
"description": "Toda la información que necesitas sobre el bot",
"subcommands": {
"commands": {
"name": "comandos",
"description": "Lista de todos los comandos y sus descripciones",
"body": "# Comandos\n\nBlitzKit ofrece los siguientes comandos:\n\n"
},
"embeds": {
"name": "embeds",
"description": "Cómo funcionan los embeds"
},
"introduction": {
"name": "introducción",
"description": "Sobre el bot"
},
"invite": {
"name": "invitar",
"description": "Cómo invitar al bot"
},
"timezones": {
"name": "zonas horarias",
"description": "Cómo Blitzkrieg infiere y utiliza tu zona horaria"
},
"credits": {
"name": "créditos",
"description": "Personas que han ayudado a BlitzKit"
}
}
},
"breakdown": {
"name": "resumen",
"description": "Resumen de un período por tanques jugados",
"body": {
"total": "Total",
"battles": "Batallas",
"wn8": "WN8",
"winrate": "Tasa de victorias",
"damage": "Daño",
"unknown_tank": "Unknown tank {id}"
}
},
"debug": {
"name": "depurar",
"description": "Muestra más información sobre el bot",
"body": {
"title": "# Depuración\n\n",
"version": "Versión",
"shard": {
"label": "Fragmentar",
"default": "predeterminado"
},
"uptime": "Tiempo en línea"
}
},
"evolution": {
"name": "evolución",
"description": "Evolución de estadísticas",
"groups": {
"player": {
"name": "jugador",
"description": "Estadísticas del jugador"
},
"tank": {
"name": "tanque",
"description": "Estadísticas del tanque"
}
}
},
"inactive": {
"name": "inactivo",
"description": "Enumera a todos los jugadores inactivos en un clan por encima de un umbral",
"options": {
"threshold": {
"name": "umbral",
"description": "The number of days inactive (default: {days} days)"
}
},
"body": {
"subtitle": "Inactive for {days}+ days",
"listing": "{days} days"
}
},
"full_stats": {
"name": "estadísticas completas",
"description": "Estadísticas completas del juego",
"body": {
"cards": {
"games": {
"title": "Partidas",
"winrate": "Tasa de victorias",
"battles": "Batallas",
"wins_losses": "Victorias / derrotas"
},
"efficiency": {
"title": "Eficiencia",
"wn8": "WN8",
"survival": "Supervivencia",
"accuracy": "Precisión"
},
"lethality": {
"title": "Letalidad",
"average_damage": "Daño promedio",
"damage_per_tier": "Daño por nivel",
"damage_ratio": "Relación de daño",
"average_kills": "Promedio de bajas"
},
"miscellaneous": {
"title": "Misceláneo",
"average_tier": "Nivel promedio",
"average_spots": "Promedio de detecciones",
"average_xp": "Promedio de XP",
"kills_to_death_ratio": "Relación bajas-muertes"
}
}
}
},
"link": {
"name": "vincular",
"description": "Vincula tu cuenta de Blitz y Discord",
"body": {
"accounts_linked": "Cuentas vinculadas",
"no_role_permissions": {
"title": "{name} failed to verify",
"description": "No tengo permiso para administrar roles."
}
},
"embed": {
"title": "¿Cuenta incorrecta?",
"description": "Usa el comando de nuevo y selecciona un usuario de los resultados de búsqueda."
}
},
"search_clans": {
"name": "buscar clanes",
"description": "Buscar clanes",
"options": {
"limit": {
"name": "límite",
"description": "The max number of clan results (default: {limit})"
}
},
"body": {
"title": "Clan search for {search} in region {region}",
"no_results": "No se encontraron clanes"
}
},
"search_players": {
"name": "buscar jugadores",
"description": "Buscar jugadores",
"options": {
"limit": {
"name": "límite",
"description": "The max number of player results (default: {limit})"
}
},
"body": {
"title": "Player search for {search} in region {region}",
"no_results": "No se encontraron jugadores"
}
},
"rating_leaderboard": {
"name": "tabla de clasificación",
"description": "Estadísticas de batallas de clasificatoria",
"subcommands": {
"neighbors": {
"name": "vecinos",
"description": "Jugadores vecinos en la tabla de posiciones"
},
"league": {
"name": "liga",
"description": "Mejores jugadores de una liga"
}
},
"options": {
"limit": {
"name": "limite",
"description": "How many neighbors to display (default: {count})"
},
"season": {
"name": "temporada",
"description": "Número de temporada (predeterminado: actual)",
"choices": {
"current": "{season} (current)"
}
},
"league": {
"name": "liga",
"description": "La liga a mostrar"
}
},
"body": {
"top_players": "Top {count} players",
"no_participation": "# {player} didn't play rating in season {season}\n\nThis player did not participate in this season or did not get past calibration.",
"no_ongoing_season": "# No hay temporada en curso\n\nActualmente no hay ninguna temporada de clasificatoria.",
"deleted_player": "Deleted {id}",
"neighbors": "Vecinos de clasificatoria",
"more": "+ {count} more"
}
},
"today": {
"name": "hoy",
"description": "Tanques jugados hoy y estadísticas"
},
"stats": {
"name": "estadísticas",
"description": "Estadísticas rápidas para un período"
},
"owned_tanks": {
"name": "tanques en propiedad",
"description": "Muestra todos los tanques propios en un nivel",
"body": {
"title": "# {name}'s owned tier {tier} tanks\n\n"
}
},
"permissions": {
"name": "permisos",
"description": "Comprueba si el bot tiene los permisos requeridos para funcionar",
"body": {
"title": "# Permissions\n\n{body}",
"view_channels": "Ver canales: necesario para encontrar el mensaje en el cual el botón \"actualizar\" fue usado",
"read_message_history": "Leer historial de mensajes: necesario para encontrar el mensaje a actualizar",
"attach_files": "Adjuntar archivos: necesario para añadir imágenes a los comandos basados en estas",
"external_emojis": "Emoticonos externos: necesario para añadir emoticonos de banderas nacionales y clases de tanque"
}
},
"ping": {
"name": "ping",
"description": "Comprueba si el bot está vivo.",
"body": {
"pong": "Pong 🏓",
"pong_with_time": "Pong 🏓 - {time}ms"
},
"subcommands": {
"blitzkit": {
"name": "blitzkit",
"description": "Hacerle un ping a BlitzKit"
},
"wotb": {
"name": "wotb",
"description": "Hacerle un ping a World of Tanks Blitz"
}
}
},
"player_info": {
"name": "info del jugador",
"description": "Información básica sobre un jugador",
"body": {
"title": "{name}'s information",
"nickname": "Apodo",
"battles": "Batallas",
"winrate": "Tasa de victorias",
"account_id": "ID de cuenta",
"created": "Creado",
"last_battle": "Última batalla"
}
},
"rating": {
"name": "clasificatoria",
"description": "Estadísticas de clasificatoria del jugador",
"subcommands": {
"today": {
"name": "hoy",
"description": "Estadísticas de clasificatoria de hoy"
},
"season": {
"name": "temporada",
"description": "Estadísticas de clasificatoria de esta temporada"
}
},
"body": {
"subtitle": {
"today": "Estadísticas de clasificatoria de hoy",
"season": "Estadísticas de clasificatoria de esta temporada"
}
},
"errors": {
"no_participation": "# {name} hasn't played rating\n\nThis player hasn't participated in the rating gamemode this season.",
"unrecorded_stats": "# Estadísticas no disponibles actualmente\n\nWargaming podría estar sufriendo una caída del servidor. Si hay alguna temporada en curso, las estadísticas deberían estar disponibles en 24 horas."
}
}
}
},
"website": {
"redirect": {
"title": "301: moved permanently",
@@ -741,14 +281,6 @@
}
},
"home": {
"pwa": {
"title": "Install the BlitzKit App",
"new": "NEW",
"checking": "Checking compatibility...",
"instructions": "Instructions for Apple devices.",
"install": "Install",
"dismiss": "Dismiss"
},
"discord": {
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
+5 -473
View File
@@ -37,10 +37,11 @@
"d_1": "Hier"
},
"branches": {
"local": "dev",
"dev": "beta",
"opentest": "test ouvert",
"preview": "aperçu"
"beta": "Beta",
"development": "Development",
"opentest": "OpenTest",
"preview": "Preview",
"production": "Production"
},
"locales": {
"en": "Anglais",
@@ -184,467 +185,6 @@
"cumulative_battle_life_time": "Total de temps de bataille"
}
},
"bot": {
"common": {
"errors": {
"clan_not_found": "# Impossible de trouver le clan\nImpossible de trouver «{search} ». Essayez de sélectionner un clan dans le résultat de la recherche.",
"player_not_found": "# Impossible de trouver l'utilisateur\nJe n'ai pas pu trouver l'utilisateur \"{search}\". Essayez de choisir un utilisateur dans le résultat de la recherche, en tapant un nom d'utilisateur valide, ou en utilisant la commande `/link`.",
"player_not_linked": "# Utilisez `/link` pour commencer\nJe ne sais pas qui vous êtes, mais vous pouvez me le dire aujourd'hui !",
"tank_name_not_found": "# Char introuvable\nImpossible de trouver le char sous le nom \"{search}\".",
"tank_id_not_found": "# Char introuvable\nImpossible de trouver le char avec l'ID \"{search}\". Essayez de taper le nom du char au lieu d'un nombre.",
"no_tank_stats": "# Aucune statistique de tank disponible\nCe joueur n'a pas de statistiques disponibles. Ce n'est peut-être pas le joueur que vous cherchez.",
"get_help": "Aidez le serveur Discord",
"uncaught_error": "# Blitzkit a rencontré une erreur !\nNous sommes désolés de cela. N'hésitez pas à [rejoindre le serveur officiel Discord](https://discord.gg/nDt7AjGJQH) pour obtenir de l'aide.",
"preview": "# Cette commande est en aperçu\n\nVous ne pouvez accéder à cette commande que dans [le serveur officiel Discord](https://discord.gg/nDt7AjGJQH). La commande sera généralement disponible sous peu.",
"query_too_small": "# Votre recherche est trop courte\nVotre recherche doit comporter au moins 3 caractères.",
"query_too_long": "# Votre recherche est trop longue\nVotre recherche doit comporter au maximum 100 caractères."
},
"wn8": {
"name": "WN8",
"types": {
"very_bad": "Très nul",
"bad": "Nul",
"below_average": "En-dessous de la moyenne",
"average": "Moyenne",
"above_average": "Au-dessus de la moyenne",
"good": "Bien",
"very_good": "Très bien",
"great": "Super",
"unicum": "Unique",
"super_unicum": "Super unique"
}
},
"subcommands": {
"today": {
"name": "aujourd'hui",
"description": "Aujourd'hui"
},
"30": {
"name": "30",
"description": "30 jours"
},
"60": {
"name": "60",
"description": "60 jours"
},
"90": {
"name": "90",
"description": "90 jours"
},
"career": {
"name": "carrière",
"description": "Carrière"
},
"custom": {
"name": "personnaliser",
"description": "Période spécifique",
"options": {
"start": {
"name": "début",
"description": "Commencer il y a un nombre de jours à partir d'aujourd'hui"
},
"end": {
"name": "fin",
"description": "Fin en nombre de jours à partir d'aujourd'hui"
}
}
}
},
"options": {
"clan": {
"name": "clan",
"description": "Le nom ou le tag du clan"
},
"tank": {
"name": "char",
"description": "Le char pour obtenir des statistiques pour"
},
"username": {
"name": "nom d'utilisateur",
"description": "Le nom d'utilisateur Blitz du joueur"
},
"filters": {
"nation": {
"name": "pays",
"description": "Pays"
},
"class": {
"name": "classe",
"description": "Classe"
},
"type": {
"name": "type",
"description": "Type"
}
},
"region": {
"name": "région",
"description": "Région"
},
"tier": {
"name": "rang",
"description": "Rang",
"choices": {
"tier": "Rang {tier_}1 - {tier_}2"
}
}
},
"periods": {
"career": "Carrière",
"today": "Aujourd'hui",
"days": "{days} jours",
"custom": "{day_1} - {day_2} jours"
},
"filters": {
"none": "Aucun filtre",
"tier": "Rang {tier}"
},
"no_data": {
"battles_in_period": "",
"battles_in_season": "Aucune bataille jouée dans cette saison",
"players_in_period": "Aucun joueur pendant cette période",
"tanks_found": "Aucun char de trouver"
},
"buttons": {
"refresh": "Actualiser"
},
"hero_stats": {
"battles": "Batailles",
"winrate": "Taux de victoires",
"damage": "Dégâts",
"damage_ratio": "Rapport de dégâts",
"tier": "Rang",
"survival": "Survie",
"accuracy": "Précision",
"kills": "Tués"
}
},
"commands": {
"dead": "# Stat Commands Are Dead\n\nThe BlitzKit Discord bot used to use BlitzStars to fetch periodic statistics. BlitzStars has enabled aggressive Cloudflare protections; hence, disabling BlitzKit from using BlitzStar's API. This has impacted the following commands:\n\n- `/stats`\n- `/today`\n- `/breakdown`\n- `/evolution`\n- `/full-stats`\n\nPlease use [Aftermath](<https://amth.one/invite>) as an alternative which is completely independent of BlitzStars. BlitzKit-hosted stat commands will return in the near future.",
"replay": {
"name": "rejouer",
"description": "Afficher les résultats de la relecture",
"options": {
"file": {
"name": "file",
"description": "Replay file"
}
}
},
"research": {
"name": "recherche",
"description": "Liste des dépenses liées à la recherche d'une ligne",
"options": {
"target_tank": {
"name": "char cible",
"description": "Le dernier char à rechercher dans la ligne"
},
"starting_tank": {
"name": "Char de départ",
"description": ""
}
},
"body": {
"subtitle": "Recherche",
"total": "Total",
"research": "Recherche : {cost}",
"upgrades": "Améliorations : {cost}",
"purchase": "Achat : {cost}",
"equipment": "Équipement : {cost}",
"estimation": "# Ceci est une estimation !\n\nCette liste peut inclure des chars déjà recherchés en raison de l'information inexacte fournie par Wargaming. Essayez de fournir un `char de départ` depuis la ligne lorsque vous utilisez la commande si vous le pouvez."
},
"errors": {
"non_tech_tree": "# {tank} n'est pas dans l'arbre technologique\n\nVous ne pouvez pas rechercher un char premium/de collection.",
"tier_1": "# {tank} est un char de rang I\n\nLe char de rang 1 sont déverrouillé gratuitement pour tous les joueurs.",
"already_researched": "# {tank} est déjà recherché\n\nEssayez de fournir un `char de départ` en utilisant la commande pour voir la progression d'une lignée.",
"start_end_equal": "# {tank} a été fourni deux fois\n\nAssurez-vous que `starting-tank` et `target-tank` sont différents.",
"tanks_not_on_line": "{tank_}1 et {tank_}2 ne sont pas sur la même ligne \n\nAssurez-vous que `starting-tank` et `target-tank` sont sur la même ligne d'arbre technologique.",
"unordered": "# {tank_1} niveau supérieur à {tank_2}\n\nAssurez-vous de fournir {tank_2} comme le `starting-tank` à la place car c'est le char de niveau inférieur."
}
},
"about": {
"name": "à propos",
"description": "Toutes les informations dont vous avez besoin sur le bot",
"subcommands": {
"commands": {
"name": "commandes",
"description": "Liste de toutes les commandes et leurs descriptions",
"body": "# Commandes\n\nBlitzKit offre les commandes suivantes :\n\n"
},
"embeds": {
"name": "intègre",
"description": "Comment fonctionnent les intégrations"
},
"introduction": {
"name": "introduction",
"description": "À propos du bot"
},
"invite": {
"name": "inviter",
"description": "Comment inviter le bot"
},
"timezones": {
"name": "fuseau horaire",
"description": "Comment BlitzKit déduit et utilise votre fuseau horaire"
},
"credits": {
"name": "crédits",
"description": "Personnes qui ont aidé BlitzKit"
}
}
},
"breakdown": {
"name": "décomposition",
"description": "Répartition d'une période par chars joués",
"body": {
"total": "Total",
"battles": "Batailles",
"wn8": "WN8",
"winrate": "Taux de victoires",
"damage": "Dégâts",
"unknown_tank": "Char {id} inconnu"
}
},
"debug": {
"name": "déboguer",
"description": "Déboguer les informations sur le bot",
"body": {
"title": "# Debog\n\n",
"version": "Version",
"shard": {
"label": "Fragment",
"default": "par défaut"
},
"uptime": "Temps de fonctionnement"
}
},
"evolution": {
"name": "évolution",
"description": "Évolution des statistiques",
"groups": {
"player": {
"name": "joueur",
"description": "Statistiques du joueur"
},
"tank": {
"name": "char",
"description": "Statistiques du char"
}
}
},
"inactive": {
"name": "inactif",
"description": "Liste tous les joueurs inactifs dans un clan au-dessus d'un seuil",
"options": {
"threshold": {
"name": "seuil",
"description": "Le nombre de jours inactifs (par défaut: {days} jours)"
}
},
"body": {
"subtitle": "Inactif depuis plus de {days} jours",
"listing": "{days} jours"
}
},
"full_stats": {
"name": "statistiques complètes",
"description": "Statistiques complètes en jeu",
"body": {
"cards": {
"games": {
"title": "Parties",
"winrate": "Taux de victoires",
"battles": "Batailles",
"wins_losses": "Victoires/Défaites"
},
"efficiency": {
"title": "Efficacité",
"wn8": "WN8",
"survival": "Survie",
"accuracy": "Précision"
},
"lethality": {
"title": "Létalité",
"average_damage": "Dégâts moyens",
"damage_per_tier": "Dégâts par tier",
"damage_ratio": "Rapport de dégâts",
"average_kills": "Destruction moyenne"
},
"miscellaneous": {
"title": "Divers",
"average_tier": "Tier moyen",
"average_spots": "Repérage moyen",
"average_xp": "Exp. Moyenne par bataille",
"kills_to_death_ratio": "Rapport tués/morts"
}
}
}
},
"link": {
"name": "lien",
"description": "Lier votre compte Blitz et Discord",
"body": {
"accounts_linked": "Comptes liés",
"no_role_permissions": {
"title": "La vérification de {name} a échouée",
"description": "Je n'ai pas l'autorisation de gérer les rôles."
}
},
"embed": {
"title": "Compte invalide ?",
"description": "Utilisez à nouveau la commande et sélectionnez un nom d'utilisateur à partir des résultats de recherche."
}
},
"search_clans": {
"name": "clans de recherche",
"description": "Rechercher des clans",
"options": {
"limit": {
"name": "limite",
"description": "Nombre maximum de résultats du clan (par défaut: {limit})"
}
},
"body": {
"title": "Recherche de clan {search} dans la région {region}",
"no_results": "Pas de clan trouvé"
}
},
"search_players": {
"name": "recherche de joueur",
"description": "Recherche des joueurs",
"options": {
"limit": {
"name": "limite",
"description": "Le nombre maximum de résultats de joueur (par défaut: {limit})"
}
},
"body": {
"title": "Recherche de {search} dans la région {region}",
"no_results": "Pas de joueur de trouvé"
}
},
"rating_leaderboard": {
"name": "Classement de côté",
"description": "Statistiques des batailles de cotés",
"subcommands": {
"neighbors": {
"name": "voisins",
"description": "Joueurs voisins dans le classement"
},
"league": {
"name": "ligue",
"description": "Meilleurs joueurs d'une ligue"
}
},
"options": {
"limit": {
"name": "limite",
"description": "Combien de voisins afficher (par défaut : {count})"
},
"season": {
"name": "saison",
"description": "Numéro de saison (défaut : courant)",
"choices": {
"current": "{season} (actuel)"
}
},
"league": {
"name": "ligue",
"description": "La ligue à afficher"
}
},
"body": {
"top_players": "Top {count} joueurs",
"no_participation": "# {player} n'a pas joué de cotées dans la saison {season}\n\nCe joueur n'a pas participé à cette saison ou n'a pas dépassé l'étalonnage.",
"no_ongoing_season": "Pas de saison en cours\n\nIl n'y a pas de saison de notation en cours actuellement.",
"deleted_player": "{id} supprimé",
"neighbors": "évaluer les voisins",
"more": "+ {count} plus"
}
},
"today": {
"name": "aujourd'hui",
"description": "Les chars et les statistiques joués aujourd'hui"
},
"stats": {
"name": "statistiques",
"description": "Statistiques rapides pour une période"
},
"owned_tanks": {
"name": "chars possédés",
"description": "Affiche tous les chars d'un niveau",
"body": {
"title": "# Les chars du palier {tier} possédés par {name}"
}
},
"permissions": {
"name": "permissions",
"description": "Vérifie si le bot a les permissions nécessaires pour fonctionner",
"body": {
"title": "# Permissions\n\n{body}",
"view_channels": "Afficher les canaux : nécessaire pour trouver le message sur lequel le bouton d'actualisation a été utilisé",
"read_message_history": "Lire l'historique des messages : nécessaire pour trouver le message à rafraîchir",
"attach_files": "Joindre des fichiers : nécessaire pour ajouter des images à des commandes basées sur l'image",
"external_emojis": "Émoticônes externes: nécessaire pour ajouter des drapeaux nationaux et des émojis de classe de char"
}
},
"ping": {
"name": "ping",
"description": "Vérifiez si le bot est en vie.",
"body": {
"pong": "Pong 🏓",
"pong_with_time": "Pong 🏓 - {time}ms"
},
"subcommands": {
"blitzkit": {
"name": "blitzkit",
"description": "Ping blitzkit"
},
"wotb": {
"name": "wotb",
"description": "Ping World of Tanks Blitz"
}
}
},
"player_info": {
"name": "info-joueur",
"description": "Informations de base sur un joueur",
"body": {
"title": "Informations de {name}",
"nickname": "Pseudo",
"battles": "Batailles",
"winrate": "Taux de victoires",
"account_id": "Account ID",
"created": "Créé",
"last_battle": "Dernière bataille"
}
},
"rating": {
"name": "côtée",
"description": "Statistiques des batailles cotées du joueur",
"subcommands": {
"today": {
"name": "aujourd'hui",
"description": "Statistiques des batailles cotées d'aujourd'hui"
},
"season": {
"name": "saison",
"description": "Statistiques des batailles cotées de cette saison"
}
},
"body": {
"subtitle": {
"today": "Statistiques de batailles cotées d'aujourd'hui",
"season": "Statistiques de batailles cotées cette saison"
}
},
"errors": {
"no_participation": "# {name} n’a pas joué en cotée\n\nCe joueur n’a pas participé au mode cotée cette saison.",
"unrecorded_stats": "# Statistiques actuellement indisponibles\n\nWargaming rencontre peut-être une interruption de service.\nSi une saison est en cours, les statistiques devraient être disponibles sous 24 heures."
}
}
}
},
"website": {
"redirect": {
"title": "301 : déplacé définitivement",
@@ -741,14 +281,6 @@
}
},
"home": {
"pwa": {
"title": "Install the BlitzKit App",
"new": "NEW",
"checking": "Checking compatibility...",
"instructions": "Instructions for Apple devices.",
"install": "Install",
"dismiss": "Dismiss"
},
"discord": {
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
+5 -473
View File
@@ -37,10 +37,11 @@
"d_1": "昨日"
},
"branches": {
"local": "開発",
"dev": "ベータ版",
"opentest": "オープンテスト",
"preview": "プレビュー"
"beta": "Beta",
"development": "Development",
"opentest": "OpenTest",
"preview": "Preview",
"production": "Production"
},
"locales": {
"en": "英語",
@@ -184,467 +185,6 @@
"cumulative_battle_life_time": "総戦闘時間"
}
},
"bot": {
"common": {
"errors": {
"clan_not_found": "",
"player_not_found": "# ユーザーが見つかりませんでした\nユーザー「{search}」が見つかりませんでした。検索結果からユーザーを選択するか、有効なユーザー名を入力するか、/link コマンドを使用してください。",
"player_not_linked": "# `/link`を使用して始めましょう\nはじめまして!あなたが誰か、私に教えてください。",
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"{search}\".",
"tank_id_not_found": "# 車両が見つかりません\nID「{search}」の戦車が見つかりませんでした。数字ではなく戦車の名前を入力してみてください。",
"no_tank_stats": "# 利用可能な車輌統計がありません\nこのプレイヤーには利用可能な統計がありません。あなたが探しているプレイヤーではない可能性があります。",
"get_help": "Discordサーバーに参加する",
"uncaught_error": "# BlitzKitがエラーに遭遇しました!\n申し訳ありません。お気軽に[公式Discordサーバー](https://discord.gg/nDt7AjGJQH)にお問い合わせください。",
"preview": "このコマンドは現在[公式Discardサーバー](https://discord.gg/nDt7AjGJQH)でのみ利用可能です。間もなく全体で利用可能になります。",
"query_too_small": "# 単語が短すぎます\n検索には少なくとも3文字が含まれている必要があります。",
"query_too_long": "# 単語が長すぎます\n検索は最大で100文字までです。"
},
"wn8": {
"name": "WN8",
"types": {
"very_bad": "Very bad",
"bad": "Bad",
"below_average": "Below average",
"average": "Average",
"above_average": "Above average",
"good": "Good",
"very_good": "Very good",
"great": "Great",
"unicum": "Unicum",
"super_unicum": "Super unicum"
}
},
"subcommands": {
"today": {
"name": "today",
"description": "Today"
},
"30": {
"name": "30",
"description": "30日"
},
"60": {
"name": "60",
"description": "60日"
},
"90": {
"name": "90",
"description": "90日"
},
"career": {
"name": "総合戦績",
"description": "Career"
},
"custom": {
"name": "カスタム",
"description": "Custom range",
"options": {
"start": {
"name": "開始",
"description": "Start in number of days ago from today"
},
"end": {
"name": "end",
"description": "End in number of days ago from today"
}
}
}
},
"options": {
"clan": {
"name": "クラン",
"description": "クラン名もしくはタグ"
},
"tank": {
"name": "車輌",
"description": "統計を得る車輌"
},
"username": {
"name": "ユーザー名",
"description": "プレイヤーのBlitzユーザー名"
},
"filters": {
"nation": {
"name": "国家",
"description": "国家"
},
"class": {
"name": "",
"description": "Class"
},
"type": {
"name": "type",
"description": "Type"
}
},
"region": {
"name": "地域",
"description": "地域"
},
"tier": {
"name": "tier",
"description": "Tier",
"choices": {
"tier": "Tier {tier_1} - {tier_2}"
}
}
},
"periods": {
"career": "Career",
"today": "Today",
"days": "{days} days",
"custom": "{day_1} - {day_2} days"
},
"filters": {
"none": "フィルターなし",
"tier": "Tier {tier}"
},
"no_data": {
"battles_in_period": "No battles played in this period",
"battles_in_season": "No battles played in this season",
"players_in_period": "No players in this period",
"tanks_found": "No tanks found"
},
"buttons": {
"refresh": "Refresh"
},
"hero_stats": {
"battles": "戦闘数",
"winrate": "勝率",
"damage": "ダメージ",
"damage_ratio": "ダメージ率",
"tier": "Tier",
"survival": "生存",
"accuracy": "命中率",
"kills": "撃破"
}
},
"commands": {
"dead": "# Stat Commands Are Dead\n\nThe BlitzKit Discord bot used to use BlitzStars to fetch periodic statistics. BlitzStars has enabled aggressive Cloudflare protections; hence, disabling BlitzKit from using BlitzStar's API. This has impacted the following commands:\n\n- `/stats`\n- `/today`\n- `/breakdown`\n- `/evolution`\n- `/full-stats`\n\nPlease use [Aftermath](<https://amth.one/invite>) as an alternative which is completely independent of BlitzStars. BlitzKit-hosted stat commands will return in the near future.",
"replay": {
"name": "replay",
"description": "Display replay results",
"options": {
"file": {
"name": "file",
"description": "Replay file"
}
}
},
"research": {
"name": "研究",
"description": "Lists the expenses in researching a line",
"options": {
"target_tank": {
"name": "target-tank",
"description": "The final tank to research in the line"
},
"starting_tank": {
"name": "starting-tank",
"description": "The starting tank in the line (inferred automatically if not provided)"
}
},
"body": {
"subtitle": "Research",
"total": "トータル",
"research": "Research: {cost}",
"upgrades": "Upgrades: {cost}",
"purchase": "Purchase: {cost}",
"equipment": "Equipment: {cost}",
"estimation": "# This is an estimation!\n\nThis list may include already researched tanks because of the inaccurate information Wargaming provides. Try providing a `starting-tank` from the line when using the command if you can."
},
"errors": {
"non_tech_tree": "# {tank} is not on the tech tree\n\nYou cannot research a premium/collector tank.",
"tier_1": "# {tank} is a tier I\n\nTier I tanks are always unlocked for free for all players.",
"already_researched": "# {tank} is already researched\n\nTry providing a `starting-tank` when using the command to view the totals for a line you have already researched.",
"start_end_equal": "# {tank} was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
"tanks_not_on_line": "# {tank_1} and {tank_2} are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
"unordered": "# {tank_1} higher tiered to {tank_2}\n\nMake sure to provide {tank_2} as the `starting-tank` instead as it is the lower tiered tank."
}
},
"about": {
"name": "about",
"description": "botについての情報の全て",
"subcommands": {
"commands": {
"name": "コマンド",
"description": "全コマンドのリストと説明",
"body": "# コマンド\nBlitzKitは以下のコマンドを提供します:"
},
"embeds": {
"name": "埋め込み",
"description": "埋め込みがどう機能するか"
},
"introduction": {
"name": "introduction",
"description": "botについて"
},
"invite": {
"name": "招待",
"description": "botを招待する方法"
},
"timezones": {
"name": "タイムゾーン",
"description": "How BlitzKit infers and uses your timezone"
},
"credits": {
"name": "クレジット",
"description": "BlitzKitを支援した人々"
}
}
},
"breakdown": {
"name": "breakdown",
"description": "A period's breakdown by tanks played",
"body": {
"total": "トータル",
"battles": "戦闘数",
"wn8": "WN8",
"winrate": "勝率",
"damage": "ダメージ",
"unknown_tank": "不明な車両{id}"
}
},
"debug": {
"name": "debug",
"description": "Debug information about the bot",
"body": {
"title": "# Debug\n\n",
"version": "Version",
"shard": {
"label": "Shard",
"default": "default"
},
"uptime": "Uptime"
}
},
"evolution": {
"name": "evolution",
"description": "Evolution of statistics",
"groups": {
"player": {
"name": "player",
"description": "Player statistics"
},
"tank": {
"name": "車輌",
"description": "Tank statistics"
}
}
},
"inactive": {
"name": "inactive",
"description": "Lists all inactive players in a clan above a threshold",
"options": {
"threshold": {
"name": "threshold",
"description": "The number of days inactive (default: {days} days)"
}
},
"body": {
"subtitle": "Inactive for {days}+ days",
"listing": "{days} days"
}
},
"full_stats": {
"name": "full-stats",
"description": "Full in-game statistics",
"body": {
"cards": {
"games": {
"title": "Games",
"winrate": "勝率",
"battles": "戦闘数",
"wins_losses": "Wins / losses"
},
"efficiency": {
"title": "Efficiency",
"wn8": "WN8",
"survival": "生存",
"accuracy": "精度"
},
"lethality": {
"title": "Lethality",
"average_damage": "Average damage",
"damage_per_tier": "Damage per tier",
"damage_ratio": "ダメージ比",
"average_kills": "Average kills"
},
"miscellaneous": {
"title": "Miscellaneous",
"average_tier": "Average tier",
"average_spots": "Average spots",
"average_xp": "Average XP",
"kills_to_death_ratio": "撃破/被撃破比"
}
}
}
},
"link": {
"name": "link",
"description": "Links your Blitz and Discord account",
"body": {
"accounts_linked": "Accounts linked",
"no_role_permissions": {
"title": "{name} failed to verify",
"description": "I don't have the permission to manage roles."
}
},
"embed": {
"title": "Incorrect account?",
"description": "Use the command again and select a username from the search results."
}
},
"search_clans": {
"name": "search-clans",
"description": "Search clans",
"options": {
"limit": {
"name": "limit",
"description": "The max number of clan results (default: {limit})"
}
},
"body": {
"title": "Clan search for {search} in region {region}",
"no_results": "No clans found"
}
},
"search_players": {
"name": "search-players",
"description": "Search players",
"options": {
"limit": {
"name": "limit",
"description": "The max number of player results (default: {limit})"
}
},
"body": {
"title": "Player search for {search} in region {region}",
"no_results": "No players found"
}
},
"rating_leaderboard": {
"name": "rating-leaderboard",
"description": "Rating battles statistics",
"subcommands": {
"neighbors": {
"name": "neighbors",
"description": "Neighboring players on the leaderboard"
},
"league": {
"name": "league",
"description": "Top players of a league"
}
},
"options": {
"limit": {
"name": "limit",
"description": "How many neighbors to display (default: {count})"
},
"season": {
"name": "season",
"description": "Season number (default: current)",
"choices": {
"current": "{season} (current)"
}
},
"league": {
"name": "league",
"description": "The league to display"
}
},
"body": {
"top_players": "Top {count} players",
"no_participation": "# {player} didn't play rating in season {season}\n\nThis player did not participate in this season or did not get past calibration.",
"no_ongoing_season": "# No ongoing season\n\nThere isn't a currently running rating season.",
"deleted_player": "Deleted {id}",
"neighbors": "Rating neighbors",
"more": "+ {count} more"
}
},
"today": {
"name": "今日",
"description": "Today's played tanks and statistics"
},
"stats": {
"name": "",
"description": "Quick statistics for a period"
},
"owned_tanks": {
"name": "owned-tanks",
"description": "Shows all own tanks in a tier",
"body": {
"title": "# {name}'s owned tier {tier} tanks\n\n"
}
},
"permissions": {
"name": "permissions",
"description": "Checks wether the bot has the required permissions to function",
"body": {
"title": "# Permissions\n\n{body}",
"view_channels": "View channels: needed to find the message the refresh button was used on",
"read_message_history": "Read message history: needed to find the message to refresh",
"attach_files": "Attach files: needed to add images to image-based commands",
"external_emojis": "External emojis: needed to add national flags and tank class emojis"
}
},
"ping": {
"name": "ping",
"description": "Check wether the bot is alive.",
"body": {
"pong": "Pong 🏓",
"pong_with_time": "Pong 🏓 - {time}ms"
},
"subcommands": {
"blitzkit": {
"name": "blitzkit",
"description": "Ping BlitzKit"
},
"wotb": {
"name": "wotb",
"description": "Ping World of Tanks Blitz"
}
}
},
"player_info": {
"name": "player-info",
"description": "Basic information about a player",
"body": {
"title": "{name}'s information",
"nickname": "Nickname",
"battles": "戦闘数",
"winrate": "勝率",
"account_id": "Account ID",
"created": "Created",
"last_battle": "Last battle"
}
},
"rating": {
"name": "rating",
"description": "Rating player's statistics",
"subcommands": {
"today": {
"name": "今日",
"description": "Today's rating statistics"
},
"season": {
"name": "season",
"description": "This season's rating statistics"
}
},
"body": {
"subtitle": {
"today": "Today's rating statistics",
"season": "This season's rating statistics"
}
},
"errors": {
"no_participation": "# {name} hasn't played rating\n\nThis player hasn't participated in the rating gamemode this season.",
"unrecorded_stats": "# Statistics currently unavailable\n\nWargaming may be currently be facing server downtime. If there is an on-going season, statistics should be available withing 24 hours."
}
}
}
},
"website": {
"redirect": {
"title": "301: moved permanently",
@@ -741,14 +281,6 @@
}
},
"home": {
"pwa": {
"title": "Install the BlitzKit App",
"new": "NEW",
"checking": "Checking compatibility...",
"instructions": "Instructions for Apple devices.",
"install": "Install",
"dismiss": "Dismiss"
},
"discord": {
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
+5 -473
View File
@@ -37,10 +37,11 @@
"d_1": "Wczoraj"
},
"branches": {
"local": "dev",
"dev": "beta",
"opentest": "otwarty test",
"preview": "podgląd"
"beta": "Beta",
"development": "Development",
"opentest": "OpenTest",
"preview": "Preview",
"production": "Production"
},
"locales": {
"en": "Angielski",
@@ -184,467 +185,6 @@
"cumulative_battle_life_time": "Całkowity czas bitwy"
}
},
"bot": {
"common": {
"errors": {
"clan_not_found": "# Could not find clan\nCouldn't find \"{search}\". Try selecting a clan from the search result.",
"player_not_found": "# Could not find user\nI couldn't find user \"{search}\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.",
"player_not_linked": "# Użyj `/link` aby zacząć\nNie wiem, kim jesteś, ale możesz mi dziś powiedzieć!",
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"{search}\".",
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"{search}\". Try typing the name of the tank instead of a number.",
"no_tank_stats": "# Brak dostępnych statystyk czołgów\nTen gracz nie ma żadnych dostępnych statystyk. Może to nie być gracz, którego szukasz.",
"get_help": "Pomóż serwerowi Discord",
"uncaught_error": "# BlitzKit wpadł w błąd!\nPrzepraszamy za to. Możesz [dołączyć do oficjalnego serwera Discord](https://discord.gg/nDt7AjGJQH), aby uzyskać pomoc.",
"preview": "# Ta komenda jest w podglądzie\n\nMożesz uzyskać dostęp do tej komendy tylko na [oficjalnym serwerze Discorda](https://discord.gg/nDt7AjGJQH). Polecenie będzie ogólnie dostępne wkrótce.",
"query_too_small": "# Twoje wyszukiwanie jest zbyt krótkie\nTwoje wyszukiwanie musi mieć co najmniej 3 znaki.",
"query_too_long": "# Twoje wyszukiwanie jest zbyt długie\nTwoje wyszukiwanie musi mieć maksymalnie 100 znaków."
},
"wn8": {
"name": "WN8",
"types": {
"very_bad": "Bardzo złe",
"bad": "Złe",
"below_average": "Poniżej średniej",
"average": "Średnia",
"above_average": "Powyżej średniej",
"good": "Dobrze",
"very_good": "Bardzo dobrze",
"great": "Świetne",
"unicum": "Unikalny",
"super_unicum": "Bardzo unikalny"
}
},
"subcommands": {
"today": {
"name": "dziś",
"description": "Dziś"
},
"30": {
"name": "30",
"description": "30 dni"
},
"60": {
"name": "60",
"description": "60 dni"
},
"90": {
"name": "90",
"description": "90 dni"
},
"career": {
"name": "kariera",
"description": "Kariera"
},
"custom": {
"name": "custom",
"description": "Zakres niestandardowy",
"options": {
"start": {
"name": "start",
"description": "Rozpocznij od dnia dzisiejszego"
},
"end": {
"name": "zakończ",
"description": "Zakończ od dnia dzisiejszego"
}
}
}
},
"options": {
"clan": {
"name": "klan",
"description": "Wpisz nazwę klanu lub tag"
},
"tank": {
"name": "czołg",
"description": "Czołg do pobrania statystyk"
},
"username": {
"name": "nazwa użytkownika",
"description": "Nazwa użytkownika Blitz gracza"
},
"filters": {
"nation": {
"name": "naród",
"description": "Naród"
},
"class": {
"name": "klasa",
"description": "Klasa"
},
"type": {
"name": "rodzaj",
"description": "Rodzaj"
}
},
"region": {
"name": "region",
"description": "Region"
},
"tier": {
"name": "poziom",
"description": "Poziom",
"choices": {
"tier": "Tier {tier_1} - {tier_2}"
}
}
},
"periods": {
"career": "Kariera",
"today": "Dzisiaj",
"days": "{days} days",
"custom": "{day_1} - {day_2} days"
},
"filters": {
"none": "Brak filtrów",
"tier": "Tier {tier}"
},
"no_data": {
"battles_in_period": "Brak bitew w tym okresie",
"battles_in_season": "Brak bitew w tym sezonie",
"players_in_period": "Brak graczy w tym okresie",
"tanks_found": "Nie znaleziono czołgu"
},
"buttons": {
"refresh": "Odśwież"
},
"hero_stats": {
"battles": "Bitwy",
"winrate": "Zwycięstwa",
"damage": "Obrażenia",
"damage_ratio": "Współczynnik obrażeń",
"tier": "Poziom",
"survival": "Przeżycie",
"accuracy": "Celność",
"kills": "Zabójstwa"
}
},
"commands": {
"dead": "# Stat Commands Are Dead\n\nThe BlitzKit Discord bot used to use BlitzStars to fetch periodic statistics. BlitzStars has enabled aggressive Cloudflare protections; hence, disabling BlitzKit from using BlitzStar's API. This has impacted the following commands:\n\n- `/stats`\n- `/today`\n- `/breakdown`\n- `/evolution`\n- `/full-stats`\n\nPlease use [Aftermath](<https://amth.one/invite>) as an alternative which is completely independent of BlitzStars. BlitzKit-hosted stat commands will return in the near future.",
"replay": {
"name": "powtórz",
"description": "Wyświetl wyniki powtórki",
"options": {
"file": {
"name": "file",
"description": "Replay file"
}
}
},
"research": {
"name": "badanie",
"description": "Wyświetla wydatki na badanie linii",
"options": {
"target_tank": {
"name": "docelowy czołg",
"description": "Ostatni czołg do badań w linii"
},
"starting_tank": {
"name": "czołg rozpoczynający",
"description": "Czołg początkowy w linii (automatycznie, jeżeli nie jest dostarczony)"
}
},
"body": {
"subtitle": "Rozpocznij badania",
"total": "Całkowity",
"research": "Research: {cost}",
"upgrades": "Upgrades: {cost}",
"purchase": "Purchase: {cost}",
"equipment": "Equipment: {cost}",
"estimation": "# To jest szacunek!\n\nTa lista może zawierać już zbadane czołgi z powodu niedokładnych informacji dostarczanych przez Wargaming. Spróbuj podać `starting-tank` z wiersza podczas używania polecenia, jeśli możesz."
},
"errors": {
"non_tech_tree": "# {tank} is not on the tech tree\n\nYou cannot research a premium/collector tank.",
"tier_1": "# {tank} is a tier I\n\nTier I tanks are always unlocked for free for all players.",
"already_researched": "# {tank} is already researched\n\nTry providing a `starting-tank` when using the command to view the totals for a line you have already researched.",
"start_end_equal": "# {tank} was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
"tanks_not_on_line": "# {tank_1} and {tank_2} are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
"unordered": "# {tank_1} higher tiered to {tank_2}\n\nMake sure to provide {tank_2} as the `starting-tank` instead as it is the lower tiered tank."
}
},
"about": {
"name": "opis",
"description": "Wszystkie informacje na temat bota",
"subcommands": {
"commands": {
"name": "komendy",
"description": "Lista wszystkich poleceń i ich opisów",
"body": "# Polecenia\n\nBlitzKit oferuje następujące polecenia:\n\n"
},
"embeds": {
"name": "osadzenie",
"description": "Jak działają osadzone"
},
"introduction": {
"name": "wprowadzenie",
"description": "O bocie"
},
"invite": {
"name": "zaproszenie",
"description": "Jak zaprosić bota"
},
"timezones": {
"name": "strefy czasowe",
"description": "W jaki sposób BlitzKit wnioskuje i wykorzystuje Twoją strefę czasową"
},
"credits": {
"name": "uznania",
"description": "Ludzie, którzy pomogli BlitzKit"
}
}
},
"breakdown": {
"name": "podział",
"description": "Podział przez okres według rozgrywanych czołgów",
"body": {
"total": "Ogółem",
"battles": "Bitwy",
"wn8": "WN8",
"winrate": "Śr. wygranych",
"damage": "Uszkodzenia",
"unknown_tank": "Unknown tank {id}"
}
},
"debug": {
"name": "debugowanie",
"description": "Debugowanie informacji o botu",
"body": {
"title": "# Debug\n\n",
"version": "Wersja",
"shard": {
"label": "Fragment",
"default": "domyślne"
},
"uptime": "Czas działania"
}
},
"evolution": {
"name": "rozwój",
"description": "Rozwój statystyk",
"groups": {
"player": {
"name": "gracz",
"description": "Statystyki gracza"
},
"tank": {
"name": "czołg",
"description": "Statystyki czołgu"
}
}
},
"inactive": {
"name": "inactive",
"description": "Lists all inactive players in a clan above a threshold",
"options": {
"threshold": {
"name": "threshold",
"description": "The number of days inactive (default: {days} days)"
}
},
"body": {
"subtitle": "Inactive for {days}+ days",
"listing": "{days} days"
}
},
"full_stats": {
"name": "full-stats",
"description": "Full in-game statistics",
"body": {
"cards": {
"games": {
"title": "Games",
"winrate": "Śr. wygranych",
"battles": "Bitwy",
"wins_losses": "Zwycięstwa / porażki"
},
"efficiency": {
"title": "Efficiency",
"wn8": "WN8",
"survival": "Przeżycie",
"accuracy": "Skuteczność"
},
"lethality": {
"title": "Lethality",
"average_damage": "Średnie uszkodzenia",
"damage_per_tier": "Damage per tier",
"damage_ratio": "Współczynnik obrażeń",
"average_kills": "Average kills"
},
"miscellaneous": {
"title": "Miscellaneous",
"average_tier": "Średni poziom",
"average_spots": "Average spots",
"average_xp": "Average XP",
"kills_to_death_ratio": "Stosunek zabójstw do śmierci"
}
}
}
},
"link": {
"name": "link",
"description": "Links your Blitz and Discord account",
"body": {
"accounts_linked": "Accounts linked",
"no_role_permissions": {
"title": "{name} failed to verify",
"description": "I don't have the permission to manage roles."
}
},
"embed": {
"title": "Incorrect account?",
"description": "Use the command again and select a username from the search results."
}
},
"search_clans": {
"name": "search-clans",
"description": "Search clans",
"options": {
"limit": {
"name": "",
"description": "The max number of clan results (default: {limit})"
}
},
"body": {
"title": "Clan search for {search} in region {region}",
"no_results": "No clans found"
}
},
"search_players": {
"name": "search-players",
"description": "Search players",
"options": {
"limit": {
"name": "",
"description": "The max number of player results (default: {limit})"
}
},
"body": {
"title": "Player search for {search} in region {region}",
"no_results": "No players found"
}
},
"rating_leaderboard": {
"name": "rating-leaderboard",
"description": "Rating battles statistics",
"subcommands": {
"neighbors": {
"name": "neighbors",
"description": "Neighboring players on the leaderboard"
},
"league": {
"name": "league",
"description": "Najlepsi gracze ligi"
}
},
"options": {
"limit": {
"name": "",
"description": "How many neighbors to display (default: {count})"
},
"season": {
"name": "season",
"description": "Season number (default: current)",
"choices": {
"current": "{season} (current)"
}
},
"league": {
"name": "league",
"description": "The league to display"
}
},
"body": {
"top_players": "Top {count} players",
"no_participation": "# {player} didn't play rating in season {season}\n\nThis player did not participate in this season or did not get past calibration.",
"no_ongoing_season": "# No ongoing season\n\nThere isn't a currently running rating season.",
"deleted_player": "Deleted {id}",
"neighbors": "Rating neighbors",
"more": "+ {count} more"
}
},
"today": {
"name": "dzisiaj",
"description": "Dzisiejsze zagrane czołgami i statystyki"
},
"stats": {
"name": "stats",
"description": "Szybkie statystyki na okres"
},
"owned_tanks": {
"name": "posiadane czołgi",
"description": "Pokazuje wszystkie własne czołgi na poziomie",
"body": {
"title": "# {name}'s owned tier {tier} tanks\n\n"
}
},
"permissions": {
"name": "uprawnienia",
"description": "Sprawdza, czy bot ma wymagane uprawnienia do funkcji",
"body": {
"title": "# Permissions\n\n{body}",
"view_channels": "Wyświetl kanały: potrzebne do znalezienia wiadomości użyty przycisk odświeżania",
"read_message_history": "Przeczytaj historię wiadomości: potrzebne do znalezienia wiadomości do odświeżenia",
"attach_files": "Dołącz pliki: potrzebne do dodawania obrazów do komend opartych na obrazach",
"external_emojis": "Zewnętrzne emotikony: potrzebne do dodania flagi krajowych i emotikonów klasy zbiorników"
}
},
"ping": {
"name": "ping",
"description": "Sprawdź, czy bot żyje.",
"body": {
"pong": "Pong 🏓",
"pong_with_time": "Pong 🏓 - {time}ms"
},
"subcommands": {
"blitzkit": {
"name": "blitzkit",
"description": "Ping BlitzKit"
},
"wotb": {
"name": "wotb",
"description": "Ping World of Tanks Blitz"
}
}
},
"player_info": {
"name": "informacje o graczu",
"description": "Podstawowe informacje o graczu",
"body": {
"title": "{name}'s information",
"nickname": "Nick",
"battles": "Bitwy",
"winrate": "Śr. wygranych",
"account_id": "ID konta",
"created": "Stworzony",
"last_battle": "Ostatnia bitwa"
}
},
"rating": {
"name": "klasyfikacja",
"description": "Statystyki klasyfikacji gracza",
"subcommands": {
"today": {
"name": "dzisiaj",
"description": "Dzisiejsze statystyki klasyfikacji"
},
"season": {
"name": "sezon",
"description": "Statystyki klasyfikacji w tym sezonie"
}
},
"body": {
"subtitle": {
"today": "Dzisiejsze statystyki klasyfikacji",
"season": "Statystyki klasyfikacji w tym sezonie"
}
},
"errors": {
"no_participation": "# {name} hasn't played rating\n\nThis player hasn't participated in the rating gamemode this season.",
"unrecorded_stats": "# Statistics currently unavailable\n\nWargaming may be currently be facing server downtime. If there is an on-going season, statistics should be available withing 24 hours."
}
}
}
},
"website": {
"redirect": {
"title": "301: moved permanently",
@@ -741,14 +281,6 @@
}
},
"home": {
"pwa": {
"title": "Install the BlitzKit App",
"new": "NEW",
"checking": "Checking compatibility...",
"instructions": "Instructions for Apple devices.",
"install": "Install",
"dismiss": "Dismiss"
},
"discord": {
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
+5 -473
View File
@@ -37,10 +37,11 @@
"d_1": "Ontem"
},
"branches": {
"local": "dev",
"dev": "beta",
"opentest": "testeaberto",
"preview": "visualização"
"beta": "Beta",
"development": "Development",
"opentest": "TesteAberto",
"preview": "Preview",
"production": "Production"
},
"locales": {
"en": "Inglês",
@@ -184,467 +185,6 @@
"cumulative_battle_life_time": "Tempo de batalha total"
}
},
"bot": {
"common": {
"errors": {
"clan_not_found": "# Could not find clan\nCouldn't find \"{search}\". Try selecting a clan from the search result.",
"player_not_found": "# Could not find user\nI couldn't find user \"{search}\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.",
"player_not_linked": "Digite \"/link\" para começar.\nEu posso não saber quem é você, mas pode-me dizer hoje sobre isso!",
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"{search}\".",
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"{search}\". Try typing the name of the tank instead of a number.",
"no_tank_stats": "# Nenhuma estatística de tanques disponível\nEsse jogador não tem as suas estatísticas disponíveis. Talvez esse pode não ser o jogador que procura.",
"get_help": "Servidor de Ajuda no Discord",
"uncaught_error": "# O BlitzKit apresentou um erro!\nSentimos muito por isso. Sinta-se a vontade para [entrar no servidor oficial no Discord]\n(https://discord.gg/nDt7AjGJQH) para conseguir ajuda.",
"preview": "# Este comando está na pré-visualização\n\nVocê só pode acessar este comando no [servidor oficial do Discord](https://discord.gg/nDt7AjGJQH). O comando estará geralmente disponível em breve.",
"query_too_small": "# Sua pesquisa é muito curta\nSua pesquisa deve ter pelo menos 3 caracteres.",
"query_too_long": "# Sua pesquisa é muito longa\nSua pesquisa deve ter no máximo 100 caracteres."
},
"wn8": {
"name": "WN8",
"types": {
"very_bad": "Muito ruim",
"bad": "Ruim",
"below_average": "Abaixo da média",
"average": "Na média",
"above_average": "Acima da média",
"good": "Bom",
"very_good": "Muito bom",
"great": "Ótimo",
"unicum": "Unicum",
"super_unicum": "Super unicum"
}
},
"subcommands": {
"today": {
"name": "hoje",
"description": "Hoje"
},
"30": {
"name": "30",
"description": "30 dias"
},
"60": {
"name": "60",
"description": "60 dias"
},
"90": {
"name": "90",
"description": "90 dias"
},
"career": {
"name": "carreira",
"description": "Carreira"
},
"custom": {
"name": "personalizado",
"description": "Intervalo personalizado",
"options": {
"start": {
"name": "iniciar",
"description": "Iniciar em número de dias atrás a partir de hoje"
},
"end": {
"name": "terminar",
"description": "Termina em número de dias atrás a partir de hoje"
}
}
}
},
"options": {
"clan": {
"name": "clã",
"description": "O nome ou tag do clã"
},
"tank": {
"name": "tanque",
"description": "O tanque para obter estatísticas para"
},
"username": {
"name": "nome de usuário",
"description": "O nome de usuário Blitz do jogador"
},
"filters": {
"nation": {
"name": "nação",
"description": "Nação"
},
"class": {
"name": "classe",
"description": "Classe"
},
"type": {
"name": "tipo",
"description": "Tipo"
}
},
"region": {
"name": "região",
"description": "Região"
},
"tier": {
"name": "nível",
"description": "Nível",
"choices": {
"tier": "Tier {tier_1} - {tier_2}"
}
}
},
"periods": {
"career": "Carreira",
"today": "Hoje",
"days": "{days} days",
"custom": "{day_1} - {day_2} days"
},
"filters": {
"none": "Sem filtros",
"tier": "Tier {tier}"
},
"no_data": {
"battles_in_period": "Não há batalhas jogadas neste período",
"battles_in_season": "Não há batalhas jogadas nesta temporada",
"players_in_period": "Nenhum jogador neste período",
"tanks_found": "Nenhum tanque encontrado"
},
"buttons": {
"refresh": "Atualizar"
},
"hero_stats": {
"battles": "Batalhas",
"winrate": "Taxa de vitória",
"damage": "Dano",
"damage_ratio": "Razão entre dano dado e recebido",
"tier": "Nível",
"survival": "Sobrevivência",
"accuracy": "Precisão",
"kills": "Abates"
}
},
"commands": {
"dead": "# Stat Commands Are Dead\n\nThe BlitzKit Discord bot used to use BlitzStars to fetch periodic statistics. BlitzStars has enabled aggressive Cloudflare protections; hence, disabling BlitzKit from using BlitzStar's API. This has impacted the following commands:\n\n- `/stats`\n- `/today`\n- `/breakdown`\n- `/evolution`\n- `/full-stats`\n\nPlease use [Aftermath](<https://amth.one/invite>) as an alternative which is completely independent of BlitzStars. BlitzKit-hosted stat commands will return in the near future.",
"replay": {
"name": "replay",
"description": "Exibir resultados de replays",
"options": {
"file": {
"name": "file",
"description": "Replay file"
}
}
},
"research": {
"name": "pesquisa",
"description": "Liste as despesas na pesquisa de uma linha",
"options": {
"target_tank": {
"name": "tanque-alvo",
"description": "O último tanque para pesquisar na linha"
},
"starting_tank": {
"name": "tanque-inicial",
"description": "O tanque inicial na linha (inferido automaticamente se não fornecido)"
}
},
"body": {
"subtitle": "Pesquisa",
"total": "Total",
"research": "Research: {cost}",
"upgrades": "Upgrades: {cost}",
"purchase": "Purchase: {cost}",
"equipment": "Equipment: {cost}",
"estimation": "# Esta é uma estimativa!\n\nEsta lista pode incluir tanques já pesquisados devido à informação imprecisa que a Wargaming fornece. Tente fornecer um `tanque-inicial` da linha ao usar o comando, se puder."
},
"errors": {
"non_tech_tree": "# {tank} is not on the tech tree\n\nYou cannot research a premium/collector tank.",
"tier_1": "# {tank} is a tier I\n\nTier I tanks are always unlocked for free for all players.",
"already_researched": "# {tank} is already researched\n\nTry providing a `starting-tank` when using the command to view the totals for a line you have already researched.",
"start_end_equal": "# {tank} was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
"tanks_not_on_line": "# {tank_1} and {tank_2} are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
"unordered": "# {tank_1} higher tiered to {tank_2}\n\nMake sure to provide {tank_2} as the `starting-tank` instead as it is the lower tiered tank."
}
},
"about": {
"name": "sobre",
"description": "Todas as informações que você precisa sobre o bot",
"subcommands": {
"commands": {
"name": "comandos",
"description": "Lista de todos os comandos e suas descrições",
"body": "# Comandos\n\nBlitzKit oferece os seguintes comandos:\n"
},
"embeds": {
"name": "incorporar",
"description": "Como incorporações funcionam"
},
"introduction": {
"name": "Introdução",
"description": "Sobre o bot"
},
"invite": {
"name": "convite",
"description": "Como convidar o bot"
},
"timezones": {
"name": "fusos horários",
"description": "Como o BlitzKit inferi e usa seu fuso horário"
},
"credits": {
"name": "créditos",
"description": "Pessoas que ajudaram BlitzKit"
}
}
},
"breakdown": {
"name": "divisão",
"description": "Análise de um período por tanques jogados",
"body": {
"total": "Total",
"battles": "Batalhas",
"wn8": "WN8",
"winrate": "Taxa de vitória",
"damage": "Dano",
"unknown_tank": "Unknown tank {id}"
}
},
"debug": {
"name": "depuração",
"description": "Depurar informações sobre o bot",
"body": {
"title": "# Depuração\n",
"version": "Versão",
"shard": {
"label": "Fragmento",
"default": "padrão"
},
"uptime": "Tempo de atividade"
}
},
"evolution": {
"name": "evolução",
"description": "Evolução das estatísticas",
"groups": {
"player": {
"name": "jogador",
"description": "Estatísticas do Jogador"
},
"tank": {
"name": "tanque",
"description": "Estatísticas do tanque"
}
}
},
"inactive": {
"name": "inativo",
"description": "Liste todos os jogadores inativos em um clã acima de um limite",
"options": {
"threshold": {
"name": "limite",
"description": "The number of days inactive (default: {days} days)"
}
},
"body": {
"subtitle": "Inactive for {days}+ days",
"listing": "{days} days"
}
},
"full_stats": {
"name": "estatísticas-completas",
"description": "Estatísticas completas dentro do jogo",
"body": {
"cards": {
"games": {
"title": "Jogos",
"winrate": "Taxa de vitória",
"battles": "Batalhas",
"wins_losses": "Vitórias / Derrotas"
},
"efficiency": {
"title": "Eficiência",
"wn8": "WN8",
"survival": "Sobrevivência",
"accuracy": "Precisão"
},
"lethality": {
"title": "Letalidade",
"average_damage": "Dano médio",
"damage_per_tier": "Dano por nível",
"damage_ratio": "Razão entre dano dado e recebido",
"average_kills": "Média de abates"
},
"miscellaneous": {
"title": "Diversos",
"average_tier": "Nível médio",
"average_spots": "Média de detecções",
"average_xp": "EXP Médio",
"kills_to_death_ratio": "Razão entre abates e mortes"
}
}
}
},
"link": {
"name": "link",
"description": "Conecte sua conta Blitz e Discord",
"body": {
"accounts_linked": "Contas vinculadas",
"no_role_permissions": {
"title": "{name} failed to verify",
"description": "Eu não tenho permissão para gerenciar funções."
}
},
"embed": {
"title": "Conta incorreta?",
"description": "Use o comando novamente e selecione um nome de usuário dos resultados da pesquisa."
}
},
"search_clans": {
"name": "procurar-clãs",
"description": "Procurar clãs",
"options": {
"limit": {
"name": "limite",
"description": "The max number of clan results (default: {limit})"
}
},
"body": {
"title": "Clan search for {search} in region {region}",
"no_results": "Nenhum clã encontrado"
}
},
"search_players": {
"name": "pesquisa-jogadores",
"description": "Procurar jogadores",
"options": {
"limit": {
"name": "limite",
"description": "The max number of player results (default: {limit})"
}
},
"body": {
"title": "Player search for {search} in region {region}",
"no_results": "Nenhum jogador encontrado"
}
},
"rating_leaderboard": {
"name": "classificação-tabela de classificação",
"description": "Estatísticas de batalhas de classificação",
"subcommands": {
"neighbors": {
"name": "vizinhos",
"description": "Jogadores vizinhos na tabela de classificação"
},
"league": {
"name": "Liga",
"description": "Melhores jogadores de uma liga"
}
},
"options": {
"limit": {
"name": "limite",
"description": "How many neighbors to display (default: {count})"
},
"season": {
"name": "temporada",
"description": "Número da temporada (padrão: atual)",
"choices": {
"current": "{season} (current)"
}
},
"league": {
"name": "Liga",
"description": "A liga para exibir"
}
},
"body": {
"top_players": "Top {count} players",
"no_participation": "# {player} didn't play rating in season {season}\n\nThis player did not participate in this season or did not get past calibration.",
"no_ongoing_season": "# Nenhuma temporada em andamento\n\nNão há uma temporada de classificação em execução no momento.",
"deleted_player": "Deleted {id}",
"neighbors": "Classificação de vizinhos",
"more": "+ {count} more"
}
},
"today": {
"name": "hoje",
"description": "Tanques jogados hoje e estatísticas"
},
"stats": {
"name": "estatísticas",
"description": "Estatísticas rápidas para um período"
},
"owned_tanks": {
"name": "tanques-possuídos",
"description": "Mostra todos os tanques possuídos em um nível",
"body": {
"title": "# {name}'s owned tier {tier} tanks\n\n"
}
},
"permissions": {
"name": "Permissões",
"description": "Verifica se o bot tem as permissões necessárias para funcionar",
"body": {
"title": "# Permissions\n\n{body}",
"view_channels": "Ver canais: necessário para encontrar a mensagem em que o botão atualizar foi usado",
"read_message_history": "Histórico de mensagens lidas: necessário para encontrar a mensagem para atualizar",
"attach_files": "Anexar arquivos: necessário para adicionar imagens a comandos baseados em imagens",
"external_emojis": "Emojis externos: necessário para adicionar bandeiras nacionais e emojis de classe de tanque"
}
},
"ping": {
"name": "ping",
"description": "Verifique se o bot está vivo.",
"body": {
"pong": "Pong 🏓",
"pong_with_time": "Pong 🏓 - {time}ms"
},
"subcommands": {
"blitzkit": {
"name": "blitzkit",
"description": "Ping BlitzKit"
},
"wotb": {
"name": "wotb",
"description": "Ping World of Tanks Blitz"
}
}
},
"player_info": {
"name": "informação do jogador",
"description": "Informações básicas sobre um jogador",
"body": {
"title": "{name}'s information",
"nickname": "Apelido",
"battles": "Batalhas",
"winrate": "Taxa de vitória",
"account_id": "ID da conta",
"created": "Criado",
"last_battle": "Última batalha"
}
},
"rating": {
"name": "classificação",
"description": "Estatísticas de jogador de classificação",
"subcommands": {
"today": {
"name": "hoje",
"description": "Estatísticas de classificação hoje"
},
"season": {
"name": "temporada",
"description": "Estatísticas de classificação desta temporada"
}
},
"body": {
"subtitle": {
"today": "Estatísticas de classificação hoje",
"season": "Estatísticas de classificação desta temporada"
}
},
"errors": {
"no_participation": "# {name} hasn't played rating\n\nThis player hasn't participated in the rating gamemode this season.",
"unrecorded_stats": "# Estatísticas atualmente indisponível\n\nWargaming pode estar no momento, enfrentando tempo de inatividade do servidor. Se houver uma temporada em curso, as estatísticas deverão ser disponibilizadas dentro de 24 horas."
}
}
}
},
"website": {
"redirect": {
"title": "301: moved permanently",
@@ -741,14 +281,6 @@
}
},
"home": {
"pwa": {
"title": "Install the BlitzKit App",
"new": "NEW",
"checking": "Checking compatibility...",
"instructions": "Instructions for Apple devices.",
"install": "Install",
"dismiss": "Dismiss"
},
"discord": {
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
+5 -473
View File
@@ -37,10 +37,11 @@
"d_1": "Вчера"
},
"branches": {
"local": "dev",
"dev": "бета",
"opentest": "общий тест",
"preview": "превью"
"beta": "Бета",
"development": "Development",
"opentest": "Открытый тест",
"preview": "Preview",
"production": "Production"
},
"locales": {
"en": "Английский",
@@ -184,467 +185,6 @@
"cumulative_battle_life_time": "Общее время в боях"
}
},
"bot": {
"common": {
"errors": {
"clan_not_found": "# Не удалось найти клан\nНе найден «{search}». Попробуйте выбрать клан из результатов поиска.",
"player_not_found": "# Не удалось найти пользователя\nНе удалось найти пользователя «{search}». Попробуйте выбрать пользователя из результатов поиска, введя правильное имя пользователя или используя команду «/link».",
"player_not_linked": "# Используйте «/link», чтобы начать\nЯ не знаю, кто ты, но давай познакомимся сегодня!",
"tank_name_not_found": "# Танк не найден\nНе удалось найти танк с названием «{search}».",
"tank_id_not_found": "# Танк не найден\nНе удалось найти танк с ID «{search}». Попробуйте ввести название танка вместо числа.",
"no_tank_stats": "# Статистика танка недоступна\nУ этого игрока нет доступной статистики. Возможно, это не тот игрок, которого вы ищете.",
"get_help": "Получить помощь",
"uncaught_error": "# BlitzKit столкнулся с ошибкой!\nНам очень жаль. Не стесняйтесь и [присоединяйтесь к официальному серверу Discord](https://discord.gg/nDt7AjGJQH), чтобы получить помощь.",
"preview": "# Эта команда находится в тестируется\n\nВы можете получить доступ к этой команде только на [официальном сервере Discord] (https://discord.gg/nDt7AjGJQH). Команда скоро станет общедоступной.",
"query_too_small": "# Ваш поисковой слишком короткий\nДлина вашего запроса не менее 3 символов.",
"query_too_long": "# Ваш поисковой запрос слишком длинный\nДлина вашего запроса должна составлять не более 100 символов."
},
"wn8": {
"name": "WN8",
"types": {
"very_bad": "Очень плохо",
"bad": "Плохо",
"below_average": "Ниже среднего",
"average": "Средне",
"above_average": "Выше среднего",
"good": "Хорошо",
"very_good": "Очень хорошо",
"great": "Отлично",
"unicum": "Уникально",
"super_unicum": "Крайне уникально"
}
},
"subcommands": {
"today": {
"name": "cегодня",
"description": "Cегодня"
},
"30": {
"name": "30",
"description": "30 дней"
},
"60": {
"name": "60",
"description": "60 дней"
},
"90": {
"name": "90",
"description": "90 дней"
},
"career": {
"name": "карьера",
"description": "Карьера"
},
"custom": {
"name": "пользовательский",
"description": "Собственный диапазон",
"options": {
"start": {
"name": "начать",
"description": "Начать через N дней с сегодняшнего дня"
},
"end": {
"name": "конец",
"description": "Закончить через N дней с сегодняшнего дня"
}
}
}
},
"options": {
"clan": {
"name": "клан",
"description": "Название или тег клана"
},
"tank": {
"name": "танк",
"description": "Танк для получения статистики для"
},
"username": {
"name": "ник",
"description": "Ник игрока в Blitz"
},
"filters": {
"nation": {
"name": "нация",
"description": "Нация"
},
"class": {
"name": "класс",
"description": "Класс"
},
"type": {
"name": "тип",
"description": "Тип"
}
},
"region": {
"name": "регион",
"description": "Регион"
},
"tier": {
"name": "уровень",
"description": "Уровень",
"choices": {
"tier": "Уровень {tier_1} - {tier_2}"
}
}
},
"periods": {
"career": "Карьера",
"today": "Cегодня",
"days": "{days} дней",
"custom": "{day_1} - {day_2} дней"
},
"filters": {
"none": "Без фильтров",
"tier": "{tier} уровень"
},
"no_data": {
"battles_in_period": "За этот период боёв не было",
"battles_in_season": "За этот сезон боёв не было",
"players_in_period": "Нет игроков за этот период",
"tanks_found": "Танки не найдены"
},
"buttons": {
"refresh": "Обновить"
},
"hero_stats": {
"battles": "Боёв",
"winrate": "Коэффициент побед",
"damage": "Урон",
"damage_ratio": "Коэффициент урона",
"tier": "Уровень",
"survival": "Выживание",
"accuracy": "Точность стрельбы",
"kills": "Уничтожил"
}
},
"commands": {
"dead": "# Команды статистики мертвы\n\nБот BlitzKit использует BlitzStars для получения периодической статистики. BlitzStars включил агрессивную защиту Cloudflare; следовательно, отключив BlitzKit от использования API BlitzStar. Это повлияло на следующие команды:\n\n- `/stats`\n- `/today`\n- `/breakdown`\n- `/evolution`\n- `/full-stats`\n\nПожалуйста, используйте [Aftermath](<https://amth.one/invite>) в качестве альтернативы, которая совершенно не зависит от BlitzStars. Команда статистики BlitzKit-hosted вернется в ближайшее время.",
"replay": {
"name": "повтор",
"description": "Показать результаты повтора",
"options": {
"file": {
"name": "файл",
"description": "Запись повтора"
}
}
},
"research": {
"name": "исследование",
"description": "Список ресурсов для исследования ветки",
"options": {
"target_tank": {
"name": "желаемый танк",
"description": "Желаемый танк ветки исследования"
},
"starting_tank": {
"name": "стартовый-танк",
"description": "Стартовый танк в ветке (назначен автоматически, если не установлен)"
}
},
"body": {
"subtitle": "Исследование",
"total": "Всего",
"research": "Исследование: {cost}",
"upgrades": "Улучшения: {cost}",
"purchase": "Покупка: {cost}",
"equipment": "Снаряжение: {cost}",
"estimation": "# Это приблизительная оценка!\n\nВ этот список могут входить уже исследованные танки из-за неточной информации, предоставляемой Wargaming. Попробуйте указать «стартовый танк» в строке при использовании команды, если сможете."
},
"errors": {
"non_tech_tree": "# {tank} отсутствует в дереве танков\n\nВы не можете исследовать премиумный / коллекционный танк.",
"tier_1": "# {tank} — танк I уровня\n\nУровень I всегда открыт бесплатно для всех игроков.",
"already_researched": "# {tank} уже исследован\n\nПопробуйте предоставить «starting-tank» при использовании команды для просмотра итогов уже исследованной ветки.",
"start_end_equal": "# {tank} был предоставлен дважды\n\nУбедитесь, что «starting-tank» и «target-tank» различаются.",
"tanks_not_on_line": "# {tank_1} и {tank_2} находятся не в одной ветке\n\nУбедитесь, что «starting-tank» и «target-tank» находятся на той же ветке дерева танков.",
"unordered": "# У {tank_1} более высокий уровень чем у {tank_2}\n\nОбязательно укажите {tank_2} в качестве «starting-tank», так как это танк более низкого уровня."
}
},
"about": {
"name": "about",
"description": "Вся необходимая информация о боте",
"subcommands": {
"commands": {
"name": "команды",
"description": "Список всех команд и их описаний",
"body": "# Команды\n\nBlitzKit предлагает следующие команды:\n\n"
},
"embeds": {
"name": "embeds",
"description": "Как работают встраиваемые элементы"
},
"introduction": {
"name": "введение",
"description": "О боте"
},
"invite": {
"name": "пригласить",
"description": "Как пригласить бота"
},
"timezones": {
"name": "timezones",
"description": "Как BlitzKit определяет и использует ваш часовой пояс"
},
"credits": {
"name": "авторы",
"description": "Люди, которые помогли BlitzKit"
}
}
},
"breakdown": {
"name": "распределение",
"description": "Статистика игр за период с распределением по танкам",
"body": {
"total": "Всего",
"battles": "Боёв",
"wn8": "WN8",
"winrate": "Коэффициент побед",
"damage": "Урон",
"unknown_tank": "Неизвестный танк {id}"
}
},
"debug": {
"name": "отладка",
"description": "Сведения для отладки",
"body": {
"title": "# Debug\n\n",
"version": "Версия",
"shard": {
"label": "Фрагмент",
"default": "default"
},
"uptime": "Время работы"
}
},
"evolution": {
"name": "изменения",
"description": "Изменения статистики",
"groups": {
"player": {
"name": "игрок",
"description": "Статистика игрока"
},
"tank": {
"name": "танк",
"description": "Статистика танков"
}
}
},
"inactive": {
"name": "неактивно",
"description": "Список всех неактивных игроков в клане выше порога",
"options": {
"threshold": {
"name": "порог",
"description": "Количество дней не активности (по умолчанию: {days} дней)"
}
},
"body": {
"subtitle": "Неактивен {days}+ дней",
"listing": "{days{days}"
}
},
"full_stats": {
"name": "full-stats",
"description": "Полная игровая статистика",
"body": {
"cards": {
"games": {
"title": "Игры",
"winrate": "Коэффициент побед",
"battles": "Боёв",
"wins_losses": "Побед / поражений"
},
"efficiency": {
"title": "Эффективность",
"wn8": "WN8",
"survival": "Выжил",
"accuracy": "Точность стрельбы"
},
"lethality": {
"title": "Смертность",
"average_damage": "Средний урон",
"damage_per_tier": "Урон по уровню",
"damage_ratio": "Коэффициент урона",
"average_kills": "Среднее число уничтоженных"
},
"miscellaneous": {
"title": "Прочее",
"average_tier": "Средний уровень",
"average_spots": "Среднее число попаданий",
"average_xp": "Средний опыт",
"kills_to_death_ratio": "Коэффициент убийств и смертей"
}
}
}
},
"link": {
"name": "ссылка",
"description": "Привяжите учётную запись Blitz и Discord",
"body": {
"accounts_linked": "Аккаунты привязаны",
"no_role_permissions": {
"title": "{name} не удалось проверить",
"description": "У меня нет разрешения на управление ролями."
}
},
"embed": {
"title": "Неверный аккаунт?",
"description": "Используйте команду снова и выберите имя пользователя из результатов поиска."
}
},
"search_clans": {
"name": "search-clans",
"description": "Поиск кланов",
"options": {
"limit": {
"name": "лимит",
"description": "Максимальное количество результатов клана (по умолчанию: {limit})"
}
},
"body": {
"title": "Поиск клана {search} в регионе {region}",
"no_results": "Кланов не найдено"
}
},
"search_players": {
"name": "search-players",
"description": "Поиск игроков",
"options": {
"limit": {
"name": "лимит",
"description": "Максимальное число результатов игрока (по умолчанию: {limit})"
}
},
"body": {
"title": "Поиск игрока {search} в регионе {region}",
"no_results": "Игроков не найдено"
}
},
"rating_leaderboard": {
"name": "rating-leaderboard",
"description": "Статистика рейтингов боёв",
"subcommands": {
"neighbors": {
"name": "ближайшие",
"description": "Ближайшие игроки в таблице лидеров"
},
"league": {
"name": "лига",
"description": "Топ игроков лиги"
}
},
"options": {
"limit": {
"name": "лимит",
"description": "Количество отображаемых ближайших игроков (по умолчанию: {count})"
},
"season": {
"name": "сезон",
"description": "Номер сезона (по умолчанию: текущий)",
"choices": {
"current": "{season} (текущий)"
}
},
"league": {
"name": "лига",
"description": "Отображаемая лига"
}
},
"body": {
"top_players": "Топ {count} игроков",
"no_participation": "# {player} не играл в рейтинг в сезоне {season}\n\nЭтот игрок не участвовал в этом сезоне или не получил прошлую калибровку.",
"no_ongoing_season": "# Нет текущего сезона\n\nВ данный момент нет рейтингового сезона.",
"deleted_player": "Удаленный {id}",
"neighbors": "Рейтинг ближайших игроков",
"more": "+ ещё {count}"
}
},
"today": {
"name": "today",
"description": "Танки, на которых сыграли сегодня и статистика"
},
"stats": {
"name": "статистика",
"description": "Быстрая статистика за период"
},
"owned_tanks": {
"name": "cобственные-танки",
"description": "Показывает все принадлежащие танки на уровне",
"body": {
"title": "# {name} принадлежат танки {tier} уровня\n\n"
}
},
"permissions": {
"name": "разрешения",
"description": "Проверяет, есть ли у бота необходимые разрешения для работы",
"body": {
"title": "# Разрешения\n\n{body}",
"view_channels": "Просмотр каналов: необходимо найти сообщение, на котором была использована кнопка обновления",
"read_message_history": "Просмотр истории сообщений: необходимо найти сообщение для обновления",
"attach_files": "Прикрепить файлы: необходимо для добавления изображений в команды на основе изображения",
"external_emojis": "Внешние эмоции: необходимы для добавления национальных флагов и эмодзи класса танков"
}
},
"ping": {
"name": "пинг",
"description": "Проверьте, что бот жив.",
"body": {
"pong": "Понг 🏓",
"pong_with_time": "Понг 🏓 — {time}мс"
},
"subcommands": {
"blitzkit": {
"name": "blitzkit",
"description": "Пинг BlitzKit"
},
"wotb": {
"name": "wotb",
"description": "Пинг World of Tanks Blitz"
}
}
},
"player_info": {
"name": "player-ifor",
"description": "Basic information about a player",
"body": {
"title": "Информация о {name}",
"nickname": "Никнейм",
"battles": "Боёв",
"winrate": "Коэффициент побед",
"account_id": "ID учётной записи",
"created": "Создано",
"last_battle": "Последний бой"
}
},
"rating": {
"name": "рейтинг",
"description": "Статистика рейтинга игрока",
"subcommands": {
"today": {
"name": "cегодня",
"description": "Статистика рейтинга за сегодня"
},
"season": {
"name": "сезон",
"description": "Статистика рейтинга в этом сезоне"
}
},
"body": {
"subtitle": {
"today": "Статистика рейтинга за сегодня",
"season": "Статистика рейтинга в этом сезоне"
}
},
"errors": {
"no_participation": "# {name} не получил рейтинга\n\nЭтот игрок в этом сезоне не участвовал в рейтинговом режиме.",
"unrecorded_stats": "# Статистика недоступна в настоящее время\n\nВ настоящее время Wargaming, возможно, сталкивается с простоями сервера. Если сезон продолжается, статистика должна быть доступна в течение 24 часов."
}
}
}
},
"website": {
"redirect": {
"title": "301: перемещено навсегда",
@@ -741,14 +281,6 @@
}
},
"home": {
"pwa": {
"title": "Установите приложение BlitzKit",
"new": "НОВИНКА",
"checking": "Проверка совместимости...",
"instructions": "Инструкция для устройств Apple.",
"install": "Установка",
"dismiss": "Отклонить"
},
"discord": {
"join_short": "Присоединяйтесь к крутым ребятам!",
"join_long": "Присоединяйся к супер-дупер крутым ребятам!",
+42 -510
View File
@@ -37,10 +37,11 @@
"d_1": "Вчора"
},
"branches": {
"local": "розробник",
"dev": "бета",
"opentest": "відкритий тест",
"preview": "попередній перегляд"
"beta": "Beta",
"development": "Development",
"opentest": "Відкритий тест",
"preview": "Preview",
"production": "Production"
},
"locales": {
"en": "Англійська",
@@ -184,467 +185,6 @@
"cumulative_battle_life_time": "Загальний час бою"
}
},
"bot": {
"common": {
"errors": {
"clan_not_found": "# Не вдалося знайти клан\nНе вдалося знайти \"{search}\". Спробуйте обрати клан з результату пошуку.",
"player_not_found": "# Не вдалося знайти користувача\nНе вдалося знайти користувача \"{search}\". Спробуйте вибрати користувача із результатів пошуку, ввівши правильне ім'я користувача, або скористайтеся командою `/link`.",
"player_not_linked": "# Використайте команду \"/link\" щоб почати\nЯ не знаю, хто ви, але ви можете сказати мені сьогодні!",
"tank_name_not_found": "# Танк не знайдено\nНе вдалося знайти танк із назвою «{search}».",
"tank_id_not_found": "# Танк не знайдено\nНе вдалося знайти танк з ID «{search}». Спробуйте ввести назву танка замість номера.",
"no_tank_stats": "# Немає статистики для танка\nЦей гравець не має статистики.\nМожливо, це не той гравець, якого ви шукаєте.",
"get_help": "Допомогти Discord серверу",
"uncaught_error": "# Упс! BlitzKit зіткнувся з помилкою!\nНам дуже шкода. Можете попросити допомоги на [офіційному сервері Discord](https://discord.gg/nDt7AjGJQH).",
"preview": "# Ця команда є в попередньому перегляді\n\nВи можете отримати доступ лише до цієї команди на [офіційному сервері Discord](https://discord.gg/nDt7AjGJQH). Команда в цілому буде доступна найближчим часом.",
"query_too_small": "# Ваш пошук занадто короткий\nВаш пошук повинен бути щонайменше 3 символи.",
"query_too_long": "# Ваш пошук занадто довгий\nВаш пошук повинен бути щонайбільше 100 символи."
},
"wn8": {
"name": "WN8",
"types": {
"very_bad": "Дуже погано",
"bad": "Погано",
"below_average": "Нижче середнього",
"average": "Середнє",
"above_average": "Вище середнього",
"good": "Добре",
"very_good": "Дуже добре",
"great": "Відмінно",
"unicum": "Винятковий",
"super_unicum": "Супер винятковий"
}
},
"subcommands": {
"today": {
"name": "сьогодні",
"description": "Сьогодні"
},
"30": {
"name": "30",
"description": "30 днів"
},
"60": {
"name": "60",
"description": "60 днів"
},
"90": {
"name": "90",
"description": "90 днів"
},
"career": {
"name": "кар'єра",
"description": "Кар'єра"
},
"custom": {
"name": "користувацьке",
"description": "Власний діапазон",
"options": {
"start": {
"name": "почати",
"description": "Початок за кількістю днів тому від сьогодні"
},
"end": {
"name": "кінець",
"description": "Кінець за кількістю днів тому від сьогодні"
}
}
}
},
"options": {
"clan": {
"name": "клан",
"description": "Назва клану або тег"
},
"tank": {
"name": "танк",
"description": "Танк для якого треба отримати статистику"
},
"username": {
"name": "ім'я користувача",
"description": "Бліц ім'я гравця"
},
"filters": {
"nation": {
"name": "нація",
"description": "Нація"
},
"class": {
"name": "клас",
"description": "Клас"
},
"type": {
"name": "вид",
"description": "Вид"
}
},
"region": {
"name": "регіон",
"description": "Регіон"
},
"tier": {
"name": "рівень",
"description": "Рівень",
"choices": {
"tier": "Рівень {tier_1} - {tier_2}"
}
}
},
"periods": {
"career": "Кар'єра",
"today": "Сьогодні",
"days": "{days} днів",
"custom": "{day_1} - {day_2} днів"
},
"filters": {
"none": "Немає фільтрів",
"tier": "Рівень {tier}"
},
"no_data": {
"battles_in_period": "За цей період боїв не було",
"battles_in_season": "У цьому сезоні боїв не було",
"players_in_period": "Немає гравців за цей період",
"tanks_found": "Жодних танків не знайдено"
},
"buttons": {
"refresh": "Оновити"
},
"hero_stats": {
"battles": "Бої",
"winrate": "Відсоток перемог",
"damage": "Шкода",
"damage_ratio": "Коефіцієнт шкоди",
"tier": "Рівень",
"survival": "Виживання",
"accuracy": "Точність",
"kills": "Знищено"
}
},
"commands": {
"dead": ".",
"replay": {
"name": "реплей",
"description": "Показати результати реплею",
"options": {
"file": {
"name": "",
"description": "Replay filehttps://crowdin.com/editor/blitzkrieg/80/en-uk?view=side-by-side#9706"
}
}
},
"research": {
"name": "дослідження",
"description": "Показує витрати в дослідженні гілки",
"options": {
"target_tank": {
"name": "цільовий танк",
"description": "Останній танк для дослідження в гілці"
},
"starting_tank": {
"name": "початковий танк",
"description": "Початковий танк в гілці (виводиться автоматично, якщо не передбачено)"
}
},
"body": {
"subtitle": "Дослідження",
"total": "Загалом",
"research": "Дослідження: {cost}",
"upgrades": "Прокачування: {cost}",
"purchase": "Вартість: {cost}",
"equipment": "Спорядження: {cost}",
"estimation": "# Це оцінка!\n\nЦей список може включати вже досліджені танки через неточну інформацію для вантажу. Спробуйте використати \"початковий танк\" з рядка за допомогою команди."
},
"errors": {
"non_tech_tree": "#{tank} відсутній в дереві дослідження\n\nВи не можете дослідити преміум/колеційний танк.",
"tier_1": "#{tank} І рівня \n\nТанки І рівня завжди розблоковані для всіх гравців безкоштовно",
"already_researched": "# {tank} вже було досліджено\n\nСпробуйте використати `початковий-танк` коли використовуєте команду для перегляду загальної гілки, яку ви вже дослідили",
"start_end_equal": "#{tank} було введено двічі \n\nПереконайтесь що `початковий-танк` та `ціль-танк` різні",
"tanks_not_on_line": "# {tank_1} та {tank_2} не в одній гілці\n\nПереконайтесь що `початковий-танк` та `ціль-танк` знаходяться в єдиній гілці дерева дослідження",
"unordered": "# {tank_1} вище рівнем відносно {tank_2}\n\nОбов’язково вкажіть {tank_2} як `початковий-танк`, оскільки це танк нижчого рівня."
}
},
"about": {
"name": "про бота",
"description": "Вся інформація про бота",
"subcommands": {
"commands": {
"name": "команди",
"description": "Список всіх команд та їх описів",
"body": "# Команди\n\nBlitzKit пропонує наступні команди:\n\n"
},
"embeds": {
"name": "вкладення",
"description": "Як працюють вкладення"
},
"introduction": {
"name": "ознайомлення",
"description": "Про бота"
},
"invite": {
"name": "запрошення",
"description": "Як запросити бота"
},
"timezones": {
"name": "часові пояси",
"description": "Як BlitzKit отримує інформацію про час і використовує ваш часовий пояс"
},
"credits": {
"name": "кредити",
"description": "Люди, які допомогли BlitzKit"
}
}
},
"breakdown": {
"name": "аналіз",
"description": "Аналіз періоду за зіграними танками",
"body": {
"total": "Загалом",
"battles": "Бої",
"wn8": "WN8",
"winrate": "Відсоток перемог",
"damage": "Шкода",
"unknown_tank": "Невідомий танк {id}"
}
},
"debug": {
"name": "налагодження",
"description": "Інформація про налагодження бота",
"body": {
"title": "# Налагодження\n\n",
"version": "Версія",
"shard": {
"label": "Уламок",
"default": "за замовчуванням"
},
"uptime": "Час роботи"
}
},
"evolution": {
"name": "еволюція",
"description": "Еволюція статистики",
"groups": {
"player": {
"name": "гравець",
"description": "Статистика гравця"
},
"tank": {
"name": "танк",
"description": "Статистика танків"
}
}
},
"inactive": {
"name": "неактивний",
"description": "Відображає всіх неактивних гравців у клані вище порогу",
"options": {
"threshold": {
"name": "поріг",
"description": "Кількість днів неактивності (за замовчуванням: {days} днів)"
}
},
"body": {
"subtitle": "Неактивний {days}+ днів",
"listing": "{days} днів"
}
},
"full_stats": {
"name": "повна статистика",
"description": "Повна ігрова статистика",
"body": {
"cards": {
"games": {
"title": "Ігри",
"winrate": "Відсоток перемог",
"battles": "Бої",
"wins_losses": "Перемоги / поразки"
},
"efficiency": {
"title": "Ефективність",
"wn8": "WN8",
"survival": "Виживання",
"accuracy": "Точність"
},
"lethality": {
"title": "Летальність",
"average_damage": "Середня шкода",
"damage_per_tier": "Шкода за рівень",
"damage_ratio": "Коефіцієнт шкоди",
"average_kills": "Середня кількість знищень"
},
"miscellaneous": {
"title": "Різне",
"average_tier": "Середній рівень",
"average_spots": "Середній засвіт",
"average_xp": "Середній досвід",
"kills_to_death_ratio": "Співвідношення знищень до смертей"
}
}
}
},
"link": {
"name": "посилання",
"description": "Прив'язує ваш Blitz та Discord акаунт",
"body": {
"accounts_linked": "Прив'язані акаунти",
"no_role_permissions": {
"title": "{name} не вдалося перевірити",
"description": "У мене немає дозволу керувати ролями."
}
},
"embed": {
"title": "Невірний обліковий запис?",
"description": "Використайте команду ще раз і виберіть ім'я користувача з результатів пошуку."
}
},
"search_clans": {
"name": "пошук кланів",
"description": "Пошук кланів",
"options": {
"limit": {
"name": "ліміт",
"description": "Максимальна кількість результатів клану (за замовчуванням: {limit})"
}
},
"body": {
"title": "Пошук клану для {search} в регіоні {region}",
"no_results": "Жодного клану не знайдено"
}
},
"search_players": {
"name": "пошук гравців",
"description": "Пошук гравців",
"options": {
"limit": {
"name": "ліміт",
"description": "Максимальна кількість результатів гравця (за замовчуванням: {limit})"
}
},
"body": {
"title": "Пошук гравця для {search} у регіоні {region}",
"no_results": "Гравця не знайдено"
}
},
"rating_leaderboard": {
"name": "таблиця рейтингу",
"description": "Статистика рейтингових боїв",
"subcommands": {
"neighbors": {
"name": "сусіди",
"description": "Сусідуючі гравці в лідерборді"
},
"league": {
"name": "ліга",
"description": "Найкращий гравець ліги"
}
},
"options": {
"limit": {
"name": "ліміт",
"description": "Скільки сусідів відобразити (за замовчуванням: {count})"
},
"season": {
"name": "сезон",
"description": "Базова інформація про гравця",
"choices": {
"current": "{season} (поточний)"
}
},
"league": {
"name": "ліга",
"description": "Ліга для відображення"
}
},
"body": {
"top_players": "Топ {count} гравців",
"no_participation": "# {player} не грав у рейтингу в сезоні {season}\n\nЦей гравець не брав участь у цьому сезоні або не проходив калібрування",
"no_ongoing_season": "# Немає поточного сезону\n\nВ даний час немає сезону рейтингу.",
"deleted_player": "",
"neighbors": "Сусіди по рейтингу",
"more": "+ {count} ще"
}
},
"today": {
"name": "сьогодні",
"description": "Сьогодні зіграні танки та статистика"
},
"stats": {
"name": "статистика",
"description": "Коротка статистика за період"
},
"owned_tanks": {
"name": "отримані танки",
"description": "Показує всі ваші танки даного рівня",
"body": {
"title": "# {name} отриманих рівнів {tier} танків\n\n"
}
},
"permissions": {
"name": "дозволи",
"description": "Перевіряє чи бот має необхідні дозволи для роботи",
"body": {
"title": "# Дозволи\n\n{body}",
"view_channels": "Перегляньте канали: необхідне для знаходження повідомлення, на якому використовувалася кнопка оновлення",
"read_message_history": "Прочитайте історію повідомлень: потрібно знайти повідомлення для оновлення",
"attach_files": "Закріпіть файли: потрібно для додавання зображень до команд на основі зображень",
"external_emojis": "Зовнішні емодзі: потрібні для додавання емодзі національних прапорів та класів техніки"
}
},
"ping": {
"name": "",
"description": "Перевірити чи бот живий",
"body": {
"pong": "Понг🏓",
"pong_with_time": "Понг🏓 - {time}мс"
},
"subcommands": {
"blitzkit": {
"name": "BlitzKit",
"description": "Ping BlitzKit"
},
"wotb": {
"name": "WoTB",
"description": "Ping World of Tanks Blitz"
}
}
},
"player_info": {
"name": "інформація про гравця",
"description": "Базова інформація про гравця",
"body": {
"title": "інформація про {name}",
"nickname": "Нік",
"battles": "Бої",
"winrate": "Відсоток перемог",
"account_id": "ID акаунта",
"created": "Створено",
"last_battle": "Остання битва"
}
},
"rating": {
"name": "рейтинг",
"description": "Статистика рейтингових боїв гравця ",
"subcommands": {
"today": {
"name": "сьогодні",
"description": "Сьогоднішня статистика рейтингових боїв"
},
"season": {
"name": "сезон",
"description": "Статистика рейтингових боїв поточного сезону"
}
},
"body": {
"subtitle": {
"today": "Сьогоднішня статистика рейтингових боїв",
"season": "Статистика рейтингових боїв даного сезону"
}
},
"errors": {
"no_participation": "# {name} не грав рейтингові бої\n\nЦей гравець не брав участь у рейтинговому режимі цього сезону.",
"unrecorded_stats": "# Статистика на цей момент недоступна\n\nWargaming наразі зіткнулась з технічними несправностями. Якщо зараз проходить сезон, статистика буде доступною в найближчі 24 години."
}
}
}
},
"website": {
"redirect": {
"title": "301: переміщено назавжди",
@@ -741,30 +281,22 @@
}
},
"home": {
"pwa": {
"title": "",
"new": "",
"checking": "",
"instructions": ".",
"install": "",
"dismiss": "Dismiss"
},
"discord": {
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
"join_short": "Приєднуйся до суспільства!",
"join_long": "Приєднуйся до найкрутішого суспільства!",
"action": "Discord"
},
"hero": {
"credit": "By {name} ❤️"
"credit": "Від {name} ❤️"
},
"hot_tanks": {
"title": "Популярні танки",
"subtitle": "Last {days} days"
"subtitle": "Останні {days} днів"
},
"other_projects": {
"title": "Інші класні проєкти"
},
"help_translate": "Please help translate {name} to {language}!",
"help_translate": "Будь ласка, допоможіть перекласти {name} на {language}!",
"seo_description": "Інструменти для всього, пов'язаного з WoT Blitz"
},
"docs": {
@@ -778,7 +310,7 @@
"reset": "Скинути",
"shuffle": "Перемішати",
"remove": "Видалити зіграні",
"tracking": "BlitzKit is tracking your battles",
"tracking": "BlitzKit відстежує ваші бої",
"sort": {
"button": "Сортувати за",
"last_played": "Остання гра",
@@ -804,15 +336,15 @@
"description": "Tank playlist generator",
"seo_description": "Don't know what to play in World of Tanks Blitz? Use BlitzKit Playlist to get suggestions!",
"button": "Створити",
"pool_size": "{count} possible tanks"
"pool_size": "{count} можливі танки"
},
"mixer": {
"name": "Mixer",
"button": "Mix and match",
"description": "Mix tank modules",
"seo_description": "Mix and match hulls, turrets, and guns of tanks in World of Tanks Blitz",
"screenshot": "Screenshot",
"randomize": "Randomize"
"screenshot": "Знімок екрану",
"randomize": "Рандомізувати"
},
"guess": {
"name": "Guess",
@@ -821,9 +353,9 @@
"seo_description": "Play a tank guessing game with a silhouette in World of Tanks Blitz",
"search": {
"placeholder": "Type to guess...",
"skip": "Skip",
"skip": "Пропустити",
"guess": "Guess",
"next": "Next",
"next": "Наступний",
"no_results": "Немає результатів"
},
"stats": "{correct} / {total} correct, {streak} streak",
@@ -838,7 +370,7 @@
"tier": "Рівень {tier}"
},
"tankopedia": {
"search": "Search a stat...",
"search": "Шукаємо стати...",
"name": "Танкопедія",
"button": "Знайти танки",
"description": "Танкова енциклопедія",
@@ -909,7 +441,7 @@
"ricochet": "Рикошет",
"explosion_radius": "Радіус розльоту уламків",
"exit": "Вийти",
"reset": "Reset"
"reset": "Скинути"
},
"advanced": "Advanced highlighting",
"shot_card": {
@@ -918,7 +450,7 @@
"spaced": "Spaced",
"external": "External",
"gap": "Gap",
"gun": "Gun",
"gun": "Пушка",
"track": "Гусениці"
},
"status": {
@@ -928,7 +460,7 @@
"splash": "{damage} Од"
},
"stats": {
"thickness_and_angle": "{thickness}mm {angle}°",
"thickness_and_angle": "{thickness}мм {angle}°",
"nominal": "{thickness}mm nominal",
"ricochet_loss": "{loss}% penetration"
}
@@ -941,7 +473,7 @@
"external": "External",
"edit": {
"label": "Редагувати",
"reset": "Reset"
"reset": "Скинути"
}
}
},
@@ -954,7 +486,7 @@
},
"vanilla": "Vanilla",
"opentest": "Відкритий тест",
"full_name": "Full name",
"full_name": "Повна назва",
"nation": "Нація",
"class": "Клас",
"tier": "Рівень",
@@ -1086,7 +618,7 @@
"hullTraverseHardTerrain": "Hard",
"hullTraverseMediumTerrain": "Середній",
"hullTraverseSoftTerrain": "Soft",
"health": "Health",
"health": "Здоров'я",
"fireChance": "Fire chance",
"viewRange": "View range",
"camouflageStill": "Нерухомо",
@@ -1211,10 +743,10 @@
"name": "Gallery",
"button": "Browse all",
"description": "Blitz player avatars",
"seo_description": "All World of Tanks Blitz player avatars",
"seo_description": "Усі World of Tanks Blitz аватари гравців",
"last_updated": "Last updated on {time}",
"search": {
"hint": "Search avatars...",
"hint": "Шукаємо аватари...",
"results": "{count} avatars"
}
},
@@ -1251,18 +783,18 @@
}
},
"tier_list": {
"name": "Tier list",
"button": "Rank tanks",
"description": "Rank Blitz tanks",
"name": "Список рівнів",
"button": "Ранги танків",
"description": "Ранги танків з блітзу",
"seo_description": "Explore tier lists ranking tanks from best to worst, based on player ratings and statistical performance in World of Tanks Blitz",
"buttons": {
"reset": "Reset",
"share": "Share"
"reset": "Скинути",
"share": "Поділитися"
}
},
"embed": {
"name": "Embed",
"button": "Create now",
"button": "Створити зараз",
"description": "Embeds for streaming",
"seo_description": "Create embeds for streaming World of Tanks Blitz with extreme customization",
"coming_soon": "More embeds coming soon.",
@@ -1273,20 +805,20 @@
},
"configuration": {
"import": {
"title": "Import",
"title": "Імпортувати",
"description": "Paste your current URL if you have one already to import your settings",
"hint": "Paste URL...",
"hint": "Вставити посилання...",
"apply": "Apply",
"idiot_title": "Invalid URL",
"idiot_title": "Некоректне посилання",
"idiot_description": "Try pasting the URL that BlitzKit previously generated, you silly goose!",
"idiot_button": "Okay, my bad"
},
"export": {
"title": "Export",
"title": "Експортувати",
"description": "Copy all properties for you streaming software",
"generate": {
"button": "Generate URL",
"title": "Generate URL",
"button": "Згенерувати посилання",
"title": "Генерація посилання",
"subtitle": "Search an account to track",
"cancel": "",
"success": {
@@ -1303,7 +835,7 @@
},
"customize": {
"title": "Customize",
"reset": "Reset",
"reset": "Скинути",
"description": "Bells and whistles of your embed"
}
},
@@ -1351,14 +883,14 @@
"seo_description": "Live and archived full rating leaderboard for World of Tanks Blitz"
},
"discord": {
"name": "Discord bot",
"button": "Install now",
"name": "Діскорд бот",
"button": "Встановити зараз",
"description": "Stats right in Discord",
"seo_description": "Get stats right in Discord with BlitzKit's Discord bot"
},
"more": {
"name": "More coming soon",
"button": "Join Discord",
"name": "Буде через певний час",
"button": "Приєднатись до Діскорду",
"description": "What we're making",
"seo_description": "What we're making with BlitzKit"
},
+28 -496
View File
@@ -13,18 +13,18 @@
"hp": "{value} 生命值",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}度°/秒",
"deg_s": "{value}度每秒",
"plus": "+{value}"
},
"gun_types": {
"auto_loader": "弹夹型坦克",
"auto_reloader": "自动装填型坦克",
"auto_reloader": "弹鼓型坦克",
"regular": "单发"
},
"shells": {
"ap": "穿甲弹",
"ap_cr": "硬式复合穿甲弹",
"hc": "反战车高爆弹",
"hc": "破甲弹",
"he": "高爆弹"
},
"player_count_period": {
@@ -37,10 +37,11 @@
"d_1": "昨日"
},
"branches": {
"local": "开发者",
"dev": "测试版",
"beta": "测试版",
"development": "Development",
"opentest": "开放测试",
"preview": "预览"
"preview": "Preview",
"production": "Production"
},
"locales": {
"en": "英语",
@@ -60,23 +61,23 @@
"3": "平均",
"4": "高于平均",
"5": "好",
"6": "非常好",
"6": "好",
"7": "很好",
"8": "极品",
"9": "超级强势玩家"
"8": "非常好",
"9": "极品"
},
"bkni_percentile": {
"0": "Super malum",
"1": "Malum",
"2": "非常差",
"0": "垃圾",
"1": "非常差",
"2": "很差",
"3": "差",
"4": "低于平均",
"5": "平均",
"6": "高于平均",
"6": "较好",
"7": "好",
"8": "非常好",
"9": "極品",
"10": "超级强势玩家"
"8": "很好",
"9": "非常好",
"10": "极品"
},
"nations": {
"ussr": "苏联",
@@ -101,7 +102,7 @@
"other": "X系"
},
"tank_class_short": {
"-1": "未识别",
"-1": "未知",
"0": "轻型坦克",
"1": "中型坦克",
"2": "重型战车",
@@ -114,14 +115,14 @@
"3": "坦克歼击车"
},
"tank_class_medium": {
"-1": "未识别",
"-1": "未知",
"0": "轻型坦克",
"1": "中型坦克",
"2": "重型坦克",
"3": "坦克歼击车"
},
"tree_type": {
"-1": "未识别",
"-1": "未知",
"0": "科技树",
"1": "金币车",
"2": "收藏车"
@@ -156,7 +157,7 @@
"normalized_shots": "开火次数",
"normalized_spotted": "侦查次数",
"normalized_survived_battles": "存活率",
"normalized_win_and_survived": "存活胜利率",
"normalized_win_and_survived": "胜利并幸存的概率",
"normalized_wins": "胜率",
"normalized_xp": "经验值",
"normalized_battle_life_time": "战斗时间",
@@ -184,467 +185,6 @@
"cumulative_battle_life_time": "总战斗时间"
}
},
"bot": {
"common": {
"errors": {
"clan_not_found": "# 找不到公会\n找不到\"{search}\"。请尝试从搜索结果中选择一个公会。",
"player_not_found": "# 找不到玩家\n无法找到玩家 “{search}”。 尝试从搜索结果中选择玩家,或输入一个有效的玩家名称, 或使用“/link”命令。",
"player_not_linked": "# 使用 `/link` 来开始\n我不知道你是谁,但你今天可以告诉我!",
"tank_name_not_found": "# 找不到坦克\n找不到名为 “{search}” 的坦克。",
"tank_id_not_found": "# 找不到坦克\n无法找到ID为 “{search}” 的坦克。 请尝试输入坦克的名称而不是数字。",
"no_tank_stats": "# 没有这辆战车的数据\n这个玩家没有可用资料。 这可能不是你要找的玩家。",
"get_help": "Discord的协助服务器",
"uncaught_error": "# BlitzKit 发生错误!\n我们对此感到抱歉。 欢迎加入[官方Discord](https://discord.gg/nDt7AjGJQH),以方便帮助你解决问题。",
"preview": "# 此命令处于预览\n\n您只能在[官方Discord服务器](https://discord.gg/nDt7AjGJQH)访问此命令。该命令将很快可用。",
"query_too_small": "# 搜寻内容过短\n您的搜寻必需至少包含 3 个字元。",
"query_too_long": "# 搜索内容过长\n您搜索的内容必须少于 100 个字符。"
},
"wn8": {
"name": "WN8",
"types": {
"very_bad": "非常差",
"bad": "差",
"below_average": "低于平均",
"average": "平均",
"above_average": "高于平均",
"good": "好",
"very_good": "非常好",
"great": "很好",
"unicum": "极品",
"super_unicum": "超强势玩家"
}
},
"subcommands": {
"today": {
"name": "今日",
"description": "今日:"
},
"30": {
"name": "30",
"description": "30 天"
},
"60": {
"name": "60",
"description": "60 天"
},
"90": {
"name": "90",
"description": "90 天"
},
"career": {
"name": "生涯",
"description": "玩家资料"
},
"custom": {
"name": "自定义",
"description": "自定义范围",
"options": {
"start": {
"name": "开始",
"description": "距今若干天前开始"
},
"end": {
"name": "结束",
"description": "距今若干天前开结束"
}
}
}
},
"options": {
"clan": {
"name": "公会",
"description": "公会名称或标签"
},
"tank": {
"name": "坦克",
"description": "要获取战绩的坦克"
},
"username": {
"name": "用户名",
"description": "玩家的闪击战游戏昵称"
},
"filters": {
"nation": {
"name": "国别",
"description": "国别"
},
"class": {
"name": "坦克车型",
"description": "坦克车型"
},
"type": {
"name": "战车类型",
"description": "战车类型"
}
},
"region": {
"name": "所在地区",
"description": "地区"
},
"tier": {
"name": "阶级",
"description": "阶级",
"choices": {
"tier": "{tier_1} - {tier_2} 阶"
}
}
},
"periods": {
"career": "玩家资料",
"today": "今日:",
"days": "{days} 天",
"custom": "{day_1} - {day_2} 天"
},
"filters": {
"none": "无筛选器",
"tier": "{tier} 阶"
},
"no_data": {
"battles_in_period": "在这段时间里没有参与任何战斗",
"battles_in_season": "在这个赛季中没有战斗记录",
"players_in_period": "这段时间内没有玩家",
"tanks_found": "找不到战车"
},
"buttons": {
"refresh": "刷新"
},
"hero_stats": {
"battles": "战斗场次",
"winrate": "胜率",
"damage": "伤害",
"damage_ratio": "伤害比",
"tier": "阶级",
"survival": "生存",
"accuracy": "命中率",
"kills": "击杀数"
}
},
"commands": {
"dead": "# 统计指令现已停用\nBlitzKit Discord 机器人此前依赖 BlitzStars 获取周期性的统计数据。由于 BlitzStars 已启用严格的 Cloudflare 防护,导致 BlitzKit 无法继续使用其 API。这已导致以下指令失效:\n/统计\n/今日\n/损坏\n/演变\n/完整统计\n请使用 [Aftermath](<https://amth.one/invite>) \n作为完全独立于 BlitzStars 的替代方案。由 BlitzKit 托管的统计指令将在近期内恢复。",
"replay": {
"name": "回放",
"description": "显示重播结果",
"options": {
"file": {
"name": "file",
"description": "Replay file"
}
}
},
"research": {
"name": "研发",
"description": "列出研发线路的费用",
"options": {
"target_tank": {
"name": "目标战车",
"description": "需要研发的目标(战车)"
},
"starting_tank": {
"name": "起始战车",
"description": "研发线路中的起始战车(如果未提供则自动推定)"
}
},
"body": {
"subtitle": "研发",
"total": "全部",
"research": "研发:{cost}",
"upgrades": "升级:{cost}",
"purchase": "购买:{cost}",
"equipment": "装备:{cost}",
"estimation": "# 这是一个估计值!\n\n这个列表可能包含已经研发过的战车,因为 Wargaming 提供了不正确的资料。 如果可以,请尝试在同一研发线路中提供一个 “起始战车” 。"
},
"errors": {
"non_tech_tree": "# \"{tank}\" 不在科技树里\n\n你不能研发一个 加值/收藏家 坦克。",
"tier_1": "# {tank} 是一级坦克\n\n一级坦克一直是对所有玩家免费解锁的。",
"already_researched": "# \"{tank}\" 已完成研发\n\n请尝试在同一研发线路上提供“起始坦克”, 以查看全部您已研发过的坦克。",
"start_end_equal": "# \"{tank}\" 重复提供了\n\n请确认 \"初始坦克\" 和 \"目标坦克\" 是不同的。",
"tanks_not_on_line": "# “{tank_1}” 和 “{tank_2}” 不在同一个研发线路上\n\n请确认“起始坦克” 和 “目标坦克” 在同一研发线路中。",
"unordered": "# “{tank_1}” 阶级比 “{tank_2}” 高\n\n请以提供 {tank_2} 作为起始坦克,因为它的阶级更低。"
}
},
"about": {
"name": "关于",
"description": "查看关于机器人的所有信息",
"subcommands": {
"commands": {
"name": "指令",
"description": "所有指令及其描述",
"body": "# 指令\n\nBlitzKit 提供了以下指令:\n\n"
},
"embeds": {
"name": "嵌入",
"description": "嵌入如何运作"
},
"introduction": {
"name": "介绍",
"description": "关于此机器人"
},
"invite": {
"name": "邀请",
"description": "如何邀请机器人"
},
"timezones": {
"name": "时区",
"description": "BlitzKit 如何判断并使用你的时区"
},
"credits": {
"name": "积分",
"description": "谁帮助过 BlitzKit"
}
}
},
"breakdown": {
"name": "详细分析",
"description": "在特定时期中, 战车的详细分析",
"body": {
"total": "全部",
"battles": "战斗场次",
"wn8": "WN8",
"winrate": "胜率",
"damage": "伤害",
"unknown_tank": "未知坦克 {id}"
}
},
"debug": {
"name": "侦错",
"description": "机器人侦错信息",
"body": {
"title": "# 侦错\n\n",
"version": "版本",
"shard": {
"label": "碎片",
"default": "默认"
},
"uptime": "运行时间"
}
},
"evolution": {
"name": "演变",
"description": "统计资料的演变",
"groups": {
"player": {
"name": "玩家",
"description": "玩家数据统计"
},
"tank": {
"name": "坦克",
"description": "坦克数据统计"
}
}
},
"inactive": {
"name": "不活跃",
"description": "列出公会中所有超过阈值的不活跃玩家",
"options": {
"threshold": {
"name": "阈值",
"description": "未上线天数( 默认:{days} 天)"
}
},
"body": {
"subtitle": "{days}+ 天未上线",
"listing": "{days} 天"
}
},
"full_stats": {
"name": "完整统计资料",
"description": "游戏内完整统计资料",
"body": {
"cards": {
"games": {
"title": "游戏",
"winrate": "胜率",
"battles": "战斗场次",
"wins_losses": "胜利/失败"
},
"efficiency": {
"title": "效率",
"wn8": "WN8",
"survival": "生存率",
"accuracy": "命中率"
},
"lethality": {
"title": "致命伤害",
"average_damage": "平均伤害",
"damage_per_tier": "各阶级伤害",
"damage_ratio": "伤害比",
"average_kills": "平均击毁"
},
"miscellaneous": {
"title": "杂项",
"average_tier": "平均阶级",
"average_spots": "平均侦查",
"average_xp": "平均经验",
"kills_to_death_ratio": "击杀阵亡比"
}
}
}
},
"link": {
"name": "链接",
"description": "链接您的 Blitz 和 Discord 账户",
"body": {
"accounts_linked": "帐号已绑定",
"no_role_permissions": {
"title": "{name} 验证失败",
"description": "我没有管理身份的权限。"
}
},
"embed": {
"title": "账户不正确?",
"description": "再次使用此命令,从搜索结果中选择一个用户名。"
}
},
"search_clans": {
"name": "搜寻-公会资料",
"description": "搜寻公会资料",
"options": {
"limit": {
"name": "限制",
"description": "公会结果的最大显示数量 (默认:{limit})"
}
},
"body": {
"title": "在地区 {region} 中搜索公会 {search}",
"no_results": "没有找到该公会"
}
},
"search_players": {
"name": "搜索-玩家",
"description": "搜索玩家",
"options": {
"limit": {
"name": "限制",
"description": "玩家结果的最大显示数量 (默认:{limit})"
}
},
"body": {
"title": "在地区 {region} 中搜索玩家 {search}",
"no_results": "未找到玩家"
}
},
"rating_leaderboard": {
"name": "评价战斗-排行榜",
"description": "评价战斗的统计数据",
"subcommands": {
"neighbors": {
"name": "附近的",
"description": "排行榜上附近的玩家"
},
"league": {
"name": "段位",
"description": "段位中的顶尖玩家"
}
},
"options": {
"limit": {
"name": "限制",
"description": "显示附近有多少玩家(默认:{count})"
},
"season": {
"name": "赛季",
"description": "赛季数 (默认:当前)",
"choices": {
"current": "{season} (当前)"
}
},
"league": {
"name": "段位",
"description": "要显示的段位"
}
},
"body": {
"top_players": "前 {count} 玩家",
"no_participation": "# {player} 没有在赛季 {season} 中进行过评价战斗\n\n该玩家没有参加该赛季或没有完成校准。",
"no_ongoing_season": "# 没有进行中的赛季\n\n目前没有赛季。",
"deleted_player": "已删除玩家 {id}",
"neighbors": "对邻居进行评分",
"more": "+ {count} 更多"
}
},
"today": {
"name": "今日",
"description": "今天玩过的坦克和统计数据"
},
"stats": {
"name": "统计资料",
"description": "一段时间内的快速统计"
},
"owned_tanks": {
"name": "已拥有的坦克",
"description": "在一个等级中显示所有已拥有的坦克",
"body": {
"title": "# {name} 所拥有的 {tier} 阶坦克\n\n"
}
},
"permissions": {
"name": "权限",
"description": "检查机器人是否拥有运行所需的权限",
"body": {
"title": "# 权限\n\n{body}",
"view_channels": "查看频道:需要找到重新整理按钮使用的讯息",
"read_message_history": "读取消息历史: 需要找到消息以刷新",
"attach_files": "附加文件:需要将图像添加到基于图像的命令",
"external_emojis": "外部表情:需要添加国家旗帜和坦克等级表情"
}
},
"ping": {
"name": "连接延迟",
"description": "检查机器人是否处于活动状态。",
"body": {
"pong": "Pong 🏓",
"pong_with_time": "Pong 🏓 - {time} 微秒"
},
"subcommands": {
"blitzkit": {
"name": "blitzkit",
"description": "测试BlitzKit的ping值(延迟值)"
},
"wotb": {
"name": "战车世界闪击战",
"description": "测试坦克世界闪击战的ping值(延迟值)"
}
}
},
"player_info": {
"name": "玩家信息",
"description": "玩家基本信息",
"body": {
"title": "{name} 的信息",
"nickname": "昵称",
"battles": "战斗场次",
"winrate": "胜率",
"account_id": "账号ID",
"created": "创建时间",
"last_battle": "最后一场战斗"
}
},
"rating": {
"name": "评价战斗",
"description": "评价战斗的统计数据",
"subcommands": {
"today": {
"name": "今日",
"description": "今日评价战斗统计数据"
},
"season": {
"name": "赛季",
"description": "本季评价战斗统计数据"
}
},
"body": {
"subtitle": {
"today": "今日评价战斗统计数据",
"season": "本季评价战斗统计数据"
}
},
"errors": {
"no_participation": "# {name} 未曾游玩评价战斗\n\n该玩家未参与本赛季的评价战斗。",
"unrecorded_stats": "# 统计数据目前无法使用\n\nWargaming 可能暂停了服务器。 如果有连续的赛季,统计数据可能在24小时内提供。"
}
}
}
},
"website": {
"redirect": {
"title": "301: 已永久移动",
@@ -741,14 +281,6 @@
}
},
"home": {
"pwa": {
"title": "安装 BlitzKit 应用程序",
"new": "新增",
"checking": "正在检查兼容性……",
"instructions": "Apple 设备的说明。",
"install": "安装",
"dismiss": "取消"
},
"discord": {
"join_short": "加入超级酷的人群!",
"join_long": "加入我们的Discord社群,和超酷的人一起聊天!",
@@ -809,7 +341,7 @@
"mixer": {
"name": "组装",
"button": "混合搭配",
"description": "混合坦克模块",
"description": "混搭不同坦克间的模块",
"seo_description": "《坦克世界:闪击战》的车体, 炮塔, 主炮混合搭配",
"screenshot": "屏幕截图",
"randomize": "随机搭配"
@@ -984,7 +516,7 @@
"equalizer": {
"title": "Equalizer",
"equalize": "Equalize",
"beta": "Beta"
"beta": "测试版"
},
"modules": {
"title": "坦克模块",
@@ -1018,8 +550,8 @@
"title": "属性",
"compare": {
"label": "比较",
"class": "{tier} {class}",
"tier": "All tier {tier}",
"class": "{tier} 阶的 {class}",
"tier": "所有{tier} 阶的",
"all": "所有坦克"
},
"values": {
@@ -1102,8 +634,8 @@
"shellCapacity": "携弹量",
"gunType": "炮管类型",
"penetrationAt250m": "两百五十米处的穿透力",
"burstShells": "Burst shell count",
"burstInterShell": "Burst inter-shell"
"burstShells": "爆破计数",
"burstInterShell": "爆破弹"
}
},
"firepower": {
@@ -1156,7 +688,7 @@
"skills": {
"clear": "清除",
"maximize": "最大化",
"equalize": "Equalize"
"equalize": "平衡"
},
"modules": {
"stock": "白板",
+9 -16
View File
@@ -1,5 +1,12 @@
{
"name": "@blitzkit/scripts",
"private": true,
"scripts": {
"discover-ids": "bun src/discoverIds/index.ts",
"build-protos": "bun src/protos.ts",
"record-performance": "bun src/recordPerformance/index.ts",
"download-client": "bun src/downloadClient/index.ts"
},
"dependencies": {
"@blitzkit/closed": "workspace:*",
"@blitzkit/core": "workspace:*",
@@ -9,7 +16,7 @@
"@gltf-transform/functions": "^4.2.1",
"@octokit/plugin-throttling": "^11.0.2",
"@octokit/rest": "^22.0.0",
"@types/bun": "^1.3.0",
"@types/bun": "^1.3.14",
"@types/decompress": "^4.2.7",
"@types/lodash-es": "^4.17.12",
"@types/lz4js": "^0.2.1",
@@ -24,6 +31,7 @@
"fast-xml-parser": "^5.2.5",
"googleapis": "^162.0.0",
"lodash-es": "^4.17.21",
"lz4-napi": "^2.9.1",
"progress": "^2.0.3",
"puppeteer": "^24.22.3",
"sharp": "^0.34.4",
@@ -33,20 +41,5 @@
"unzipit": "^1.4.3",
"yaml": "^2.8.1"
},
"private": true,
"scripts": {
"build:assets": "bun src/buildAssets/index.ts",
"build:averages": "bun src/averages.ts",
"setup:depotdownloader": "bun src/setupDepotDownloader.ts",
"setup:patches": "bun src/applyPatches.ts",
"build:reviews": "bun src/reviews.ts",
"build:ids": "bun src/discoverIds.ts",
"build:ratingleaderboard": "bun src/buildRatingLeaderboard.ts",
"build:proxyclient": "bun src/buildProxyClient.ts",
"build:catalogitemaccesssor": "bun src/buildCatalogItemAccessor.ts",
"prunestrings": "bun src/pruneStrings.ts",
"build:protos": "bun src/protos.ts",
"patchnotes": "bun src/patchNotes.ts"
},
"type": "module"
}
-229
View File
@@ -1,229 +0,0 @@
import {
AverageDefinitions,
AverageDefinitionsAllStats,
AverageDefinitionsEntry,
type IndividualTankStats,
REGIONS,
type Region,
Samples,
averageDefinitionsAllStatsKeys,
emptySamples,
getAccountInfo,
getTankStats,
idToRegion,
} from "@blitzkit/core";
import { chunk, times } from "lodash-es";
import { AssetUploader } from "./core/github/assetUploader";
import { fetchPreDiscoveredIds } from "./core/github/fetchPreDiscoveredIds";
interface DataPoint {
x: number;
y: number;
w: number;
}
const MINUTE = 60 * 1000;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
// const MAX_ACTIVITY_TIME = 120 * DAY;
// const MIN_BATTLES = 5000;
const RUN_TIME = 5 * HOUR + 55 * MINUTE;
const THREADS = 10;
const PLAYER_IDS_PER_CALL = 100;
const startTime = Date.now();
const preDiscoveredIds = await fetchPreDiscoveredIds();
const playerIds: Record<Region, number[][]> = {
asia: chunk(
preDiscoveredIds.filter((id) => idToRegion(id) === "asia"),
PLAYER_IDS_PER_CALL,
),
com: chunk(
preDiscoveredIds.filter((id) => idToRegion(id) === "com"),
PLAYER_IDS_PER_CALL,
),
eu: chunk(
preDiscoveredIds.filter((id) => idToRegion(id) === "eu"),
PLAYER_IDS_PER_CALL,
),
};
let regionIndex = 0;
const samples: Samples = { ...emptySamples };
const availableRegions = [...REGIONS];
const tankIds: number[] = [];
const tanksSorted: Record<number, IndividualTankStats[]> = {};
let postWorkRequested = false;
times(THREADS, async () => {
while (availableRegions.length > 0 && startTime + RUN_TIME > Date.now()) {
const region = availableRegions[regionIndex];
const regionIds = playerIds[region];
const [ids] = regionIds.splice(
Math.floor(Math.random() * regionIds.length),
1,
);
const accountInfo = await getAccountInfo(region, ids, undefined, {
fields: "last_battle_time,statistics.all.battles",
});
const filteredIds = ids.filter((_, index) => {
const info = accountInfo[index];
if (info === null) return false;
const timeSinceLastActivity = Date.now() - info.last_battle_time * 1000;
samples.total++;
if (timeSinceLastActivity <= 120 * DAY) samples.d_120++;
if (timeSinceLastActivity <= 90 * DAY) samples.d_90++;
if (timeSinceLastActivity <= 60 * DAY) samples.d_60++;
if (timeSinceLastActivity <= 30 * DAY) samples.d_30++;
if (timeSinceLastActivity <= 7 * DAY) samples.d_7++;
if (timeSinceLastActivity <= 1 * DAY) samples.d_1++;
return true;
// return (
// timeSinceLastActivity <= MAX_ACTIVITY_TIME &&
// info.statistics.all.battles > MIN_BATTLES
// );
});
const players = await Promise.all(
filteredIds.map((id) =>
getTankStats(region, id, {
fields: "last_battle_time,battle_life_time,all,tank_id",
}),
),
);
players.forEach((tanks) => {
tanks?.forEach((tank) => {
if (tank.all.battles === 0) return;
if (!tankIds.includes(tank.tank_id)) {
console.log(
`Found new tank: ${tank.tank_id} (${tankIds.length + 1})`,
);
tankIds.push(tank.tank_id);
tanksSorted[tank.tank_id] = [];
}
tanksSorted[tank.tank_id].push(tank);
});
});
if (regionIds.length === 0 && availableRegions.includes(region)) {
availableRegions.splice(availableRegions.indexOf(region), 1);
}
regionIndex = (regionIndex + 1) % availableRegions.length;
}
if (!postWorkRequested) {
postWorkRequested = true;
postWork();
}
});
async function postWork() {
console.log(
`Generating statistics based on ${samples.d_120.toLocaleString()} players (${samples.total.toLocaleString()} checked in total) and ${
tankIds.length
} tanks...`,
);
using uploader = new AssetUploader("averages");
const averages: Record<number, AverageDefinitionsEntry> = {};
tankIds.forEach((id) => {
const tanksSortedEntry = tanksSorted[id];
// const tanks = tanksSortedEntry.filter(
// (tank) => Date.now() - tank.last_battle_time * 1000 <= MAX_ACTIVITY_TIME,
// );
const tanks = tanksSortedEntry;
const entry: {
mu: Partial<Record<keyof AverageDefinitionsAllStats, number>>;
sigma: Partial<Record<keyof AverageDefinitionsAllStats, number>>;
r: Partial<Record<keyof AverageDefinitionsAllStats, number>>;
samples: Samples;
} = {
mu: {},
sigma: {},
r: {},
samples: { ...emptySamples },
};
tanks.forEach((tank) => {
const timeSinceLastActivity = Date.now() - tank.last_battle_time * 1000;
entry.samples!.total++;
if (timeSinceLastActivity <= 120 * DAY) entry.samples!.d_120++;
if (timeSinceLastActivity <= 90 * DAY) entry.samples!.d_90++;
if (timeSinceLastActivity <= 60 * DAY) entry.samples!.d_60++;
if (timeSinceLastActivity <= 30 * DAY) entry.samples!.d_30++;
if (timeSinceLastActivity <= 7 * DAY) entry.samples!.d_7++;
if (timeSinceLastActivity <= 1 * DAY) entry.samples!.d_1++;
});
const dataWY = tanks.map((tank) => ({
w: tank.all.battles,
y: tank.all.wins / tank.all.battles,
}));
averageDefinitionsAllStatsKeys.forEach((key) => {
const data: DataPoint[] = dataWY.map(({ w, y }, index) => ({
w,
x:
(key === "battle_life_time"
? tanks[index].battle_life_time
: tanks[index].all[key]) / w,
y,
}));
if (data.length === 0) return;
function sum(slicer: (data: DataPoint) => number) {
return data.reduce(
(accumulator, data) => accumulator + slicer(data),
0,
);
}
const sum_w = sum(({ w }) => w);
const sum_wx = sum(({ w, x }) => w * x);
const sum_wy = sum(({ w, y }) => w * y);
const x_bar = sum_wx / sum_w;
const y_bar = sum_wy / sum_w;
// undo normalization for mu to get a simple average
const mu = sum(({ w, x }) => w * x) / samples.d_120;
const sigma_numerator = sum(({ w, x }) => w * (x - x_bar) ** 2);
const sigma = Math.sqrt(sigma_numerator / sum_w);
const r_numerator = sum(({ w, x, y }) => w * (x - x_bar) * (y - y_bar));
const r_denominator_x = sum(({ w, x }) => w * (x - x_bar) ** 2);
const r_denominator_y = sum(({ w, y }) => w * (y - y_bar) ** 2);
const r = r_numerator / Math.sqrt(r_denominator_x * r_denominator_y);
entry.mu[key] = mu;
entry.sigma[key] = sigma;
entry.r[key] = isNaN(r) ? 0 : r;
});
averages[id] = entry as AverageDefinitionsEntry;
});
const time = Date.now();
const averageDefinitions = {
averages,
samples,
time,
} satisfies AverageDefinitions;
await uploader.add({
path: "averages.pb",
content: AverageDefinitions.encode(averageDefinitions).finish(),
});
await uploader.flush();
}
@@ -1,62 +0,0 @@
import { readdir, readFile, writeFile } from "fs/promises";
const SOURCES = "../../packages/closed/protos";
const TARGET = "../../packages/closed/src/unreal/catalogItemAccessor.ts";
const componentPattern = /^message (\w+)Component {/gm;
const files = await readdir(SOURCES).then((files) =>
files.filter(
(file) =>
file.startsWith("blitz_static_") && file.endsWith("_component.proto")
)
);
const imports: { name: string; file: string }[] = [];
for (const file of files) {
const content = await readFile(`${SOURCES}/${file}`).then((buffer) =>
buffer.toString()
);
const components = content.matchAll(componentPattern);
for (const match of components) {
const name = match[1];
const duplicate = imports.find((_import) => _import.name === name);
if (duplicate) {
console.warn(
`Duplicate component ${name} across ${duplicate.file}.proto and ${file}`
);
continue;
}
const importFile = file.replace(".proto", "");
imports.push({ name, file: importFile });
}
}
let content = "";
for (const _import of imports) {
content += `import { ${_import.name}Component } from "@protos/${_import.file}";\n`;
}
content +=
'import { BaseCatalogItemAccessor } from "./baseCatalogItemAccessor";\n\n';
content +=
"export class CatalogItemAccessor extends BaseCatalogItemAccessor {\n";
for (const _import of imports) {
const strippedName = _import.name.replace("BlitzStatic", "");
const defaultName = strippedName[0].toLowerCase() + strippedName.slice(1);
content += ` ${strippedName}(name = "${defaultName}Component") {\n`;
content += ` return this.component(name, ${_import.name}Component);\n`;
content += " }\n";
}
content += "}\n";
await writeFile(TARGET, content);
-40
View File
@@ -1,40 +0,0 @@
import { readFile, writeFile } from "fs/promises";
const IMPORT = "@protos/auto_legacy_proxy_client";
const SOURCE = "../../packages/closed/protos/auto_legacy_proxy_client.proto";
const TARGET = "../../packages/closed/src/unreal/methodPopulatedProxyClient.ts";
const requestPattern = /^message (\w+)Request {/gm;
const source = await readFile(SOURCE).then((buffer) => buffer.toString());
const requests = source.matchAll(requestPattern);
const imports: string[] = [];
const names: string[] = [];
for (const match of requests) {
const name = match[1];
imports.push(`${name}Request`, `${name}Response`);
names.push(name);
}
let content = "";
content += "import {\n";
content += imports.map((i) => ` ${i},`).join("\n");
content += `\n} from "${IMPORT}";\n`;
content += 'import { BaseProxyClient } from "./baseProxyClient";\n\n';
content +=
"export abstract class MethodPopulatedProxyClient extends BaseProxyClient {\n";
content += names
.map((name) => {
return ` ${name}(data: ${name}Request) {
return this.request("${name}Request", ${name}Request, data, ${name}Response);
}\n`;
})
.join("");
content += "}\n";
await writeFile(TARGET, content);
@@ -1,169 +0,0 @@
import {
getAccountInfo,
patientFetchJSON,
RatingInfo,
RatingLeaderboard,
RatingLeaderboardEntryV1,
RatingLeaderboardEntryV2,
RatingNeighbors,
RatingPlayer,
Region,
REGIONS,
regionToRegionSubdomain,
} from "@blitzkit/core";
import { chunk } from "lodash-es";
import { argv } from "process";
import { AssetUploader } from "./core/github/assetUploader";
const region = argv
.find((arg) => arg.startsWith("--region="))
?.split("=")[1] as Region | undefined;
if (region === undefined || !REGIONS.includes(region)) {
throw new Error("No valid region specified");
}
console.log(`Building rating leaderboard for ${region}`);
const regionSubdomain = regionToRegionSubdomain(region);
const NEIGHBORS = 2 ** 8;
const PLAYERS = await patientFetchJSON<RatingInfo>(
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/season/`
).then((data) => (data.detail === undefined ? data.count : undefined));
if (PLAYERS === undefined) {
throw new Error("No current rating season running");
}
const players: Record<number, RatingPlayer> = {};
const entriesV1: RatingLeaderboardEntryV1[] = [];
let registered = 0;
async function branchFromPlayer(id: number) {
const { neighbors } = await patientFetchJSON<RatingNeighbors>(
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${NEIGHBORS}`
);
const firstPlayer = neighbors[0];
const lastPlayer = neighbors[neighbors.length - 1];
const branches: Promise<void>[] = [];
if (!players[firstPlayer.number])
branches.push(branchFromPlayer(firstPlayer.spa_id));
if (!players[lastPlayer.number])
branches.push(branchFromPlayer(lastPlayer.spa_id));
neighbors.forEach((neighbor) => {
if (players[neighbor.number]) return;
players[neighbor.number] = neighbor;
registered++;
});
console.log(
`Registered ${registered} / ${PLAYERS} (${(
(registered / PLAYERS!) *
100
).toFixed(2)}%)`
);
await Promise.all(branches);
}
async function branchFromLeague(id: number) {
const { result } = await patientFetchJSON<{ result: RatingPlayer[] }>(
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/`
);
const firstPlayer = result[0];
const lastPlayer = result.at(-1)!;
await Promise.all([
branchFromPlayer(firstPlayer.spa_id),
branchFromPlayer(lastPlayer.spa_id),
]);
}
console.log("Branching from leagues...");
await Promise.all([
branchFromLeague(0),
branchFromLeague(1),
branchFromLeague(2),
branchFromLeague(3),
branchFromLeague(4),
]);
console.log("Converting to an array...");
for (let index = 0; index < PLAYERS; index++) {
const listing = players[index + 1];
if (listing) {
entriesV1[index] = { id: listing.spa_id, score: listing.score };
} else {
console.warn(`Position ${index + 1} had no player`);
entriesV1[index] = { id: 0, score: 0 };
}
}
console.log("Requesting further stats...");
const entries: RatingLeaderboardEntryV2[] = [];
let chunkIndex = 0;
for (const leaderboardChunk of chunk(entriesV1, 100)) {
console.log(
`Chunk ${chunkIndex + 1} of ${Math.ceil(entriesV1.length / 100)}`
);
const stats = await getAccountInfo(
region,
leaderboardChunk.map(({ id }) => id),
["statistics.rating"]
);
stats.forEach((stat, index) => {
const trueIndex = chunkIndex * 100 + index;
if (
leaderboardChunk[index].id === 0 ||
stat?.statistics.rating === undefined
) {
entries[trueIndex] = {
battles: 0,
damage: 0,
id: 0,
kills: 0,
score: 0,
survived: 0,
wins: 0,
};
} else {
entries[trueIndex] = {
id: leaderboardChunk[index].id,
score: leaderboardChunk[index].score,
battles: stat.statistics.rating.battles,
damage: stat.statistics.rating.damage_dealt,
kills: stat.statistics.rating.frags,
survived: stat.statistics.rating.survived_battles,
wins: stat.statistics.rating.wins,
};
}
});
chunkIndex++;
}
const info = await patientFetchJSON<RatingInfo & { detail: undefined }>(
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/season/`
);
const normalizedServer = regionSubdomain === "na" ? "com" : regionSubdomain;
const content = RatingLeaderboard.encode({
version: { $case: "v2", value: { entries } },
}).finish();
using uploader = new AssetUploader(`rating leaderboard ${regionSubdomain}`);
await uploader.add({
path: `regions/${normalizedServer}/rating/${info.current_season}.pb`,
content,
});
await uploader.flush();
@@ -0,0 +1,58 @@
import { $ } from "bun";
import { mkdir, rm } from "node:fs/promises";
export class GitObjectStorage {
constructor(
private repo: string,
private base: string,
private token?: string,
) {}
async init() {
await rm(this.base, { recursive: true, force: true });
console.log(`Cloning ${this.repo} to ${this.base}`);
const url = this.token
? `https://x-access-token:${this.token}@github.com/${this.repo}.git`
: `https://github.com/${this.repo}.git`;
await $`git clone --depth 1 ${url} ${this.base}`;
return this;
}
async commit() {
const shell = $.cwd(this.base);
await shell`git add .`.quiet();
await shell`git commit --amend --reset-author -m "ids update ${new Date().toISOString()}"`.quiet();
await shell`git push --force-with-lease`;
}
async mkdir(path: string) {
await mkdir(`${this.base}/${path}`, { recursive: true });
}
async rm(path: string) {
await rm(`${this.base}/${path}`, { force: true });
}
async write(path: string, bytes: Uint8Array | string) {
await Bun.write(`${this.base}/${path}`, bytes);
}
file(path: string) {
return Bun.file(`${this.base}/${path}`);
}
async bytes(path: string) {
const file = this.file(path);
return await file.bytes();
}
async json<Type>(path: string) {
const file = this.file(path);
return (await file.json()) as Type;
}
}
@@ -0,0 +1,79 @@
import { decompress } from "lz4js";
export class IdArray {
constructor(
private array: Uint32Array = new Uint32Array(),
private _size = array.length,
) {}
get(index: number) {
return this.array[index];
}
push(id: number) {
if (this._size === this.array.length) {
const bigger = new Uint32Array(
this.array.length === 0 ? 1 : this.array.length * 2,
);
bigger.set(this.array);
this.array = bigger;
}
this.array[this._size++] = id;
}
sort() {
this.array.subarray(0, this._size).sort();
}
size() {
return this._size;
}
toBytes() {
return new Uint8Array(
this.array.buffer,
this.array.byteOffset,
this._size * Uint32Array.BYTES_PER_ELEMENT,
);
}
toArray() {
return Array.from(this.array.subarray(0, this._size));
}
static fromBytes(bytes: Uint8Array) {
if (bytes.byteLength % Uint32Array.BYTES_PER_ELEMENT !== 0) {
throw new Error("Misaligned bytes");
}
const size = bytes.byteLength / Uint32Array.BYTES_PER_ELEMENT;
const capacity = this.nextPowerOf2(size);
const values = new Uint32Array(bytes.buffer, bytes.byteOffset, size);
const uintArray = new Uint32Array(capacity);
uintArray.set(values);
return new IdArray(uintArray, size);
}
static fromCompressed(bytes: Uint8Array) {
const decompressed = decompress(bytes);
return this.fromBytes(decompressed);
}
static fromArray(array: number[]) {
const uintArray = new Uint32Array(this.nextPowerOf2(array.length));
uintArray.set(array);
return new IdArray(uintArray, array.length);
}
private static nextPowerOf2(value: number): number {
if (value <= 1) return 1;
return 2 ** Math.ceil(Math.log2(value));
}
}
-165
View File
@@ -1,165 +0,0 @@
import {
DidsReadStream,
DidsWriteStream,
DiscoveredIdsDefinitions,
MIN_REGION_IDS,
REGIONS,
Region,
asset,
getAccountInfo,
idToRegion,
retryAbleBlitzFetchEvent,
} from "@blitzkit/core";
import { chunk, times, uniq } from "lodash-es";
import { compress, decompress } from "lz4js";
import { AssetUploader } from "./core/github/assetUploader";
const CHUNK_SIZE = 2 ** 21;
const RUN_TIME = 1000 * 60 * 60 * 5.5;
const PROGRESS_UPDATE_FREQUENCY = 1000 * 60;
const MAX_REQUESTS = 10;
const ACCOUNTS_PER_CALL = 100;
const TERMINATION_THRESHOLD = 1000;
const startTime = Date.now();
const indexableRegions = [...REGIONS];
let ids: number[] = [];
const preDiscoveredManifest = (await fetch(asset("ids/manifest.json")).then(
(response) => response.json()
)) as DiscoveredIdsDefinitions;
console.log(
`Fetching ${preDiscoveredManifest.chunks} pre-discovered chunks...`
);
let chunkIndex = 0;
while (chunkIndex < preDiscoveredManifest.chunks) {
const preDiscovered = await fetch(asset(`ids/${chunkIndex}.dids.lz4`)).then(
async (response) => {
const buffer = await response.arrayBuffer();
const decompressed = decompress(new Uint8Array(buffer)).buffer;
return new DidsReadStream(decompressed as ArrayBuffer).dids();
}
);
// no spread syntax: https://github.com/oven-sh/bun/issues/11734
ids = ids.concat(preDiscovered);
console.log(
`Pre-discovered ${preDiscovered.length} ids (chunk ${chunkIndex})`
);
chunkIndex++;
}
const regionalIdIndex: Record<Region, number> = {
asia: ids.findLast((id) => idToRegion(id) === "asia") ?? MIN_REGION_IDS.asia,
com: ids.findLast((id) => idToRegion(id) === "com") ?? MIN_REGION_IDS.com,
eu: ids.findLast((id) => idToRegion(id) === "eu") ?? MIN_REGION_IDS.eu,
};
const zeroStreak: Record<Region, number> = { asia: 0, com: 0, eu: 0 };
let regionIndex = 0;
let outOfTimeFlagged = false;
async function verify(region: Region, ids: number[]) {
const infos = await getAccountInfo(region, ids, undefined, {
// cheeky way of requesting nothing
fields: "account_id,-account_id",
});
const filtered = ids.filter((_, index) => {
const info = infos[index];
return info !== null;
});
return filtered;
}
let discardAttempts = 0;
let lastProgressUpdate = 0;
retryAbleBlitzFetchEvent.on(() => discardAttempts++);
const interval = setInterval(async () => {
if (discardAttempts > 0) return discardAttempts--;
const region = indexableRegions[regionIndex];
const idsToVerify = times(
ACCOUNTS_PER_CALL,
(index) => regionalIdIndex[region] + index
);
const verified = await verify(region, idsToVerify);
if (Date.now() - lastProgressUpdate > PROGRESS_UPDATE_FREQUENCY) {
lastProgressUpdate = Date.now();
console.log(`Discovered ${ids.length} ids in ${Date.now() - startTime}ms`);
}
if (verified.length === 0) {
zeroStreak[region]++;
if (zeroStreak[region] >= TERMINATION_THRESHOLD) {
const sliceableIndex = indexableRegions.indexOf(region);
if (sliceableIndex !== -1) {
console.log(`Stopped discovery for ${region}`);
indexableRegions.splice(sliceableIndex, 1);
if (indexableRegions.length === 0) {
console.log("All players discovered exhaustively!");
await post();
}
}
}
} else {
zeroStreak[region] = 0;
ids.push(...verified);
}
if (Date.now() - startTime > RUN_TIME && !outOfTimeFlagged) {
outOfTimeFlagged = true;
console.log("Out of time");
await post();
}
regionIndex = (regionIndex + 1) % indexableRegions.length;
regionalIdIndex[region] += ACCOUNTS_PER_CALL;
}, 1000 / MAX_REQUESTS);
async function post() {
clearInterval(interval);
ids = uniq(ids).sort((a, b) => a - b);
console.log(`Uploading ${ids.length} ids...`);
using uploader = new AssetUploader("discovered ids");
const idsChunked = chunk(ids, CHUNK_SIZE);
let index = 0;
for (const chunk of idsChunked) {
const didsWriteStream = new DidsWriteStream().dids(chunk);
const buffer = didsWriteStream.uint8Array;
const content = compress(buffer);
console.log(
`Chunk ${index} with ${chunk.length} ids (compressed: ${content.length}; uncompressed: ${buffer.length})`
);
await uploader.add({
content,
path: `ids/${index}.dids.lz4`,
});
index++;
}
await uploader.add({
content: new TextEncoder().encode(
JSON.stringify({
chunks: idsChunked.length,
count: ids.length,
time: Date.now(),
} satisfies DiscoveredIdsDefinitions)
),
path: "ids/manifest.json",
});
await uploader.flush();
}
@@ -0,0 +1,9 @@
export const API_RATE = 10; // 10Hz
export const MAX_IDS_PER_CALL = 100;
export const MAX_DRY_STREAK = 10_000;
export const ACCOMMODATED_ID_COUNT = 500_000_000; // 500M ids
const PERFORMANCE_RUN_TIME = (5 * 60 + 30) * 60;
export const IDS_PER_CHUNK = PERFORMANCE_RUN_TIME * API_RATE;
export const CHUNK_COUNT = Math.ceil(ACCOMMODATED_ID_COUNT / IDS_PER_CHUNK);
+212
View File
@@ -0,0 +1,212 @@
import { range } from "lodash-es";
import { compress } from "lz4js";
import { exit } from "node:process";
import { GitObjectStorage } from "../core/blitzkit/gitObjectStorage";
import { IdArray } from "../core/blitzkit/idArray";
import { CHUNK_COUNT, MAX_DRY_STREAK, MAX_IDS_PER_CALL } from "./constants";
import { atMaxRate } from "./time";
import { IdsManifest, QuickInfo, RegionDescriptor } from "./types";
const storage = await new GitObjectStorage(
"blitzkit/data-ids",
"../../temp/ids",
import.meta.env.GH_TOKEN,
).init();
await storage.mkdir("chunks");
console.log(`Discovering pre-existing chunks...`);
const discoveredChunks: (IdArray | null)[] = [];
const manifest = await storage.json<IdsManifest>("manifest.json");
for (let i = 0; i < manifest.chunks; i++) {
const bytes = await storage.bytes(`chunks/chunk-${i}.dat.lz4`);
const ids = IdArray.fromCompressed(bytes);
discoveredChunks.push(ids);
}
console.log(`Found ${manifest.chunks} pre-existing chunks`);
let chunks: IdArray[] = [];
if (manifest.chunks === CHUNK_COUNT) {
console.log("Pre-existing chunking is consistent, passing as-is...");
chunks = discoveredChunks as IdArray[];
} else {
console.log("Pre-existing chunking is not consistent, re-chunking...");
chunks = Array.from({ length: CHUNK_COUNT }).map(() => new IdArray());
for (let i = 0; i < discoveredChunks.length; i++) {
const ids = discoveredChunks[i];
const size = ids!.size();
for (let j = 0; j < size; j++) {
const id = ids!.get(j);
const chunk = chunks[id % CHUNK_COUNT];
chunk.push(id);
}
discoveredChunks[i] = null;
}
console.log("Sorting chunks...");
for (let i = 0; i < chunks.length; i++) chunks[i].sort();
manifest.chunks = CHUNK_COUNT;
manifest.last_verified = manifest.last_verified % CHUNK_COUNT;
}
const regions: RegionDescriptor[] = [
{ domain: "eu", seed: 5e8, max: 10e8 - 1, dry_streak: 0 },
{ domain: "com", seed: 10e8, max: 20e8 - 1, dry_streak: 0 },
{ domain: "asia", seed: 20e8, max: 31e8 - 1, dry_streak: 0 },
];
console.log("Finding new seed ids...");
for (let i = 0; i < manifest.chunks; i++) {
const chunk = chunks[i];
const size = chunk.size();
for (let j = 0; j < size; j++) {
const id = chunk.get(j);
for (const region of regions) {
if (id <= region.max && id >= region.seed) {
region.seed = id + 1;
}
}
}
}
console.log("Found seeds:");
for (const region of regions) {
console.log(` ${region.domain}: ${region.seed.toLocaleString()}`);
}
let isSaving = false;
async function save() {
if (isSaving) return;
isSaving = true;
console.log("Saving chunks...");
await storage.rm("chunks/*");
let totalFound = 0;
for (let i = 0; i < chunks.length; i++) {
const chunk = chunks[i];
const bytes = chunk.toBytes();
const compressed = compress(bytes);
totalFound += chunk.size();
await storage.write(`chunks/chunk-${i}.dat.lz4`, compressed);
}
manifest.total_count = totalFound;
await storage.write("manifest.json", JSON.stringify(manifest, null, 2));
await storage.commit();
console.log(
`Committed ${totalFound.toLocaleString()} total ids across ${manifest.chunks} chunks`,
);
exit(0);
}
process.on("SIGINT", save);
process.on("SIGTERM", save);
const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID;
const fields = "account_id%2C-account_id";
let regionI = 0;
console.log("Starting discovery loop...");
atMaxRate(async ({ _break: break1 }) => {
if (regions.length === 0) {
console.log("All regions exhausted, exiting discovery loop...");
return break1();
}
regionI = (regionI + 1) % regions.length;
const region = regions[regionI];
if (region.dry_streak >= MAX_DRY_STREAK) {
console.log(
`Removing region ${region.domain} removed due to ${region.dry_streak} dry streak...`,
);
regions.splice(regionI, 1);
return;
}
const id0 = region.seed;
const id1 = Math.min(id0 + MAX_IDS_PER_CALL - 1, region.max);
const idRange = range(id0, id1 + 1);
region.seed += idRange.length;
const accountIds = idRange.join(",");
const url = `https://api.wotblitz.${
region.domain
}/wotb/account/info/?application_id=${applicationId}&fields=${
fields
}&account_id=${accountIds}`;
atMaxRate(async ({ _break: break2 }) => {
if (idRange.length < MAX_IDS_PER_CALL) {
console.log(
`Removing region ${region.domain} removed due to ID range exhaustion...`,
);
regions.splice(regionI, 1);
}
const response = await fetch(url);
const data = (await response.json()) as QuickInfo;
if (data.status !== "ok") {
console.warn("Failed API call; trying again...");
return;
}
let foundIds = 0;
for (const key in data.data) {
const value = data.data[key];
if (value === null) continue;
foundIds++;
const id = Number(key);
const chunkIndex = id % manifest.chunks;
const chunk = chunks[chunkIndex];
chunk.push(id);
}
if (foundIds === 0) {
region.dry_streak += idRange.length;
} else {
region.dry_streak = 0;
// console.log(`Found ${foundIds} ids in ${request.region.domain}`);
}
break2();
});
});
await save();
+45
View File
@@ -0,0 +1,45 @@
import { API_RATE } from "./constants";
const MAX_COMPUTE_TIME = (5 * 60 + 55) * 60 * 1000; // 5hr 55min
const t0 = performance.now();
const t1 = t0 + MAX_COMPUTE_TIME;
export function withinTimeLimit() {
return performance.now() < t1;
}
export function sleep(t: number) {
return new Promise((resolve) => setTimeout(resolve, t));
}
interface MethodContext {
_break(): void;
}
let lastT = 0;
const maxDelta = 1000 / API_RATE;
const DELTA_BUFFER = 0;
export async function atMaxRate(method: (context: MethodContext) => void) {
let isBroken = false;
const context: MethodContext = {
_break() {
isBroken = true;
},
};
while (!isBroken) {
if (!withinTimeLimit()) break;
const t = performance.now();
const delta = t - lastT;
if (delta < maxDelta) {
await sleep(maxDelta - delta + DELTA_BUFFER);
}
method(context);
lastT = t;
}
}
+20
View File
@@ -0,0 +1,20 @@
export type IdsManifest = IdsManifestV1;
interface IdsManifestV1 {
version: 1;
last_verified: number;
chunks: number;
total_count: number;
}
export interface RegionDescriptor {
domain: string;
seed: number;
max: number;
dry_streak: number;
}
export interface QuickInfo {
status: string;
data: Record<string, {} | null>;
}
@@ -0,0 +1,9 @@
// dava engine has a lot of tiny files, this is faster
export const DEPOT_DOWNLOADER_MAX_DOWNLOADS = 256;
export const DEPOT_DOWNLOADER_OUTPUT = "../../temp/depotdownloader";
export const DEPOT_DOWNLOADER_EXECUTABLE = `${DEPOT_DOWNLOADER_OUTPUT}/DepotDownloader`;
export const ARIA2_OUTPUT = "../../temp/aria2";
export const ARIA2_EXECUTABLE = `${ARIA2_OUTPUT}/aria2c`;
export const WGPKG_DIR = "../../temp/wgpkgs";
@@ -0,0 +1,98 @@
import { assertSecret } from "@blitzkit/core";
import { $ } from "bun";
import { mkdir, rm } from "node:fs/promises";
import {
DEPOT_DOWNLOADER_EXECUTABLE,
DEPOT_DOWNLOADER_MAX_DOWNLOADS,
DEPOT_DOWNLOADER_OUTPUT,
WGPKG_DIR,
} from "./constants";
import { QuickGitHubRelease } from "./types";
const provider = assertSecret(import.meta.env.CLIENT_PROVIDER);
const source = assertSecret(import.meta.env.CLIENT_SOURCE);
const dir = assertSecret(import.meta.env.CLIENT_DIR);
await rm(dir, { recursive: true, force: true });
await mkdir(dir, { recursive: true });
if (provider === "steam") {
const [id, platform] = source.split("/");
console.log("Downloading Depot Downloader...");
const releases = await fetch(
"https://api.github.com/repos/SteamRE/DepotDownloader/releases/latest",
).then((response) => response.json() as Promise<QuickGitHubRelease>);
const asset = releases.assets.find((asset) =>
asset.name.includes("linux-x64"),
);
if (!asset) {
throw new Error("No linux-x64 asset found for Steam DepotDownloader");
}
await rm(DEPOT_DOWNLOADER_OUTPUT, { recursive: true, force: true });
await $`curl -L "${asset.browser_download_url}" -o "${DEPOT_DOWNLOADER_OUTPUT}.zip"`;
await $`unzip "${DEPOT_DOWNLOADER_OUTPUT}.zip" -d "${DEPOT_DOWNLOADER_OUTPUT}"`;
await $`chmod +x "${DEPOT_DOWNLOADER_EXECUTABLE}"`;
console.log("Downloading client...");
const username = assertSecret(import.meta.env.STEAM_USERNAME);
const password = assertSecret(import.meta.env.STEAM_PASSWORD);
await $`${DEPOT_DOWNLOADER_EXECUTABLE} -os ${
platform
} -app ${id} -username ${username} -password ${
password
} -dir ${dir} -max-downloads ${DEPOT_DOWNLOADER_MAX_DOWNLOADS}`;
} else if (provider === "zip") {
const url = assertSecret(import.meta.env.CLIENT_SOURCE);
console.log("Downloading and extracting zipped client...");
await mkdir(dir, { recursive: true });
await $`curl -L "${url}" | bsdtar -xf- --strip-components=1 -C ${dir}`;
} else if (provider === "wgpkg") {
let index = 1;
const urls: string[] = [];
console.log("Discovering packages...");
while (true) {
const url = `${source}.${index.toString().padStart(3, "0")}`;
const response = await fetch(url);
if (response.status === 200) {
urls.push(url);
} else if (response.status === 404) {
break;
} else {
throw new Error(`Wargaming response error status: ${response.status}`);
}
index++;
}
console.log(`Downloading ${urls.length} packages...`);
await rm(WGPKG_DIR, { recursive: true });
const inputContent = urls
.map((url) => {
const name = url.split("/").pop()!;
return `${url}\n out=${name}\n dir=${WGPKG_DIR}`;
})
.join("\n");
await $`echo ${inputContent} | aria2c -j ${urls.length} -x 2 -s 2 -i -`;
const firstChunk = `${WGPKG_DIR}/${urls[0].split("/").pop()!}`;
await $`7z x "${firstChunk}" -o${dir} -y`;
await rm(WGPKG_DIR, { recursive: true });
} else {
throw new Error(`Unknown provider: ${provider}`);
}
@@ -0,0 +1,8 @@
export interface QuickGitHubRelease {
assets: Asset[];
}
interface Asset {
name: string;
browser_download_url: string;
}
-319
View File
@@ -1,319 +0,0 @@
import {
availableProvisions,
createDefaultProvisions,
createDefaultSkills,
fetchConsumableDefinitions,
fetchEquipmentDefinitions,
fetchModelDefinitions,
fetchProvisionDefinitions,
fetchSkillDefinitions,
fetchTankDefinitions,
TankDefinition,
tankIcon,
} from "@blitzkit/core";
import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
import strings from "@blitzkit/i18n/strings/en.json";
import { writeFile } from "fs/promises";
import { isEqual } from "lodash-es";
import { tankCharacteristics } from "../../website/src/core/blitzkit/tankCharacteristics";
import { genericDefaultEquipmentMatrix } from "../../website/src/stores/duel/constants";
const domain = "https://opentest.blitzkit.app";
import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets";
const currentTankDefinitions = await fetchTankDefinitions();
const currentConsumableDefinitions = await fetchConsumableDefinitions();
const currentProvisionDefinitions = await fetchProvisionDefinitions();
const currentEquipmentDefinitions = await fetchEquipmentDefinitions();
const currentSkillDefinitions = await fetchSkillDefinitions();
const currentModelDefinitions = await fetchModelDefinitions();
import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets-preview";
const previewTankDefinitions = await fetchTankDefinitions();
const previewConsumableDefinitions = await fetchConsumableDefinitions();
const previewProvisionDefinitions = await fetchProvisionDefinitions();
const previewEquipmentDefinitions = await fetchEquipmentDefinitions();
const previewSkillDefinitions = await fetchSkillDefinitions();
const previewModelDefinitions = await fetchModelDefinitions();
let notes = "# BlitzKit OpenTest Patch Notes\n\n";
const newTanks: TankDefinition[] = [];
for (const id in previewTankDefinitions.tanks) {
if (!(id in currentTankDefinitions.tanks)) {
newTanks.push(previewTankDefinitions.tanks[id]);
}
}
newTanks.sort((a, b) => b.tier - a.tier);
import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets-preview";
if (newTanks.length > 0) {
notes += "## New Tanks\n\n";
for (const tank of newTanks) {
notes += `### [${tank.name!.locales.en}](${domain}/tanks/${tank.slug})\n`;
notes += `![](${tankIcon(tank.id)})\n\n`;
}
notes += "\n";
}
const changedTanks: [TankDefinition, TankDefinition][] = [];
for (const stringId in previewTankDefinitions.tanks) {
const id = Number(stringId);
if (newTanks.some((tank) => tank.id === id)) continue;
const current = currentTankDefinitions.tanks[id];
const preview = previewTankDefinitions.tanks[id];
if (isEqual(current, preview)) continue;
changedTanks.push([current, preview]);
}
changedTanks.sort((a, b) => b[1].tier - a[1].tier);
import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets";
if (changedTanks.length > 0) {
notes += "## Changed Tanks\n\n";
for (const [current, preview] of changedTanks) {
const currentTurret = current.turrets.at(-1)!;
const previewTurret = preview.turrets.at(-1)!;
const currentGun = currentTurret.guns.at(-1)!;
const previewGun = previewTurret.guns.at(-1)!;
const currentShell = currentGun.shells[0];
const previewShell = previewGun.shells[0];
const currentEngine = current.engines.at(-1)!;
const previewEngine = preview.engines.at(-1)!;
const currentTracks = current.tracks.at(-1)!;
const previewTracks = preview.tracks.at(-1)!;
const currentTankModel = currentModelDefinitions.models[current.id];
const previewTankModel = previewModelDefinitions.models[preview.id];
const currentConsumables = Object.values(
currentConsumableDefinitions.consumables,
).filter((consumable) =>
checkConsumableProvisionInclusivity(consumable, current, currentGun),
);
const previewConsumables = Object.values(
previewConsumableDefinitions.consumables,
).filter((consumable) =>
checkConsumableProvisionInclusivity(consumable, preview, previewGun),
);
const addedConsumables = previewConsumables.filter(
(previewConsumable) =>
!currentConsumables.some(
(currentConsumable) => currentConsumable.id === previewConsumable.id,
),
);
const removedConsumables = currentConsumables.filter(
(currentConsumable) =>
!previewConsumables.some(
(previewConsumable) => previewConsumable.id === currentConsumable.id,
),
);
const currentProvisions = availableProvisions(
current,
currentGun,
currentProvisionDefinitions,
);
const previewProvisions = availableProvisions(
preview,
previewGun,
previewProvisionDefinitions,
);
const addedProvisions = previewProvisions.filter(
(previewProvision) =>
!currentProvisions.some(
(currentProvision) => currentProvision.id === previewProvision.id,
),
);
const removedProvisions = currentProvisions.filter(
(currentProvision) =>
!previewProvisions.some(
(previewProvision) => previewProvision.id === currentProvision.id,
),
);
const currentEquipment = currentEquipmentDefinitions.presets[
current.equipment_preset
].slots
.map(({ left, right }) => [left, right])
.flat();
const previewEquipment = previewEquipmentDefinitions.presets[
preview.equipment_preset
].slots
.map(({ left, right }) => [left, right])
.flat();
const addedEquipment = previewEquipment
.filter(
(previewEquipment) =>
!currentEquipment.some(
(currentEquipment) => currentEquipment === previewEquipment,
),
)
.map((id) => previewEquipmentDefinitions.equipments[id]);
const removedEquipment = currentEquipment
.filter(
(currentEquipment) =>
!previewEquipment.some(
(previewEquipment) => previewEquipment === currentEquipment,
),
)
.map((id) => currentEquipmentDefinitions.equipments[id]);
const equalize = false;
const currentCharacteristics = tankCharacteristics(
{
tank: current,
gun: currentGun,
shell: currentShell,
applyDynamicArmor: false,
applyReactiveArmor: false,
applySpallLiner: false,
assaultDistance: 0,
crewSkills: createDefaultSkills(currentSkillDefinitions),
camouflage: true,
consumables: [],
engine: currentEngine,
provisions: createDefaultProvisions(
current,
currentGun,
currentProvisionDefinitions,
),
equipmentMatrix: genericDefaultEquipmentMatrix,
stockEngine: current.engines[0],
stockGun: current.turrets[0].guns[0],
stockTrack: current.tracks[0],
stockTurret: current.turrets[0],
track: currentTracks,
turret: currentTurret,
equalize,
},
{
equipmentDefinitions: currentEquipmentDefinitions,
provisionDefinitions: currentProvisionDefinitions,
tankModelDefinition: currentTankModel,
},
);
const previewCharacteristics = tankCharacteristics(
{
tank: preview,
gun: previewGun,
shell: previewShell,
applyDynamicArmor: false,
applyReactiveArmor: false,
applySpallLiner: false,
assaultDistance: 0,
crewSkills: createDefaultSkills(previewSkillDefinitions),
camouflage: true,
consumables: [],
engine: previewEngine,
provisions: createDefaultProvisions(
preview,
previewGun,
previewProvisionDefinitions,
),
equipmentMatrix: genericDefaultEquipmentMatrix,
stockEngine: preview.engines[0],
stockGun: preview.turrets[0].guns[0],
stockTrack: preview.tracks[0],
stockTurret: preview.turrets[0],
track: previewTracks,
turret: previewTurret,
equalize,
},
{
equipmentDefinitions: previewEquipmentDefinitions,
provisionDefinitions: previewProvisionDefinitions,
tankModelDefinition: previewTankModel,
},
);
const changedCharacteristics: string[] = [];
for (const key in currentCharacteristics) {
const characteristicKey = key as keyof typeof currentCharacteristics;
if (
currentCharacteristics[characteristicKey] !==
previewCharacteristics[characteristicKey]
) {
changedCharacteristics.push(characteristicKey);
}
}
notes += `### [${preview.name!.locales.en}](${domain}/tanks/${preview.slug})\n`;
notes += `![](${tankIcon(preview.id)})\n\n`;
if (addedConsumables.length + removedConsumables.length > 0) {
notes += `Consumables: `;
notes += [
...addedConsumables.map(
(consumable) => `🟢 ${consumable.name!.locales.en}`,
),
...removedConsumables.map(
(consumable) => `🔴 ${consumable.name!.locales.en}`,
),
].join(", ");
notes += `\n`;
}
if (addedProvisions.length + removedProvisions.length > 0) {
notes += `Provisions: `;
notes += [
...addedProvisions.map(
(provision) => `🟢 ${provision.name!.locales.en}`,
),
...removedProvisions.map(
(provision) => `🔴 ${provision.name!.locales.en}`,
),
].join(", ");
notes += `\n`;
}
if (addedEquipment.length + removedEquipment.length > 0) {
notes += `-Equipment: `;
notes += [
...addedEquipment.map(
(equipment) => `🟢 ${equipment.name!.locales.en}`,
),
...removedEquipment.map(
(equipment) => `🔴 ${equipment.name!.locales.en}`,
),
].join(", ");
notes += `\n`;
}
if (changedCharacteristics.length > 0) {
notes += `Characteristics:\n`;
for (const characteristicUntyped of changedCharacteristics) {
const characteristic =
characteristicUntyped as keyof typeof currentCharacteristics;
const name =
strings.website.tools.tankopedia.characteristics.values[
characteristic
];
const before = currentCharacteristics[characteristic];
const after = previewCharacteristics[characteristic];
notes += `- ${
typeof before === "number" && typeof after === "number"
? after > before
? "⬆️"
: "🔻"
: "✏️"
} ${name}: \`${
typeof before === "number" ? before.toFixed(2) : before
}\` → \`${typeof after === "number" ? after.toFixed(2) : after}\`\n`;
}
}
}
}
await writeFile("../../temp/patch-notes.md", notes);
-50
View File
@@ -1,50 +0,0 @@
import { type TranslationTree } from "@blitzkit/i18n";
import locales from "@blitzkit/i18n/locales.json";
import { writeFile } from "fs/promises";
const ROOT = "@blitzkit/i18n/strings";
const WORKING_ROOT = "../../packages/i18n/strings";
const defaultStrings = require(`${ROOT}/${locales.default}.json`).default;
const nonDefaultLocales = locales.supported.filter(
({ locale }) => locale !== locales.default
);
function prune(defaultStrings: TranslationTree, strings: TranslationTree) {
for (const key in strings) {
if (key in defaultStrings) {
if (typeof defaultStrings[key] === typeof strings[key]) {
if (typeof strings[key] === "string") {
if (
// are equivalent regardless of %s flavor
(defaultStrings[key] as string).replaceAll(/%s\d/g, "%s") ===
(strings[key] as string).replaceAll(/%s\d/g, "%s") ||
// string is empty
strings[key] === "" ||
// string has old flavor of %s
strings[key].match(/%s([^\d]|$)/g) !== null
) {
delete strings[key];
}
} else {
prune(defaultStrings[key] as TranslationTree, strings[key]);
}
} else {
delete strings[key];
}
} else {
delete strings[key];
}
}
return strings;
}
for (const locale of nonDefaultLocales) {
console.log(`Pruning ${locale}...`);
const strings = require(`${ROOT}/${locale}.json`).default;
const pruned = prune(defaultStrings, strings);
writeFile(`${WORKING_ROOT}/${locale}.json`, JSON.stringify(pruned, null, 2));
}
@@ -0,0 +1,10 @@
export const IDS_API_BASE = "https://ids.blitzkit.app";
export const PARAMETERS = [
"spotted",
"hits",
"frags",
"wins",
"losses",
"capture_points",
];
@@ -0,0 +1,64 @@
import { idToRegion } from "@blitzkit/core";
import { GitObjectStorage } from "../core/blitzkit/gitObjectStorage";
import { IdArray } from "../core/blitzkit/idArray";
import { API_RATE } from "../discoverIds/constants";
import { sleep, withinTimeLimit } from "../discoverIds/time";
import { IdsManifest } from "../discoverIds/types";
import { IDS_API_BASE } from "./constants";
import { PerformanceManifest, QuickStatistics } from "./types";
const applicationId = import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID;
const storage = await new GitObjectStorage(
"blitzkit/data-performance",
"../../temp/performance",
import.meta.env.GH_TOKEN,
).init();
const manifest = await storage.json<PerformanceManifest>("manifest.json");
const idsManifest = await fetch(`${IDS_API_BASE}/manifest.json`).then(
(response) => response.json() as Promise<IdsManifest>,
);
while (withinTimeLimit()) {
manifest.last_updated = (manifest.last_updated + 1) % idsManifest.chunks;
const url = `${IDS_API_BASE}/chunks/chunk-${manifest.last_updated}.dat.lz4`;
const response = await fetch(url);
const buffer = await response.arrayBuffer();
const bytes = new Uint8Array(buffer);
const ids = IdArray.fromCompressed(bytes);
const size = ids.size();
console.log(
`Chunk ${manifest.last_updated} contains ${size} ids, estimated to take ${(size / API_RATE / 60).toFixed(0)}min`,
);
for (let i = 0; i < size; i++) {
await sleep();
const id = ids.get(i);
const domain = idToRegion(id);
const url = `https://api.wotblitz.${
domain
}/wotb/tanks/stats/?application_id=${applicationId}&account_id=${id}`;
const response = await fetch(url);
const json = (await response.json()) as QuickStatistics;
const data = json.data[id];
if (json.status !== "ok" || data === null) {
console.log(`Failed player ${id}; skipping...`);
continue;
}
for (const tank of data) {
console.log(new Date(tank.battle_life_time * 1000));
}
}
}
// storage.commit();
@@ -0,0 +1,26 @@
export type PerformanceManifest = PerformanceManifestV1;
interface PerformanceManifestV1 {
version: 1;
last_updated: number;
}
export interface QuickStatistics {
status: string;
data: Record<string, QuickStatisticsTank[] | null>;
}
interface QuickStatisticsTank {
last_battle_time: number;
battle_life_time: number;
tank_id: number;
all: Record<string, number>;
}
interface ChunkObservations {
count: number;
observations: Record<number, Observation>;
}
interface Observation {}
@@ -1,39 +0,0 @@
import decompress from "decompress";
import { existsSync } from "fs";
import { mkdir, readdir, writeFile } from "fs/promises";
if (!existsSync("../../temp")) {
console.log("Creating temp directory...");
await mkdir("../../temp");
}
console.log("Downloading depot downloader...");
await fetch(
"https://api.github.com/repos/SteamRE/DepotDownloader/releases/latest"
)
.then(
(response) =>
response.json() as Promise<{
assets: { browser_download_url: string; name: string }[];
}>
)
.then(({ assets }) => {
const asset = assets.find(({ name }) => name.includes("linux-x64"));
if (!asset) throw new Error("No asset found");
return fetch(asset.browser_download_url);
})
.then((response) => response.arrayBuffer())
.then((arrayBuffer) =>
writeFile("../../temp/depotDownloader.zip", new Uint8Array(arrayBuffer))
);
console.log("Unzipping...");
await decompress(
"../../temp/depotDownloader.zip",
"../../temp/depotDownloader"
);
console.log(
"Download complete; files:",
await readdir("../../temp/depotDownloader")
);
-619
View File
@@ -1,619 +0,0 @@
name;maxHealthFactor;piercingPowerFactor;moduleHealthFactor;damageFactor;armorFactor
china:Ch01_Type59;1.3;1.33;1.5;1.29;1.35
china:Ch02_Type62;1.5;1.6;1.5;1.27;1.61
china:Ch03_WZ-111;1.4;1.17;1.4;1.3;1.35
china:Ch04_T34_1;1.5;1.54;1.4;1.3;1.61
china:Ch05_T34_2;1.3;1.33;1.5;1.29;1.34
china:Ch07_Vickers_MkE_Type_BT26;5.5;5.07;3.3;7.05;8.8
china:Ch08_Type97_Chi_Ha;3.7;3.71;2.2;3.13;5.56
china:Ch09_M5;2.9;2.91;1.9;2.54;3.47
china:Ch10_IS2;1.6;1.44;1.4;1.33;1.61
china:Ch11_110;1.4;1.17;1.4;1.29;1.35
china:Ch12_111_1_2_3;1.2;1.02;1.4;1.17;1.14
china:Ch14_T34_3;1.3;1.37;1.5;1.13;1.17
china:Ch15_59_16;1.9;1.77;1.6;1.57;2.24
china:Ch15_59_16_BP;1.9;1.7;1.6;1.56;2.24
china:Ch16_WZ_131;1.6;1.52;1.5;1.28;1.61
china:Ch17_WZ131_1_WZ132;1.4;1.22;1.5;1.29;1.34
china:Ch18_WZ-120;1.1;1.09;1.4;1.11;1.17
china:Ch20_Type58;1.9;1.88;1.5;1.57;1.86
china:Ch21_T34;2.7;2.1;1.7;1.97;2.12
china:Ch23_112;1.4;1.17;1.4;1.31;1.35
china:Ch24_Type64;1.9;1.86;1.6;1.57;2.35
china:Ch26_59_Patton;1.3;1.33;1.5;1.1;1.16
china:Ch28_WZ_132A;1.1;1.04;1.5;1.11;1.24
china:Ch34_WZ-131G_FT;2.1;1.57;1.4;1.51;1.73
china:Ch34_WZ-131G_FT_BP;2.1;1.57;1.4;1.51;2.02
china:Ch35_T-34-2G_FT;1.8;1.48;1.4;1.25;1.61
china:Ch36_WZ-111-1G_FT;1.6;1.16;1.4;1.25;1.35
china:Ch37_WZ-111G_FT;1.2;1.05;1.4;1.15;1.13
china:Ch39_WZ120_1G_FT;1.6;1.16;1.4;1.24;1.35
china:Ch40_WZ120G_FT;1.1;1.05;1.4;1.08;1.13
china:Ch42_M41D;1.7;1.49;1.5;1.24;1.61
china:Ch43_WZ-122_2;1.3;1.21;1.5;1.31;1.35
china:Ch45_WZ_114;1.2;1.02;1.4;1.19;1.13
china:Ch47_BZ_176;1.2;1.03;1.4;1.17;1.13
china:Ch49_BZ_68;1.2;1.02;1.4;1.2;1.16
china:Ch50_BZ_166;1.4;1.17;1.4;1.3;1.35
china:Ch51_BZ_58;1.6;1.46;1.4;1.29;1.62
china:Ch56_BZ_74_1;1.2;1.02;1.4;1.16;1.14
china:Ch59_BZ58_2;1.2;1.02;1.4;1.19;1.13
china:Ch60_Type59_spc;1.3;1.37;1.5;1.32;1.35
china:Ch_LT_vz38;4.3;4.29;2.7;3.99;4.88
china:Ch_WZ-112v2;1.4;1.17;1.4;1.31;1.35
china:Type_58BP;1.9;1.88;1.5;1.57;2.04
european:Cz02_T50;1.1;1.03;1.4;1.12;1.19
european:Cz05_T34-100;1.5;1.45;1.4;1.29;1.61
european:Cz07_TVP_46;1.3;1.12;1.5;1.29;1.35
european:Cz08_T-25;1.9;1.83;1.5;1.56;2.02
european:Cz08_T-25BP;1.9;1.83;1.5;1.55;2.02
european:Cz08_T-25BPS;1.9;1.83;1.5;1.57;2.02
european:Cz13_T-27;1.1;1.07;1.4;1.12;1.19
european:Cz14_Skoda_T-56;1.4;1.17;1.4;1.33;1.35
european:Cz15_Skoda_T-45;1.7;1.44;1.4;1.27;1.61
european:Cz16_TNH_T_vz51;1.2;1.02;1.4;1.17;1.15
european:Cz18_TNH_105_1000;1.4;1.16;1.4;1.28;1.35
european:Cz19_Vz_44_1;1.7;1.43;1.4;1.3;1.61
european:Cz20_ShPTK_TVP_100;1.5;1.16;1.4;1.24;1.34
european:Cz29_Skoda_P-JS;2.2;1.67;1.4;1.56;1.78
european:Cz29_Skoda_P-JS_BP;2.2;1.67;1.4;1.57;1.79
european:F01_Vickers_MkF;5.5;5.07;3.3;6.16;8.8
european:It09_P43_ter;1.5;1.54;1.4;1.3;1.61
european:It10_P43_BD;1.9;1.74;1.5;1.57;1.82
european:It10_P43_bis;1.9;1.74;1.5;1.57;1.82
european:It10_P43_bis_BP;1.9;1.7;1.5;1.58;1.82
european:It116_Carro_d_assalto_P88;1.6;1.33;1.4;1.28;1.61
european:It117_ProgettoCC55mod54;1.3;1.13;1.4;1.29;1.35
european:It119_ProgettoC50mod66;1.1;1.02;1.4;1.18;1.13
european:It12_Prototipo_Standard_B;1.1;1.04;1.4;1.14;1.17
european:It13_Progetto_M35_mod_46;1.3;1.33;1.5;1.27;1.35
european:It14_P44_Pantera;1.3;1.21;1.5;1.26;1.35
european:It15_Semovente_M41;3.2;1.98;1.5;1.97;2.13
european:It16_Bassoto;2;1.59;1.4;1.53;1.8
european:It17_Semovente_mod_56;1.7;1.4;1.4;1.28;1.62
european:It18_Semovente_mod_64;1.4;1.2;1.4;1.24;1.35
european:It19_Controcarro_1MK2_Ciclope;1.1;1.05;1.4;1.19;1.13
european:It22_Progetto_C45_mod_71;1.3;1.14;1.4;1.23;1.35
european:It30_CC_mod_64_Prem;1.4;1.17;1.4;1.25;1.35
european:Pl03_PzV_Pudel;1.9;1.57;1.5;1.56;1.92
european:Pl05_50TP_Tyszkiewicza;1.2;1.02;1.4;1.18;1.13
european:Pl06_10TP;4.3;4.29;2.7;4.28;7.66
european:Pl07_14TP;3.6;3.57;2.1;3.12;5.56
european:Pl08_50TP_prot;1.2;1.02;1.4;1.17;1.14
european:Pl10_40TP_Habicha;2.1;1.64;1.4;1.57;1.83
european:Pl119_CS_52_LIS;1.5;1.56;1.4;1.31;1.61
european:Pl11_45TP_Habicha;1.6;1.44;1.4;1.3;1.61
european:Pl13_53TP_Markowskiego;1.4;1.14;1.4;1.32;1.35
european:Pl17_DS_PZlnz;2.6;2.28;1.7;1.95;2.12
european:Pl18_BUGI;1.9;1.57;1.5;1.58;1.94
european:Pl20_CS_44;1.5;1.54;1.4;1.3;1.61
european:Pl22_CS_59;1.1;1.04;1.4;1.1;1.21
european:Pl23_CS_53;1.3;1.31;1.5;1.29;1.35
european:Pl24_56TP;1.4;1.15;1.4;1.29;1.35
european:Pl27_SDP_Wz_66T;1.5;1.17;1.4;1.27;1.35
european:Pl28_SDP_Wz_60;1.1;1.05;1.4;1.11;1.12
european:Pl30_SDP_Wz58T;1.4;1.16;1.4;1.26;1.35
european:Pl31_SDP_Wz_57D;1.7;1.43;1.4;1.28;1.62
european:Pl32_SDP_Wz_44;2;1.57;1.4;1.52;1.76
european:Pl33_SDP_Wz_40;3.2;2.13;1.5;1.96;2.13
european:S02_StrvM42;2.7;2.22;1.7;1.95;2.11
european:S04_Lago-I;3.1;2.69;1.9;2.54;2.32
european:S07_Strv74BP;1.9;1.67;1.5;1.55;1.9
european:S07_Strv_74;1.9;1.67;1.5;1.55;1.9
european:S13_Leo;1.5;1.58;1.4;1.3;1.61
european:S13_Leo_NY;1.5;1.58;1.4;1.3;1.61
european:S17_EMIL_1952E2;1.2;1.02;1.4;1.16;1.12
european:S18_EMIL_1951E1;1.3;1.16;1.4;1.29;1.35
european:S23_Strv_81;1.3;1.14;1.5;1.26;1.35
european:S25_EMIL_51;1.3;1.12;1.4;1.29;1.35
european:S26_Lancen_C;1.3;1.22;1.5;1.31;1.35
european:S32_Bofors_Tornvagn;1.1;1.02;1.4;1.17;1.12
france:AMX38;4.4;4.29;2.7;4.15;5.95
france:AMX40;3;2.88;1.9;2.52;3.53
france:AMX50_Foch;1.2;1.05;1.4;1.11;1.14
france:AMX_12t;1.9;1.64;1.6;1.6;2.02
france:AMX_13_75;1.7;1.61;1.5;1.25;1.61
france:AMX_13_90;1.4;1.37;1.5;1.29;1.34
france:AMX_50_100;1.4;1.12;1.4;1.26;1.35
france:AMX_50_120;1.2;1.02;1.4;1.12;1.17
france:AMX_AC_Mle1946;1.7;1.45;1.4;1.24;1.61
france:AMX_AC_Mle1948;1.5;1.16;1.4;1.24;1.35
france:AMX_M4_1945;1.6;1.47;1.4;1.28;1.61
france:ARL_44;2.1;1.57;1.4;1.53;1.82
france:ARL_44BP;2.1;1.53;1.4;1.58;1.81
france:ARL_V39;2;1.59;1.4;1.52;1.82
france:Achilles_FR;2.1;1.6;1.4;1.52;1.83
france:B1;4.1;2.8;1.6;2.54;3.64
france:BDR_G1B;3.2;1.91;1.5;1.95;2.13
france:Bat-Chat-25CL;1.1;1.05;1.5;1.1;1.22
france:D1;4.3;4.62;2.7;3.8;6.52
france:D2;3.7;3.64;2.2;3;5.52
france:ELC_AMX;2.8;1.97;2.3;2.31;2.09
france:F113_PzV_FR;1.9;1.65;1.5;1.56;1.89
france:F114_Projet_4_1;1.1;1.03;1.4;1.09;1.22
france:F115_Lorraine_50t;1.2;1.02;1.4;1.13;1.16
france:F116_Bat_Chat_Bourrasque;1.3;1.28;1.5;1.31;1.34
france:F117_Alt_Proto_AMX_30;1.3;1.15;1.5;1.29;1.35
france:F11_Renault_G1R;3.2;2;1.5;1.96;2.12
france:F120_Project_Louis;1.1;1.04;1.4;1.12;1.14
france:F123_ERAC_105_Proto;1.3;1.22;1.5;1.3;1.35
france:F126_AMX_13_M24;1.5;1.58;1.4;1.32;1.62
france:F127_ELC_AMX_901_Proto;1.4;1.28;1.5;1.28;1.34
france:F140_Sentinelle;1.4;1.15;1.4;1.28;1.35
france:F19_Lorraine40t;1.3;1.17;1.5;1.3;1.34
france:F19_Lorraine40t_Com;1.3;1.17;1.5;1.29;1.34
france:F34_ARL_V39_BP;2.1;1.59;1.4;1.51;1.82
france:F68_AMX_Chasseur_de_char_46;1.3;1.17;1.5;1.25;1.33
france:F69_AMX_13D;1.4;1.16;1.5;1.28;1.34
france:F69_AMX_13_57GF;1.6;1.49;1.5;1.26;1.61
france:F71_AMX_30_prototype;1.1;1.06;1.4;1.12;1.19
france:F73_M4A1_Revalorise;1.3;1.23;1.5;1.27;1.34
france:F74_AMX_M4_1949;1.4;1.12;1.4;1.28;1.35
france:F75_Char_de_25t;1.1;1.02;1.5;1.11;1.22
france:F84_Somua_SM;1.4;1.12;1.4;1.14;1.14
france:F89_Canon_dassaut_105;1.6;1.15;1.4;1.26;1.35
france:FCM_36Pak40;4.2;2.95;1.9;3.18;5.52
france:FCM_50t;1.3;1.17;1.5;1.25;1.35
france:F_AMX-12BP;1.9;1.64;1.6;1.6;2.02
france:RenaultFT_AC;5.5;4.37;2.2;4.49;8.51
france:RenaultR35;5.5;5.28;3.3;6.76;7.8
france:RenaultUE57;4.2;2.79;1.9;2.68;5.95
france:S_35CA;3.2;2.07;1.5;1.96;2.11
france:Somua_Sau_40;3.7;2.75;1.8;2.54;3.62
germany:B-1bis_captured;4.1;2.96;1.6;2.55;3.89
germany:DW_II;3.7;2.52;1.6;2.52;3.53
germany:DickerMax;2.1;1.57;1.4;1.53;2.02
germany:E-25;1.8;1.46;1.4;1.24;1.61
germany:E-50;1.1;1.09;1.4;1.1;1.15
germany:E-75;1.2;1.02;1.4;1.17;1.14
germany:Ferdinand;1.4;1.16;1.4;1.25;1.35
germany:G100_Gtraktor_Krupp;4.9;3.31;1.9;3.49;5.75
germany:G103_RU_251;1.4;1.3;1.5;1.25;1.33
germany:G106_PzKpfwPanther_AusfF;1.3;1.21;1.5;1.26;1.35
germany:G109_Steyr_WT;1.8;1.37;1.4;1.27;1.6
germany:G110_Mauschen;1.2;1.02;1.4;1.18;1.12
germany:G112_JagdPanzer_105;1.2;1.05;1.4;1.04;1.18
germany:G113_SP_I_C;1.7;1.49;1.5;1.28;1.58
germany:G114_Rheinmetall_Scorpion;1.6;1.16;1.4;1.24;1.33
germany:G115_VK10001P;1.4;1.15;1.4;1.33;1.35
germany:G118_VK4503;1.7;1.3;1.4;1.27;1.61
germany:G119_Panzer58_SH;1.3;1.22;1.5;1.28;1.35
germany:G119_Pz58_Mutz;1.3;1.17;1.5;1.26;1.35
germany:G120_M41_90;1.4;1.29;1.5;1.26;1.34
germany:G126_HWK_12;1.1;1.05;1.5;1.08;1.29
germany:G136_Tiger_131;2.1;1.68;1.4;1.55;1.85
germany:G138_VK16801P;1.5;1.15;1.4;1.16;1.13
germany:G140_HWK_30;1.4;1.27;1.5;1.27;1.34
germany:G142_M48RPz;1.3;1.18;1.5;1.27;1.35
germany:G147_mPanzer_64;1.7;1.49;1.5;1.29;1.61
germany:G148_Elephant;1.4;1.16;1.4;1.21;1.35
germany:G154_Kpz_07_RH;1.3;1.15;1.5;1.26;1.35
germany:G162_Project_Kpz_07P_E;1.1;1.02;1.4;1.16;1.14
germany:G164_Kpz_Pr_68_P;1.3;1.22;1.5;1.3;1.34
germany:G165_Erich_Koncept;1.1;1.06;1.4;1.14;1.19
germany:G166_LKpz_70_K;1.1;1.02;1.5;1.11;1.32
germany:G167_Tiger_Maus;1.2;1.03;1.4;1.11;1.13
germany:G168_KJpz_T_III;1.2;1.05;1.4;1.05;1.29
germany:G190_VK_1602_Quby;1.4;1.36;1.5;1.25;1.34
germany:G20_Marder_II;4.2;3.01;1.9;3.18;5.54
germany:G85_Auf_Panther;1.5;1.49;1.5;1.25;1.61
germany:G_E-75RT;1.4;1.14;1.4;1.3;1.35
germany:G_E-75TS;1.2;1.03;1.4;1.13;1.13
germany:G_VK3502_BP;2.1;1.68;1.4;1.57;1.79
germany:H39_captured;4.3;5.58;2.7;3.99;6.04
germany:Hetzer;3.7;2.6;1.8;2.54;3.47
germany:Indien_Panzer;1.3;1.17;1.5;1.25;1.35
germany:JagdPanther;1.8;1.37;1.4;1.25;1.62
germany:JagdPantherII;1.6;1.16;1.4;1.23;1.35
germany:JagdPzIV;2.1;1.58;1.4;1.49;1.8
germany:JagdPzIV_AusfBP;2;1.58;1.4;1.49;1.8
germany:JagdTiger;1.1;1.05;1.4;1.04;1.12
germany:JagdTiger_SdKfz_185;1.4;1.17;1.4;1.21;1.35
germany:JagdTiger_SdKfz_185_Snowstorm;1.4;1.33;1.4;1.21;1.35
germany:Krupp-38D;2.6;2.22;1.7;1.95;2.12
germany:Lowe;1.4;1.11;1.4;1.29;1.35
germany:Nashorn;2;1.51;1.4;1.51;2.24
germany:Nashorn_BP;2;1.51;1.4;1.5;2.24
germany:Panther_II;1.3;1.21;1.5;1.26;1.35
germany:Panther_M10;1.5;1.54;1.4;1.26;1.61
germany:PanzerJager_I;5.5;4.33;2.2;4.49;8.8
germany:Pro_Ag_A;1.1;1.02;1.4;1.1;1.21
germany:Pz35t;4.4;4.9;2.7;3.94;6.05
germany:Pz38_NA;2.9;2.78;1.9;2.55;3.65
germany:Pz38t;3.6;3.88;2.1;3.05;5.58
germany:PzII;5.5;5.28;3.3;6.67;6.81
germany:PzIII;3.7;3.69;2.2;3.26;5.49
germany:PzIII_A;3.7;3.09;2.1;2.98;5.58
germany:PzIII_IV;2.7;2.22;1.7;1.95;2.12
germany:PzII_J;3.6;5.5;2.1;2.83;5.46
germany:PzII_Luchs;2.9;2.94;1.9;2.55;2.2
germany:PzIV;2.7;2.22;1.7;1.95;2.13
germany:PzIV_Hydro;2.5;2.22;1.7;1.95;2.11
germany:PzIV_schmalturm;1.9;1.65;1.5;1.53;1.87
germany:PzV;1.5;1.33;1.4;1.26;1.61
germany:PzVI;1.7;1.3;1.4;1.28;1.62
germany:PzVIB_Tiger_II;1.4;1.14;1.4;1.27;1.35
germany:PzVI_AusfBP;2.1;1.68;1.4;1.56;1.79
germany:PzVI_Tiger_P;1.7;1.3;1.4;1.28;1.62
germany:PzV_PzIV;2.6;2.22;1.7;1.95;2.12
germany:Pz_II_AusfG;3.8;2.9;2.1;2.85;5.55
germany:Pz_IV_AusfA;3.6;3.2;2.2;4.05;5.74
germany:Pz_IV_AusfBT;2.7;2.53;1.7;1.96;2.12
germany:Pz_IV_AusfD;3.1;2.43;1.9;2.54;3.09
germany:Pz_Sfl_IVc;3;1.61;1.5;1.98;2.08
germany:RhB_Waffentrager;1.6;1.26;1.4;1.28;1.33
germany:S01_Frankentank;1.7;1.33;1.4;1.37;1.62
germany:S35_captured;3.8;2.85;2.1;2.93;5.52
germany:StuGIII;3.2;1.93;1.5;1.97;2.12
germany:Sturer_Emil;1.8;1.31;1.4;1.28;1.61
germany:T-15;3.8;4.72;2.1;2.82;5.55
germany:T-25;2.5;2.13;1.7;1.95;2.11
germany:VK1602;2.8;2.9;2.3;1.96;2.11
germany:VK2801;1.9;1.53;1.6;1.58;1.95
germany:VK2801_BP;1.9;1.53;1.6;1.59;1.95
germany:VK3001H;3.2;1.75;1.5;1.96;2.13
germany:VK3001P;1.9;1.61;1.5;1.57;1.81
germany:VK3002DB;1.5;1.58;1.4;1.28;1.61
germany:VK3002DB_BP;1.9;1.65;1.5;1.56;1.87
germany:VK3002DB_V1;1.9;1.65;1.5;1.56;1.84
germany:VK3002M;1.9;1.65;1.5;1.57;1.91
germany:VK3601H;2.1;1.64;1.4;1.57;1.78
germany:VK4502A;1.3;1.17;1.4;1.29;1.35
germany:VK4502P;1.2;1.02;1.4;1.18;1.13
germany:Waffentrager_IV;1.2;1.06;1.4;1.12;1.19
germany:Waffentrager_IV_CL;1.2;1.05;1.4;1.06;1.19
japan:Chi_Ha;3.6;3.8;2.2;3.2;5.54
japan:Chi_He;3;2.72;1.9;2.55;3.24
japan:Chi_Ni;4.4;4.62;2.5;4.09;6.52
japan:Chi_Nu;2.6;2.02;1.7;1.94;2.12
japan:Chi_Nu_Kai;2.6;2.02;1.7;1.95;2.11
japan:Chi_Ri;1.5;1.58;1.4;1.25;1.61
japan:Chi_To;1.9;1.61;1.5;1.55;1.9
japan:Firefly_GuP;1.9;1.49;1.5;1.55;2.01
japan:G09_Hetzer_GuP;3.7;2.6;1.8;2.54;3.47
japan:Ha_Go;5.7;5.36;3.3;5.99;8.2
japan:IS-2_GuP;1.6;1.44;1.4;1.33;1.61
japan:J21_Ho_Ri;1.2;1.05;1.4;1.11;1.13
japan:J22_Ho_Ri_type96;1.5;1.16;1.4;1.25;1.35
japan:J23_Chi_To_SPG;1.8;1.37;1.4;1.25;1.62
japan:J24_Type_57;1.4;1.16;1.4;1.33;1.35
japan:J26_Type_68;1.2;1.02;1.4;1.13;1.13
japan:J27_Chi_Se;1.4;1.15;1.4;1.27;1.35
japan:J28_Ju_To;1.7;1.41;1.4;1.32;1.62
japan:J29_Ju_Nu;2.1;1.73;1.4;1.57;1.8
japan:J29_Ju_Nu_BP;2.1;1.73;1.4;1.57;1.8
japan:J30_Mitsu_108;3.2;2.37;1.5;1.97;2.13
japan:J31_Type_5_Ka_Ri;1.5;1.16;1.4;1.26;1.35
japan:J36_Type_63_HT;1.4;1.15;1.4;1.29;1.35
japan:J38_Type_95_Ji_Ro;1.5;1.16;1.4;1.22;1.35
japan:Ke_Ho;2.9;2.71;1.9;2.55;1
japan:Ke_Ni;3.8;4.07;2.1;2.67;5.71
japan:Ke_Ni_B;4.5;4.85;2.1;2.14;5.71
japan:PzVI_GuP;2.1;1.64;1.4;1.54;1.81
japan:Pz_IV_AusfH;2.6;2.22;1.7;1.95;2.12
japan:STA_1;1.3;1.15;1.5;1.28;1.34
japan:Type_61;1.1;1.02;1.4;1.1;1.24
other:AMX_dracula;1.5;1.58;1.4;1.3;1.61
other:Akawara;1.3;1.28;1.5;1.28;1.35
other:Oth06_Sega_Lupus;1.7;1.44;1.4;1.34;1.62
other:Oth08_WH_Vindicator;1.8;1.74;1.4;1.24;1.61
other:Oth09_WH_Predator;1.7;1.47;1.4;1.25;1.62
other:Oth11_T-55A;1.1;1.1;1.4;1.09;1.16
other:Oth12_Sega_Hafen;1.6;1.66;1.4;1.27;1.61
other:Oth14_MdM_AshKnight;1.7;1.47;1.4;1.27;1.62
other:Oth15_RustCrab;2.6;2.22;1.7;1.96;2.12
other:Oth16_KV_TG;1.6;1.76;1.4;1.27;1.61
other:Oth17_BDT-5A;2.6;2.22;1.7;1.95;2.12
other:Oth18_BDT-5B;1.9;1.85;1.5;1.55;1.94
other:Oth19_BDT-5C;1.5;1.66;1.4;1.25;1.61
other:Oth20_M4FL10;1.5;1.67;1.4;1.27;1.61
other:Oth22_Sega_Vulcan;1.7;1.41;1.4;1.33;1.62
other:Oth23_Werewolf;1.6;1.44;1.4;1.33;1.61
other:Oth24_StuH_42HW;3.2;2.6;1.5;1.97;2.13
other:Oth25_HWT20_FalconFire;1.7;1.47;1.4;1.25;1.62
other:Oth26_StrvM42HW;2.7;2.22;1.7;1.96;2.11
other:Oth27_M24_AMX;1.9;1.64;1.6;1.6;2.02
other:Oth28_Sturmfeur_HW;1.7;1.41;1.4;1.31;1.61
other:Oth29_A15HW;2.8;1.83;2.3;1.96;2.12
other:Oth30_T54MS;1.3;1.2;1.5;1.3;1.35
other:Oth31_T150BP;2.1;1.71;1.4;1.56;1.79
other:Oth33_Blizzard_BP;2;1.68;1.6;1.56;2.21
other:Oth34_PanzerMummy;1.7;1.39;1.4;1.3;1.61
other:Oth35_PanzerMejai;3.2;2.27;1.5;1.95;2.13
other:Oth36_Indien_Panzer_S1;1.5;1.56;1.4;1.28;1.61
other:Oth37_T26_E4_SuperPershing_S1;1.3;1.25;1.5;1.3;1.35
other:Oth38_50TP_Tyszkiewicza_S1;1.1;1.02;1.4;1.17;1.14
other:Oth39_T20_S2;1.5;1.62;1.4;1.28;1.61
other:Oth40_TVP_46_S2;1.3;1.15;1.5;1.28;1.35
other:Oth41_Centurion_Mk3_S2;1.1;1.05;1.4;1.11;1.18
other:Oth42_ForestWitch_HW23;1.7;1.42;1.4;1.32;1.61
other:Oth44_Charioteer_T;1.6;1.17;1.4;1.27;1.35
other:Oth45_Sandstriker;1.4;1.16;1.4;1.33;1.35
other:Oth46_Charioteer_S3;1.8;1.37;1.4;1.27;1.61
other:Oth47_AMX_13_90_S3;1.4;1.3;1.5;1.28;1.34
other:Oth48_EMIL_1951E1_S3;1.1;1.02;1.4;1.15;1.14
other:Oth49_TM_04_NT;1.5;1.63;1.4;1.34;1.61
other:Oth50_STT;1.6;1.55;1.5;1.28;1.61
other:Oth51_FD_24;1.3;1.18;1.5;1.28;1.35
other:Oth52_Toxique;1.3;1.32;1.5;1.31;1.35
other:Oth53_Rammer;1.6;1.46;1.4;1.31;1.61
other:Oth61_Pz_VM_Set;4.1;2.58;1.6;2.54;3.71
other:Oth62_Pz_VlD_Set;3.2;1.97;1.5;1.96;2.13
other:Oth63_Pz_VllF_Set;2.1;1.64;1.4;1.57;1.84
other:Oth64_Pz_VlllK_Set;1.6;1.44;1.4;1.31;1.61
other:Oth65_PzVIllM_Tigeom;2.1;1.67;1.4;1.59;1.86
other:Oth66_E100ANN_S4;1.7;1.47;1.4;1.33;1.62
other:Oth67_FV215b_183ANN_S4;1.5;1.16;1.4;1.24;1.35
other:Oth68_KV2ANN_S4;1.2;1.03;1.4;1.23;1.14
other:Oth69_Souleater;1.5;1.17;1.4;1.26;1.35
other:Oth70_Lightbringer;1.6;1.47;1.4;1.29;1.61
other:Oth71_Titan_Strife;1.3;1.16;1.4;1.28;1.35
other:Oth72_Hazard_I;1.5;1.57;1.4;1.3;1.61
other:Oth73_Nemini;1.4;1.17;1.4;1.31;1.35
other:Oth74_Argonaut;1.1;1.05;1.4;1.06;1.18
other:Oth75_Braniaq_DM5;1.1;1.06;1.4;1.14;1.18
other:Oth76_Destiny;1.2;1.02;1.4;1.17;1.15
other:Oth77_Prowler;1.4;1.16;1.4;1.3;1.35
other:Oth78_AC_17_P_Sterope;1.2;1.02;1.4;1.13;1.12
other:Oth80_AC_16_Celeno;1.2;1.02;1.4;1.13;1.12
other:Oth81_AC_15_Taygete;1.4;1.24;1.4;1.33;1.35
other:Oth82_AC_14_Pleione;1.4;1.15;1.4;1.29;1.35
other:Oth83_AC_13_Merope;1.7;1.4;1.4;1.32;1.61
other:Oth84_FVE_Defender;1.1;1.12;1.4;1.16;1.15
other:Oth85_Ghoul;1.3;1.36;1.5;1.28;1.35
other:Oth86_Survivalist;1.1;1.06;1.4;1.1;1.12
other:Oth92_JagdPantherII_Titan;1.4;1.17;1.4;1.24;1.35
other:Oth93_Convoyer;1.4;1.16;1.4;1.24;1.35
other:Sega_Edelweiss;1.5;1.62;1.4;1.28;1.61
other:Sega_Nameless;1.7;1.41;1.4;1.29;1.62
other:Strv_74A2;1.9;1.8;1.5;1.54;1.9
other:Van_Helsing;1.8;1.59;1.4;1.25;1.61
other:WZ-135G_FT;1.8;1.5;1.4;1.3;1.61
uk:GB03_Cruiser_Mk_I;4.4;4.9;2.7;3.96;9.12
uk:GB04_Valentine;2.9;1.85;1.9;2.53;3.37
uk:GB05_Vickers_Medium_Mk_II;6.5;3.84;2.2;3.8;12.37
uk:GB06_Vickers_Medium_Mk_III;4.9;3.9;1.9;2.88;5.74
uk:GB07_Matilda;4.1;2.83;1.6;2.56;3.64
uk:GB08_Churchill_I;3.3;1.8;1.5;1.96;2.13
uk:GB09_Churchill_VII;2.1;1.7;1.4;1.54;1.76
uk:GB09_Churchill_VII_BP;2.1;1.67;1.4;1.54;1.79
uk:GB101_FV1066_Senlac;1.4;1.16;1.5;1.24;1.33
uk:GB106_Cobra;1.1;1.05;1.4;1.14;1.16
uk:GB108_A46;1.5;1.56;1.5;1.25;1.6
uk:GB109_GSOR_1008;1.6;1.16;1.4;1.25;1.34
uk:GB109_GSOR_1008_Fearless;1.6;1.18;1.4;1.24;1.34
uk:GB10_Black_Prince;1.7;1.47;1.4;1.23;1.62
uk:GB110_FV4201_Chieftain_Prototype;1.1;1.02;1.4;1.13;1.15
uk:GB111_Charlemagne;1.4;1.13;1.4;1.31;1.35
uk:GB112_Caliban;1.4;1.16;1.4;1.45;1.35
uk:GB114_Vickers_MBT_Mk3;1.3;1.28;1.5;1.12;1.14
uk:GB116_Harry_Hopkins;2.5;1.97;2.3;1.96;2.12
uk:GB11_Caernarvon;1.3;1.13;1.4;1.23;1.35
uk:GB11_Caernarvon_D;1.4;1.16;1.4;1.3;1.35
uk:GB128_Nemesis;1.3;1.29;1.5;1.28;1.35
uk:GB12_Conqueror;1.1;1.02;1.4;1.1;1.13
uk:GB19_Sherman_Firefly;1.9;1.49;1.5;1.55;2.01
uk:GB20_Crusader;2.7;1.83;2.3;1.96;2.11
uk:GB21_A27BP;1.9;1.7;1.5;1.55;1.94
uk:GB21_Cromwell;1.9;1.7;1.5;1.55;1.94
uk:GB22_Comet;1.5;1.58;1.4;1.25;1.61
uk:GB23_Centurion;1.3;1.12;1.5;1.25;1.35
uk:GB24_Centurion_Mk3;1.1;1.02;1.4;1.14;1.15
uk:GB32_Tortoise;1.1;1.05;1.4;1.05;1.11
uk:GB35_Sentinel_AC_IV;1.9;1.7;1.5;1.56;1.94
uk:GB39_Universal_CarrierQF2;5.5;4.89;2.2;3.99;11.07
uk:GB40_Gun_Carrier_BP;2;1.56;1.4;1.52;1.77
uk:GB40_Gun_Carrier_Churchill;2.1;1.48;1.4;1.51;1.79
uk:GB41_Challenger;1.8;1.35;1.4;1.26;1.61
uk:GB42_Valentine_AT;4.3;2.68;1.9;2.43;5.48
uk:GB44_Archer_Custom;3.2;1.76;1.5;1.98;2.08
uk:GB51_Excelsior;3.2;2.27;1.5;1.97;2.13
uk:GB52_FV201_A45;1.7;1.47;1.4;1.24;1.61
uk:GB57_Alecto;3.7;2.93;1.8;2.54;1
uk:GB58_Cruiser_Mk_III;4.4;4.9;2.5;4.26;6.52
uk:GB59_Cruiser_Mk_IV;3.7;3.82;2.2;3.05;5.52
uk:GB60_Covenanter;3;3;1.9;2.55;2.68
uk:GB63_TOG_II;1.9;1.53;1.4;1.54;1.8
uk:GB63_TOG_II_BP;1.9;1.53;1.4;1.57;1.79
uk:GB68_Matilda_Black_Prince;2.5;1.96;1.7;1.98;2.12
uk:GB69_Cruiser_Mk_II;5.9;5.88;3.3;5.32;8.2
uk:GB71_AT_15A;1.7;1.58;1.4;1.22;1.62
uk:GB72_AT15;1.4;1.19;1.4;1.19;1.35
uk:GB73_AT2;3.2;2.45;1.5;1.97;2.13
uk:GB74_AT8;2;1.6;1.4;1.51;1.76
uk:GB75_AT7;1.7;1.33;1.4;1.23;1.62
uk:GB76_Mk_VIC;4.3;5.58;2.7;4.01;9.12
uk:GB80_Charioteer;1.6;1.16;1.4;1.27;1.34
uk:GB81_FV4004;1.2;1.05;1.4;1.11;1.2
uk:GB85_Cromwell_Berlin;1.9;1.7;1.5;1.55;1.94
uk:GB87_Chieftain_T95_turret;1.4;1.17;1.4;1.25;1.35
uk:GB93_Caernarvon_AX;1.3;1.13;1.4;1.25;1.35
uk:GB94_Centurion_Mk5-1_RAAC;1.3;1.12;1.5;1.28;1.35
uk:GB97_Chimera;1.3;1.15;1.5;1.32;1.35
uk:GB99_Turtle_Mk1;1.4;1.18;1.4;1.22;1.35
uk:GB_Centurion_MkDF;1.3;1.12;1.5;1.29;1.35
uk:GB_Churchill_MkVI;2.1;1.73;1.4;1.54;1.77
uk:GB_Churchill_MkVIII;2.1;1.64;1.4;1.56;1.79
uk:GB_FV301;1.4;1.07;1.5;1.25;1.35
uk:GB_Sherman_V;2.6;2.28;1.7;1.96;2.11
uk:GB_Vickers_Cruiser;1.1;1;1.5;1.11;1.2
uk:Sentinel_AC1;3;2.88;1.9;2.55;3.47
usa:A05_M4_105BD;2.7;2.4;1.7;1.96;2.12
usa:A100_T49;1.4;1.53;1.5;1.29;1.34
usa:A102_T28_concept;1.7;1.47;1.4;1.27;1.62
usa:A104_M4A3E8A;1.9;1.88;1.5;1.54;1.84
usa:A111_T25_Pilot;1.3;1.27;1.5;1.26;1.35
usa:A115_Chrysler_K;1.4;1.16;1.4;1.3;1.35
usa:A117_T26E5;1.4;1.15;1.4;1.27;1.35
usa:A121_T26E3;1.5;1.54;1.4;1.31;1.61
usa:A122_TS-5;1.4;1.15;1.4;1.23;1.35
usa:A124_T54E2;1.4;1.15;1.4;1.29;1.35
usa:A125_AEP_1;1.1;1.02;1.4;1.15;1.14
usa:A130_Super_Hellcat;1.7;1.51;1.4;1.24;1.6
usa:A132_T77;1.3;1.15;1.4;1.29;1.35
usa:A136_T42;1.5;1.54;1.4;1.29;1.61
usa:A140_ASTRON_REX_105;1.3;1.31;1.5;1.31;1.35
usa:A141_M-4-Y;1.4;1.16;1.4;1.27;1.35
usa:A142_Pawlack_Tank;2.1;1.68;1.4;1.58;1.85
usa:A146_TL_7_120;1.1;1.02;1.4;1.14;1.14
usa:A150_MBT_B;1.2;1.02;1.4;1.21;1.13
usa:A151_TS_60;1.3;1.14;1.4;1.06;1.15
usa:A175_ATAC;1.1;1.02;1.5;1.11;1.32
usa:A68_T28D;1.4;1.2;1.4;1.28;1.35
usa:A77_T6M;3;2.72;1.9;2.52;3.24
usa:A94_T37;1.9;1.73;1.6;1.55;2.21
usa:A99_T92E1;1.1;1.1;1.5;1.2;1.2
usa:M-3-Y;1.4;1.14;1.4;1.28;1.35
usa:M-3-Y-Midas;1.4;1.14;1.4;1.28;1.35
usa:M-5-Y;1.1;1.02;1.4;1.2;1.14
usa:M-7-Y;1.7;1.32;1.4;1.32;1.61
usa:M103;1.1;1.02;1.4;1.14;1.13
usa:M10_Wolverine;3.2;2.18;1.5;1.96;2.13
usa:M18E2BP;1.9;1.65;1.5;1.57;2.02
usa:M18_Hellcat;2.1;1.58;1.4;1.51;2.99
usa:M22_Locust;3.8;4.23;2.1;3;5.59
usa:M24_Chaffee;1.9;1.73;1.6;1.55;2.36
usa:M24_Chaffee_BP;1.9;1.73;1.6;1.55;2.36
usa:M2_lt;5.5;5.28;3.3;6.76;7.39
usa:M2_med;3.6;4.29;2.2;2.93;5.54
usa:M36_BP;2.1;1.58;1.4;1.53;1.85
usa:M36_Slagger;2.1;1.58;1.4;1.54;1.85
usa:M3_Grant;3;2.38;1.9;2.55;3.13
usa:M3_Stuart;4.4;4.29;2.7;4.23;5.89
usa:M41_Bulldog;1.6;1.49;1.5;1.25;1.61
usa:M46_CL;1.1;1.04;1.4;1.08;1.17
usa:M46_Patton;1.1;1.08;1.4;1.09;1.17
usa:M4A2E4;2.6;2.28;1.7;1.96;2.13
usa:M4A3E8_BP;1.9;1.88;1.5;1.55;1.84
usa:M4A3E8_Sherman;1.9;1.88;1.5;1.55;1.84
usa:M4_Sherman;2.7;1.98;1.7;1.95;2.12
usa:M5_Stuart;3.7;3.48;2.1;2.94;5.57
usa:M6;2.1;1.6;1.4;1.58;1.8
usa:M6A2E1;1.6;1.32;1.4;1.33;1.62
usa:M6A2E1_exp;1.4;1.07;1.4;1.33;1.35
usa:M6E2BP;2.1;1.6;1.4;1.57;1.77
usa:M6E2V2_BP;2.1;1.64;1.4;1.53;1.78
usa:M7_med;3.1;2.38;1.9;2.54;2.98
usa:M8A1;4;2.73;1.8;2.54;2.87
usa:Pershing;1.3;1.33;1.5;1.25;1.35
usa:Ram-II;2.5;2.22;1.7;1.97;2.12
usa:Sherman_Jumbo;1.9;1.88;1.5;1.56;1.8
usa:T14;3.2;2.35;1.5;1.97;2.13
usa:T18;5.5;4.48;2.2;4.43;5.65
usa:T1_E6;4.4;5;2.7;4.12;8.54
usa:T1_hvy;3.3;2;1.5;1.96;2.13
usa:T20;1.5;1.54;1.4;1.28;1.61
usa:T21;1.9;1.81;1.6;1.57;2.27
usa:T21_BP;1.9;1.81;1.6;1.56;2.24
usa:T23E3;1.5;1.54;1.4;1.25;1.61
usa:T25_2;1.7;1.4;1.4;1.23;1.61
usa:T25_AT;1.7;1.44;1.4;1.26;1.61
usa:T26_E4_SuperPershing;1.3;1.2;1.5;1.29;1.35
usa:T28;1.4;1.16;1.4;1.23;1.35
usa:T28_Prototype;1.4;1.16;1.4;1.24;1.35
usa:T29;1.7;1.32;1.4;1.31;1.61
usa:T2_lt;4.3;5.45;2.7;3.9;8.26
usa:T2_med;4.6;5;2.5;4.23;7.11
usa:T30;1.1;1.05;1.4;1.12;1.17
usa:T32;1.4;1.16;1.4;1.29;1.35
usa:T34_hvy;1.3;1.07;1.4;1.3;1.35
usa:T34_hvy_Freedom;1.4;1.07;1.4;1.31;1.35
usa:T40;4;2.38;1.8;2.54;3.13
usa:T49;3.2;2.18;1.5;1.97;2.08
usa:T49_Fearless;1.4;1.53;1.5;1.28;1.34
usa:T54E1;1.1;1.06;1.4;1.1;1.16
usa:T69;1.3;1.33;1.5;1.27;1.35
usa:T71;1.6;1.49;1.5;1.27;1.6
usa:T7_Combat_Car;4.4;5.58;2.7;4.26;8.54
usa:T82;4.1;13.5;1.9;2.78;5.78
usa:T95;1.2;1.05;1.4;1.11;1.12
usa:T95E2;1.3;1.33;1.5;1.25;1.35
ussr:A-20;3.1;2.72;1.9;2.54;3
ussr:A-32;3;2.85;1.9;2.52;3.43
ussr:AT-1;5.5;3.99;2.2;3.99;8.51
ussr:BT-2;4.3;4.71;2.7;4.18;6.52
ussr:BT-7;3.6;2.98;2.1;3.06;5.51
ussr:BT-SV;3.8;2.86;2.1;3.16;5.58
ussr:Churchill_LL;3.2;2.27;1.5;1.97;2.13
ussr:GAZ-74b;4;2.48;1.8;2.53;3.05
ussr:IS;1.6;1.44;1.4;1.32;1.62
ussr:IS-2Sh;1.4;1.15;1.4;1.31;1.35
ussr:IS-3;1.4;1.16;1.4;1.3;1.35
ussr:IS8;1.2;1.02;1.4;1.12;1.15
ussr:ISU-152;1.6;1.13;1.4;1.38;1.35
ussr:K-91;1.1;1.02;1.4;1.1;1.12
ussr:KV-13;1.5;1.67;1.4;1.26;1.61
ussr:KV-1s;2.3;1.6;1.4;1.6;1.82
ussr:KV-1s-BP;2.3;1.5;1.4;1.64;1.83
ussr:KV-220;3.2;2.27;1.5;1.96;2.13
ussr:KV-3;1.7;1.44;1.4;1.33;1.62
ussr:KV-5;1.5;1.15;1.4;1.29;1.35
ussr:KV1;3.2;2.11;1.5;1.95;2.13
ussr:KV2;2.4;2.25;1.4;1.52;1.85
ussr:KV4;1.4;1.13;1.4;1.3;1.35
ussr:LTP;3.8;2.88;2.1;3.16;5.56
ussr:M3_Stuart_LL;3.7;5.04;2.1;2.86;5.51
ussr:MS-1obr21;4.3;4.71;2.7;4.18;6.98
ussr:MT25;2;1.79;1.6;1.56;2.06
ussr:Matilda_II_LL;2.5;2.22;1.7;1.96;2.12
ussr:Object252;1.4;1.14;1.4;1.33;1.35
ussr:Object252_Fearless;1.4;1.41;1.4;1.33;1.35
ussr:Object_704;1.1;1.05;1.4;1.1;1.13
ussr:R105_BT_7A;3.8;6.27;2.1;3.86;5.64
ussr:R107_LTB;1.6;1.56;1.5;1.28;1.61
ussr:R109_T54S;1.4;1.34;1.5;1.29;1.34
ussr:R111_ISU130;1.5;1.16;1.4;1.23;1.35
ussr:R112_T54_45;1.3;1.37;1.5;1.31;1.35
ussr:R113_Object_730;1.4;1.15;1.4;1.31;1.35
ussr:R115_IS-3_auto;1.4;1.15;1.4;1.3;1.35
ussr:R116_ISU122C_Berlin;1.8;1.45;1.4;1.22;1.62
ussr:R127_T44_100_P;1.3;1.33;1.5;1.27;1.35
ussr:R131_LTG;1.6;1.61;1.5;1.28;1.61
ussr:R134_Object_252U;1.4;1.14;1.4;1.33;1.35
ussr:R140_M4A2_Loza;1.9;1.88;1.5;1.55;1.84
ussr:R146_STG;1.3;1.33;1.5;1.33;1.35
ussr:R154_T-34E-43;2.6;2.22;1.7;1.96;2.11
ussr:R158_LT_432;1.4;1.34;1.5;1.28;1.34
ussr:R159_SU_130PM;1.6;1.16;1.4;1.26;1.34
ussr:R160_object_244;2.1;1.71;1.4;1.55;1.82
ussr:R172_Object_752;1.1;1.02;1.4;1.13;1.13
ussr:R175_IS_2E;1.6;1.47;1.4;1.31;1.61
ussr:R180_Object_274A;1.3;1.31;1.5;1.3;1.35
ussr:R181_Object_452K;1.2;1.02;1.4;1.15;1.13
ussr:R187_Object_590;1.3;1.32;1.5;1.29;1.35
ussr:R192_K_2;1.4;1.12;1.4;1.33;1.35
ussr:R194_ST_62_var_2;1.3;1.31;1.5;1.28;1.35
ussr:R217_Object_Kust;1.5;1.16;1.4;1.27;1.35
ussr:R43_T-70_57;3.8;3.41;2.1;3.43;5.49
ussr:R63_ST_IBD;1.2;1.02;1.4;1.17;1.14
ussr:R71_IS_2B;1.6;1.44;1.4;1.33;1.61
ussr:R98_T44_85;1.5;1.64;1.4;1.26;1.61
ussr:R_Object_84;1.1;1.07;1.5;1.12;1.17
ussr:ST_I;1.2;1.02;1.4;1.16;1.13
ussr:SU-100;2.1;1.57;1.4;1.55;1.81
ussr:SU-101;1.5;1.15;1.4;1.26;1.35
ussr:SU-152;1.8;2;1.4;1.38;1.61
ussr:SU-76;4.2;3.6;1.9;3.18;5.56
ussr:SU-85;3.2;1.99;1.5;1.97;2.12
ussr:SU100M1;1.7;1.35;1.4;1.25;1.61
ussr:SU100Y;2.1;1.53;1.4;1.57;1.89
ussr:SU122_44;1.8;1.45;1.4;1.25;1.61
ussr:SU122_54;1.2;1.05;1.4;1.03;1.14
ussr:SU76I;4.1;3.31;1.9;3.18;5.47
ussr:SU_85I;3.1;2.3;1.5;1.98;2.12
ussr:T-127;3.8;2.76;2.1;3.16;5.52
ussr:T-26;5.5;5.07;3.3;6.16;8.2
ussr:T-28;3;2.85;1.9;2.54;3.09
ussr:T-34;2.7;2.01;1.7;1.96;2.12
ussr:T-34-85;1.9;1.71;1.5;1.58;1.87
ussr:T-34-85_Rudy;1.5;1.67;1.4;1.26;1.61
ussr:T-34-85_Victory;1.9;1.88;1.5;1.57;1.87
ussr:T-43;1.5;1.6;1.4;1.25;1.61
ussr:T-44;1.3;1.33;1.5;1.34;1.35
ussr:T-46;3.8;4.32;2.1;2.83;5.6
ussr:T-54;1.1;1.1;1.4;1.09;1.16
ussr:T150;2.1;1.55;1.4;1.59;1.82
ussr:T34_85BP;1.9;1.88;1.5;1.57;1.87
ussr:T34_85TUR;1.5;1.58;1.4;1.29;1.62
ussr:Tetrarch_LL;4.3;3.88;2.7;3.88;8.51
ussr:Valentine_LL;2.9;4.55;1.9;2.55;3.42
1 name maxHealthFactor piercingPowerFactor moduleHealthFactor damageFactor armorFactor
2 china:Ch01_Type59 1.3 1.33 1.5 1.29 1.35
3 china:Ch02_Type62 1.5 1.6 1.5 1.27 1.61
4 china:Ch03_WZ-111 1.4 1.17 1.4 1.3 1.35
5 china:Ch04_T34_1 1.5 1.54 1.4 1.3 1.61
6 china:Ch05_T34_2 1.3 1.33 1.5 1.29 1.34
7 china:Ch07_Vickers_MkE_Type_BT26 5.5 5.07 3.3 7.05 8.8
8 china:Ch08_Type97_Chi_Ha 3.7 3.71 2.2 3.13 5.56
9 china:Ch09_M5 2.9 2.91 1.9 2.54 3.47
10 china:Ch10_IS2 1.6 1.44 1.4 1.33 1.61
11 china:Ch11_110 1.4 1.17 1.4 1.29 1.35
12 china:Ch12_111_1_2_3 1.2 1.02 1.4 1.17 1.14
13 china:Ch14_T34_3 1.3 1.37 1.5 1.13 1.17
14 china:Ch15_59_16 1.9 1.77 1.6 1.57 2.24
15 china:Ch15_59_16_BP 1.9 1.7 1.6 1.56 2.24
16 china:Ch16_WZ_131 1.6 1.52 1.5 1.28 1.61
17 china:Ch17_WZ131_1_WZ132 1.4 1.22 1.5 1.29 1.34
18 china:Ch18_WZ-120 1.1 1.09 1.4 1.11 1.17
19 china:Ch20_Type58 1.9 1.88 1.5 1.57 1.86
20 china:Ch21_T34 2.7 2.1 1.7 1.97 2.12
21 china:Ch23_112 1.4 1.17 1.4 1.31 1.35
22 china:Ch24_Type64 1.9 1.86 1.6 1.57 2.35
23 china:Ch26_59_Patton 1.3 1.33 1.5 1.1 1.16
24 china:Ch28_WZ_132A 1.1 1.04 1.5 1.11 1.24
25 china:Ch34_WZ-131G_FT 2.1 1.57 1.4 1.51 1.73
26 china:Ch34_WZ-131G_FT_BP 2.1 1.57 1.4 1.51 2.02
27 china:Ch35_T-34-2G_FT 1.8 1.48 1.4 1.25 1.61
28 china:Ch36_WZ-111-1G_FT 1.6 1.16 1.4 1.25 1.35
29 china:Ch37_WZ-111G_FT 1.2 1.05 1.4 1.15 1.13
30 china:Ch39_WZ120_1G_FT 1.6 1.16 1.4 1.24 1.35
31 china:Ch40_WZ120G_FT 1.1 1.05 1.4 1.08 1.13
32 china:Ch42_M41D 1.7 1.49 1.5 1.24 1.61
33 china:Ch43_WZ-122_2 1.3 1.21 1.5 1.31 1.35
34 china:Ch45_WZ_114 1.2 1.02 1.4 1.19 1.13
35 china:Ch47_BZ_176 1.2 1.03 1.4 1.17 1.13
36 china:Ch49_BZ_68 1.2 1.02 1.4 1.2 1.16
37 china:Ch50_BZ_166 1.4 1.17 1.4 1.3 1.35
38 china:Ch51_BZ_58 1.6 1.46 1.4 1.29 1.62
39 china:Ch56_BZ_74_1 1.2 1.02 1.4 1.16 1.14
40 china:Ch59_BZ58_2 1.2 1.02 1.4 1.19 1.13
41 china:Ch60_Type59_spc 1.3 1.37 1.5 1.32 1.35
42 china:Ch_LT_vz38 4.3 4.29 2.7 3.99 4.88
43 china:Ch_WZ-112v2 1.4 1.17 1.4 1.31 1.35
44 china:Type_58BP 1.9 1.88 1.5 1.57 2.04
45 european:Cz02_T50 1.1 1.03 1.4 1.12 1.19
46 european:Cz05_T34-100 1.5 1.45 1.4 1.29 1.61
47 european:Cz07_TVP_46 1.3 1.12 1.5 1.29 1.35
48 european:Cz08_T-25 1.9 1.83 1.5 1.56 2.02
49 european:Cz08_T-25BP 1.9 1.83 1.5 1.55 2.02
50 european:Cz08_T-25BPS 1.9 1.83 1.5 1.57 2.02
51 european:Cz13_T-27 1.1 1.07 1.4 1.12 1.19
52 european:Cz14_Skoda_T-56 1.4 1.17 1.4 1.33 1.35
53 european:Cz15_Skoda_T-45 1.7 1.44 1.4 1.27 1.61
54 european:Cz16_TNH_T_vz51 1.2 1.02 1.4 1.17 1.15
55 european:Cz18_TNH_105_1000 1.4 1.16 1.4 1.28 1.35
56 european:Cz19_Vz_44_1 1.7 1.43 1.4 1.3 1.61
57 european:Cz20_ShPTK_TVP_100 1.5 1.16 1.4 1.24 1.34
58 european:Cz29_Skoda_P-JS 2.2 1.67 1.4 1.56 1.78
59 european:Cz29_Skoda_P-JS_BP 2.2 1.67 1.4 1.57 1.79
60 european:F01_Vickers_MkF 5.5 5.07 3.3 6.16 8.8
61 european:It09_P43_ter 1.5 1.54 1.4 1.3 1.61
62 european:It10_P43_BD 1.9 1.74 1.5 1.57 1.82
63 european:It10_P43_bis 1.9 1.74 1.5 1.57 1.82
64 european:It10_P43_bis_BP 1.9 1.7 1.5 1.58 1.82
65 european:It116_Carro_d_assalto_P88 1.6 1.33 1.4 1.28 1.61
66 european:It117_ProgettoCC55mod54 1.3 1.13 1.4 1.29 1.35
67 european:It119_ProgettoC50mod66 1.1 1.02 1.4 1.18 1.13
68 european:It12_Prototipo_Standard_B 1.1 1.04 1.4 1.14 1.17
69 european:It13_Progetto_M35_mod_46 1.3 1.33 1.5 1.27 1.35
70 european:It14_P44_Pantera 1.3 1.21 1.5 1.26 1.35
71 european:It15_Semovente_M41 3.2 1.98 1.5 1.97 2.13
72 european:It16_Bassoto 2 1.59 1.4 1.53 1.8
73 european:It17_Semovente_mod_56 1.7 1.4 1.4 1.28 1.62
74 european:It18_Semovente_mod_64 1.4 1.2 1.4 1.24 1.35
75 european:It19_Controcarro_1MK2_Ciclope 1.1 1.05 1.4 1.19 1.13
76 european:It22_Progetto_C45_mod_71 1.3 1.14 1.4 1.23 1.35
77 european:It30_CC_mod_64_Prem 1.4 1.17 1.4 1.25 1.35
78 european:Pl03_PzV_Pudel 1.9 1.57 1.5 1.56 1.92
79 european:Pl05_50TP_Tyszkiewicza 1.2 1.02 1.4 1.18 1.13
80 european:Pl06_10TP 4.3 4.29 2.7 4.28 7.66
81 european:Pl07_14TP 3.6 3.57 2.1 3.12 5.56
82 european:Pl08_50TP_prot 1.2 1.02 1.4 1.17 1.14
83 european:Pl10_40TP_Habicha 2.1 1.64 1.4 1.57 1.83
84 european:Pl119_CS_52_LIS 1.5 1.56 1.4 1.31 1.61
85 european:Pl11_45TP_Habicha 1.6 1.44 1.4 1.3 1.61
86 european:Pl13_53TP_Markowskiego 1.4 1.14 1.4 1.32 1.35
87 european:Pl17_DS_PZlnz 2.6 2.28 1.7 1.95 2.12
88 european:Pl18_BUGI 1.9 1.57 1.5 1.58 1.94
89 european:Pl20_CS_44 1.5 1.54 1.4 1.3 1.61
90 european:Pl22_CS_59 1.1 1.04 1.4 1.1 1.21
91 european:Pl23_CS_53 1.3 1.31 1.5 1.29 1.35
92 european:Pl24_56TP 1.4 1.15 1.4 1.29 1.35
93 european:Pl27_SDP_Wz_66T 1.5 1.17 1.4 1.27 1.35
94 european:Pl28_SDP_Wz_60 1.1 1.05 1.4 1.11 1.12
95 european:Pl30_SDP_Wz58T 1.4 1.16 1.4 1.26 1.35
96 european:Pl31_SDP_Wz_57D 1.7 1.43 1.4 1.28 1.62
97 european:Pl32_SDP_Wz_44 2 1.57 1.4 1.52 1.76
98 european:Pl33_SDP_Wz_40 3.2 2.13 1.5 1.96 2.13
99 european:S02_StrvM42 2.7 2.22 1.7 1.95 2.11
100 european:S04_Lago-I 3.1 2.69 1.9 2.54 2.32
101 european:S07_Strv74BP 1.9 1.67 1.5 1.55 1.9
102 european:S07_Strv_74 1.9 1.67 1.5 1.55 1.9
103 european:S13_Leo 1.5 1.58 1.4 1.3 1.61
104 european:S13_Leo_NY 1.5 1.58 1.4 1.3 1.61
105 european:S17_EMIL_1952E2 1.2 1.02 1.4 1.16 1.12
106 european:S18_EMIL_1951E1 1.3 1.16 1.4 1.29 1.35
107 european:S23_Strv_81 1.3 1.14 1.5 1.26 1.35
108 european:S25_EMIL_51 1.3 1.12 1.4 1.29 1.35
109 european:S26_Lancen_C 1.3 1.22 1.5 1.31 1.35
110 european:S32_Bofors_Tornvagn 1.1 1.02 1.4 1.17 1.12
111 france:AMX38 4.4 4.29 2.7 4.15 5.95
112 france:AMX40 3 2.88 1.9 2.52 3.53
113 france:AMX50_Foch 1.2 1.05 1.4 1.11 1.14
114 france:AMX_12t 1.9 1.64 1.6 1.6 2.02
115 france:AMX_13_75 1.7 1.61 1.5 1.25 1.61
116 france:AMX_13_90 1.4 1.37 1.5 1.29 1.34
117 france:AMX_50_100 1.4 1.12 1.4 1.26 1.35
118 france:AMX_50_120 1.2 1.02 1.4 1.12 1.17
119 france:AMX_AC_Mle1946 1.7 1.45 1.4 1.24 1.61
120 france:AMX_AC_Mle1948 1.5 1.16 1.4 1.24 1.35
121 france:AMX_M4_1945 1.6 1.47 1.4 1.28 1.61
122 france:ARL_44 2.1 1.57 1.4 1.53 1.82
123 france:ARL_44BP 2.1 1.53 1.4 1.58 1.81
124 france:ARL_V39 2 1.59 1.4 1.52 1.82
125 france:Achilles_FR 2.1 1.6 1.4 1.52 1.83
126 france:B1 4.1 2.8 1.6 2.54 3.64
127 france:BDR_G1B 3.2 1.91 1.5 1.95 2.13
128 france:Bat-Chat-25CL 1.1 1.05 1.5 1.1 1.22
129 france:D1 4.3 4.62 2.7 3.8 6.52
130 france:D2 3.7 3.64 2.2 3 5.52
131 france:ELC_AMX 2.8 1.97 2.3 2.31 2.09
132 france:F113_PzV_FR 1.9 1.65 1.5 1.56 1.89
133 france:F114_Projet_4_1 1.1 1.03 1.4 1.09 1.22
134 france:F115_Lorraine_50t 1.2 1.02 1.4 1.13 1.16
135 france:F116_Bat_Chat_Bourrasque 1.3 1.28 1.5 1.31 1.34
136 france:F117_Alt_Proto_AMX_30 1.3 1.15 1.5 1.29 1.35
137 france:F11_Renault_G1R 3.2 2 1.5 1.96 2.12
138 france:F120_Project_Louis 1.1 1.04 1.4 1.12 1.14
139 france:F123_ERAC_105_Proto 1.3 1.22 1.5 1.3 1.35
140 france:F126_AMX_13_M24 1.5 1.58 1.4 1.32 1.62
141 france:F127_ELC_AMX_901_Proto 1.4 1.28 1.5 1.28 1.34
142 france:F140_Sentinelle 1.4 1.15 1.4 1.28 1.35
143 france:F19_Lorraine40t 1.3 1.17 1.5 1.3 1.34
144 france:F19_Lorraine40t_Com 1.3 1.17 1.5 1.29 1.34
145 france:F34_ARL_V39_BP 2.1 1.59 1.4 1.51 1.82
146 france:F68_AMX_Chasseur_de_char_46 1.3 1.17 1.5 1.25 1.33
147 france:F69_AMX_13D 1.4 1.16 1.5 1.28 1.34
148 france:F69_AMX_13_57GF 1.6 1.49 1.5 1.26 1.61
149 france:F71_AMX_30_prototype 1.1 1.06 1.4 1.12 1.19
150 france:F73_M4A1_Revalorise 1.3 1.23 1.5 1.27 1.34
151 france:F74_AMX_M4_1949 1.4 1.12 1.4 1.28 1.35
152 france:F75_Char_de_25t 1.1 1.02 1.5 1.11 1.22
153 france:F84_Somua_SM 1.4 1.12 1.4 1.14 1.14
154 france:F89_Canon_dassaut_105 1.6 1.15 1.4 1.26 1.35
155 france:FCM_36Pak40 4.2 2.95 1.9 3.18 5.52
156 france:FCM_50t 1.3 1.17 1.5 1.25 1.35
157 france:F_AMX-12BP 1.9 1.64 1.6 1.6 2.02
158 france:RenaultFT_AC 5.5 4.37 2.2 4.49 8.51
159 france:RenaultR35 5.5 5.28 3.3 6.76 7.8
160 france:RenaultUE57 4.2 2.79 1.9 2.68 5.95
161 france:S_35CA 3.2 2.07 1.5 1.96 2.11
162 france:Somua_Sau_40 3.7 2.75 1.8 2.54 3.62
163 germany:B-1bis_captured 4.1 2.96 1.6 2.55 3.89
164 germany:DW_II 3.7 2.52 1.6 2.52 3.53
165 germany:DickerMax 2.1 1.57 1.4 1.53 2.02
166 germany:E-25 1.8 1.46 1.4 1.24 1.61
167 germany:E-50 1.1 1.09 1.4 1.1 1.15
168 germany:E-75 1.2 1.02 1.4 1.17 1.14
169 germany:Ferdinand 1.4 1.16 1.4 1.25 1.35
170 germany:G100_Gtraktor_Krupp 4.9 3.31 1.9 3.49 5.75
171 germany:G103_RU_251 1.4 1.3 1.5 1.25 1.33
172 germany:G106_PzKpfwPanther_AusfF 1.3 1.21 1.5 1.26 1.35
173 germany:G109_Steyr_WT 1.8 1.37 1.4 1.27 1.6
174 germany:G110_Mauschen 1.2 1.02 1.4 1.18 1.12
175 germany:G112_JagdPanzer_105 1.2 1.05 1.4 1.04 1.18
176 germany:G113_SP_I_C 1.7 1.49 1.5 1.28 1.58
177 germany:G114_Rheinmetall_Scorpion 1.6 1.16 1.4 1.24 1.33
178 germany:G115_VK10001P 1.4 1.15 1.4 1.33 1.35
179 germany:G118_VK4503 1.7 1.3 1.4 1.27 1.61
180 germany:G119_Panzer58_SH 1.3 1.22 1.5 1.28 1.35
181 germany:G119_Pz58_Mutz 1.3 1.17 1.5 1.26 1.35
182 germany:G120_M41_90 1.4 1.29 1.5 1.26 1.34
183 germany:G126_HWK_12 1.1 1.05 1.5 1.08 1.29
184 germany:G136_Tiger_131 2.1 1.68 1.4 1.55 1.85
185 germany:G138_VK16801P 1.5 1.15 1.4 1.16 1.13
186 germany:G140_HWK_30 1.4 1.27 1.5 1.27 1.34
187 germany:G142_M48RPz 1.3 1.18 1.5 1.27 1.35
188 germany:G147_mPanzer_64 1.7 1.49 1.5 1.29 1.61
189 germany:G148_Elephant 1.4 1.16 1.4 1.21 1.35
190 germany:G154_Kpz_07_RH 1.3 1.15 1.5 1.26 1.35
191 germany:G162_Project_Kpz_07P_E 1.1 1.02 1.4 1.16 1.14
192 germany:G164_Kpz_Pr_68_P 1.3 1.22 1.5 1.3 1.34
193 germany:G165_Erich_Koncept 1.1 1.06 1.4 1.14 1.19
194 germany:G166_LKpz_70_K 1.1 1.02 1.5 1.11 1.32
195 germany:G167_Tiger_Maus 1.2 1.03 1.4 1.11 1.13
196 germany:G168_KJpz_T_III 1.2 1.05 1.4 1.05 1.29
197 germany:G190_VK_1602_Quby 1.4 1.36 1.5 1.25 1.34
198 germany:G20_Marder_II 4.2 3.01 1.9 3.18 5.54
199 germany:G85_Auf_Panther 1.5 1.49 1.5 1.25 1.61
200 germany:G_E-75RT 1.4 1.14 1.4 1.3 1.35
201 germany:G_E-75TS 1.2 1.03 1.4 1.13 1.13
202 germany:G_VK3502_BP 2.1 1.68 1.4 1.57 1.79
203 germany:H39_captured 4.3 5.58 2.7 3.99 6.04
204 germany:Hetzer 3.7 2.6 1.8 2.54 3.47
205 germany:Indien_Panzer 1.3 1.17 1.5 1.25 1.35
206 germany:JagdPanther 1.8 1.37 1.4 1.25 1.62
207 germany:JagdPantherII 1.6 1.16 1.4 1.23 1.35
208 germany:JagdPzIV 2.1 1.58 1.4 1.49 1.8
209 germany:JagdPzIV_AusfBP 2 1.58 1.4 1.49 1.8
210 germany:JagdTiger 1.1 1.05 1.4 1.04 1.12
211 germany:JagdTiger_SdKfz_185 1.4 1.17 1.4 1.21 1.35
212 germany:JagdTiger_SdKfz_185_Snowstorm 1.4 1.33 1.4 1.21 1.35
213 germany:Krupp-38D 2.6 2.22 1.7 1.95 2.12
214 germany:Lowe 1.4 1.11 1.4 1.29 1.35
215 germany:Nashorn 2 1.51 1.4 1.51 2.24
216 germany:Nashorn_BP 2 1.51 1.4 1.5 2.24
217 germany:Panther_II 1.3 1.21 1.5 1.26 1.35
218 germany:Panther_M10 1.5 1.54 1.4 1.26 1.61
219 germany:PanzerJager_I 5.5 4.33 2.2 4.49 8.8
220 germany:Pro_Ag_A 1.1 1.02 1.4 1.1 1.21
221 germany:Pz35t 4.4 4.9 2.7 3.94 6.05
222 germany:Pz38_NA 2.9 2.78 1.9 2.55 3.65
223 germany:Pz38t 3.6 3.88 2.1 3.05 5.58
224 germany:PzII 5.5 5.28 3.3 6.67 6.81
225 germany:PzIII 3.7 3.69 2.2 3.26 5.49
226 germany:PzIII_A 3.7 3.09 2.1 2.98 5.58
227 germany:PzIII_IV 2.7 2.22 1.7 1.95 2.12
228 germany:PzII_J 3.6 5.5 2.1 2.83 5.46
229 germany:PzII_Luchs 2.9 2.94 1.9 2.55 2.2
230 germany:PzIV 2.7 2.22 1.7 1.95 2.13
231 germany:PzIV_Hydro 2.5 2.22 1.7 1.95 2.11
232 germany:PzIV_schmalturm 1.9 1.65 1.5 1.53 1.87
233 germany:PzV 1.5 1.33 1.4 1.26 1.61
234 germany:PzVI 1.7 1.3 1.4 1.28 1.62
235 germany:PzVIB_Tiger_II 1.4 1.14 1.4 1.27 1.35
236 germany:PzVI_AusfBP 2.1 1.68 1.4 1.56 1.79
237 germany:PzVI_Tiger_P 1.7 1.3 1.4 1.28 1.62
238 germany:PzV_PzIV 2.6 2.22 1.7 1.95 2.12
239 germany:Pz_II_AusfG 3.8 2.9 2.1 2.85 5.55
240 germany:Pz_IV_AusfA 3.6 3.2 2.2 4.05 5.74
241 germany:Pz_IV_AusfBT 2.7 2.53 1.7 1.96 2.12
242 germany:Pz_IV_AusfD 3.1 2.43 1.9 2.54 3.09
243 germany:Pz_Sfl_IVc 3 1.61 1.5 1.98 2.08
244 germany:RhB_Waffentrager 1.6 1.26 1.4 1.28 1.33
245 germany:S01_Frankentank 1.7 1.33 1.4 1.37 1.62
246 germany:S35_captured 3.8 2.85 2.1 2.93 5.52
247 germany:StuGIII 3.2 1.93 1.5 1.97 2.12
248 germany:Sturer_Emil 1.8 1.31 1.4 1.28 1.61
249 germany:T-15 3.8 4.72 2.1 2.82 5.55
250 germany:T-25 2.5 2.13 1.7 1.95 2.11
251 germany:VK1602 2.8 2.9 2.3 1.96 2.11
252 germany:VK2801 1.9 1.53 1.6 1.58 1.95
253 germany:VK2801_BP 1.9 1.53 1.6 1.59 1.95
254 germany:VK3001H 3.2 1.75 1.5 1.96 2.13
255 germany:VK3001P 1.9 1.61 1.5 1.57 1.81
256 germany:VK3002DB 1.5 1.58 1.4 1.28 1.61
257 germany:VK3002DB_BP 1.9 1.65 1.5 1.56 1.87
258 germany:VK3002DB_V1 1.9 1.65 1.5 1.56 1.84
259 germany:VK3002M 1.9 1.65 1.5 1.57 1.91
260 germany:VK3601H 2.1 1.64 1.4 1.57 1.78
261 germany:VK4502A 1.3 1.17 1.4 1.29 1.35
262 germany:VK4502P 1.2 1.02 1.4 1.18 1.13
263 germany:Waffentrager_IV 1.2 1.06 1.4 1.12 1.19
264 germany:Waffentrager_IV_CL 1.2 1.05 1.4 1.06 1.19
265 japan:Chi_Ha 3.6 3.8 2.2 3.2 5.54
266 japan:Chi_He 3 2.72 1.9 2.55 3.24
267 japan:Chi_Ni 4.4 4.62 2.5 4.09 6.52
268 japan:Chi_Nu 2.6 2.02 1.7 1.94 2.12
269 japan:Chi_Nu_Kai 2.6 2.02 1.7 1.95 2.11
270 japan:Chi_Ri 1.5 1.58 1.4 1.25 1.61
271 japan:Chi_To 1.9 1.61 1.5 1.55 1.9
272 japan:Firefly_GuP 1.9 1.49 1.5 1.55 2.01
273 japan:G09_Hetzer_GuP 3.7 2.6 1.8 2.54 3.47
274 japan:Ha_Go 5.7 5.36 3.3 5.99 8.2
275 japan:IS-2_GuP 1.6 1.44 1.4 1.33 1.61
276 japan:J21_Ho_Ri 1.2 1.05 1.4 1.11 1.13
277 japan:J22_Ho_Ri_type96 1.5 1.16 1.4 1.25 1.35
278 japan:J23_Chi_To_SPG 1.8 1.37 1.4 1.25 1.62
279 japan:J24_Type_57 1.4 1.16 1.4 1.33 1.35
280 japan:J26_Type_68 1.2 1.02 1.4 1.13 1.13
281 japan:J27_Chi_Se 1.4 1.15 1.4 1.27 1.35
282 japan:J28_Ju_To 1.7 1.41 1.4 1.32 1.62
283 japan:J29_Ju_Nu 2.1 1.73 1.4 1.57 1.8
284 japan:J29_Ju_Nu_BP 2.1 1.73 1.4 1.57 1.8
285 japan:J30_Mitsu_108 3.2 2.37 1.5 1.97 2.13
286 japan:J31_Type_5_Ka_Ri 1.5 1.16 1.4 1.26 1.35
287 japan:J36_Type_63_HT 1.4 1.15 1.4 1.29 1.35
288 japan:J38_Type_95_Ji_Ro 1.5 1.16 1.4 1.22 1.35
289 japan:Ke_Ho 2.9 2.71 1.9 2.55 1
290 japan:Ke_Ni 3.8 4.07 2.1 2.67 5.71
291 japan:Ke_Ni_B 4.5 4.85 2.1 2.14 5.71
292 japan:PzVI_GuP 2.1 1.64 1.4 1.54 1.81
293 japan:Pz_IV_AusfH 2.6 2.22 1.7 1.95 2.12
294 japan:STA_1 1.3 1.15 1.5 1.28 1.34
295 japan:Type_61 1.1 1.02 1.4 1.1 1.24
296 other:AMX_dracula 1.5 1.58 1.4 1.3 1.61
297 other:Akawara 1.3 1.28 1.5 1.28 1.35
298 other:Oth06_Sega_Lupus 1.7 1.44 1.4 1.34 1.62
299 other:Oth08_WH_Vindicator 1.8 1.74 1.4 1.24 1.61
300 other:Oth09_WH_Predator 1.7 1.47 1.4 1.25 1.62
301 other:Oth11_T-55A 1.1 1.1 1.4 1.09 1.16
302 other:Oth12_Sega_Hafen 1.6 1.66 1.4 1.27 1.61
303 other:Oth14_MdM_AshKnight 1.7 1.47 1.4 1.27 1.62
304 other:Oth15_RustCrab 2.6 2.22 1.7 1.96 2.12
305 other:Oth16_KV_TG 1.6 1.76 1.4 1.27 1.61
306 other:Oth17_BDT-5A 2.6 2.22 1.7 1.95 2.12
307 other:Oth18_BDT-5B 1.9 1.85 1.5 1.55 1.94
308 other:Oth19_BDT-5C 1.5 1.66 1.4 1.25 1.61
309 other:Oth20_M4FL10 1.5 1.67 1.4 1.27 1.61
310 other:Oth22_Sega_Vulcan 1.7 1.41 1.4 1.33 1.62
311 other:Oth23_Werewolf 1.6 1.44 1.4 1.33 1.61
312 other:Oth24_StuH_42HW 3.2 2.6 1.5 1.97 2.13
313 other:Oth25_HWT20_FalconFire 1.7 1.47 1.4 1.25 1.62
314 other:Oth26_StrvM42HW 2.7 2.22 1.7 1.96 2.11
315 other:Oth27_M24_AMX 1.9 1.64 1.6 1.6 2.02
316 other:Oth28_Sturmfeur_HW 1.7 1.41 1.4 1.31 1.61
317 other:Oth29_A15HW 2.8 1.83 2.3 1.96 2.12
318 other:Oth30_T54MS 1.3 1.2 1.5 1.3 1.35
319 other:Oth31_T150BP 2.1 1.71 1.4 1.56 1.79
320 other:Oth33_Blizzard_BP 2 1.68 1.6 1.56 2.21
321 other:Oth34_PanzerMummy 1.7 1.39 1.4 1.3 1.61
322 other:Oth35_PanzerMejai 3.2 2.27 1.5 1.95 2.13
323 other:Oth36_Indien_Panzer_S1 1.5 1.56 1.4 1.28 1.61
324 other:Oth37_T26_E4_SuperPershing_S1 1.3 1.25 1.5 1.3 1.35
325 other:Oth38_50TP_Tyszkiewicza_S1 1.1 1.02 1.4 1.17 1.14
326 other:Oth39_T20_S2 1.5 1.62 1.4 1.28 1.61
327 other:Oth40_TVP_46_S2 1.3 1.15 1.5 1.28 1.35
328 other:Oth41_Centurion_Mk3_S2 1.1 1.05 1.4 1.11 1.18
329 other:Oth42_ForestWitch_HW23 1.7 1.42 1.4 1.32 1.61
330 other:Oth44_Charioteer_T 1.6 1.17 1.4 1.27 1.35
331 other:Oth45_Sandstriker 1.4 1.16 1.4 1.33 1.35
332 other:Oth46_Charioteer_S3 1.8 1.37 1.4 1.27 1.61
333 other:Oth47_AMX_13_90_S3 1.4 1.3 1.5 1.28 1.34
334 other:Oth48_EMIL_1951E1_S3 1.1 1.02 1.4 1.15 1.14
335 other:Oth49_TM_04_NT 1.5 1.63 1.4 1.34 1.61
336 other:Oth50_STT 1.6 1.55 1.5 1.28 1.61
337 other:Oth51_FD_24 1.3 1.18 1.5 1.28 1.35
338 other:Oth52_Toxique 1.3 1.32 1.5 1.31 1.35
339 other:Oth53_Rammer 1.6 1.46 1.4 1.31 1.61
340 other:Oth61_Pz_VM_Set 4.1 2.58 1.6 2.54 3.71
341 other:Oth62_Pz_VlD_Set 3.2 1.97 1.5 1.96 2.13
342 other:Oth63_Pz_VllF_Set 2.1 1.64 1.4 1.57 1.84
343 other:Oth64_Pz_VlllK_Set 1.6 1.44 1.4 1.31 1.61
344 other:Oth65_PzVIllM_Tigeom 2.1 1.67 1.4 1.59 1.86
345 other:Oth66_E100ANN_S4 1.7 1.47 1.4 1.33 1.62
346 other:Oth67_FV215b_183ANN_S4 1.5 1.16 1.4 1.24 1.35
347 other:Oth68_KV2ANN_S4 1.2 1.03 1.4 1.23 1.14
348 other:Oth69_Souleater 1.5 1.17 1.4 1.26 1.35
349 other:Oth70_Lightbringer 1.6 1.47 1.4 1.29 1.61
350 other:Oth71_Titan_Strife 1.3 1.16 1.4 1.28 1.35
351 other:Oth72_Hazard_I 1.5 1.57 1.4 1.3 1.61
352 other:Oth73_Nemini 1.4 1.17 1.4 1.31 1.35
353 other:Oth74_Argonaut 1.1 1.05 1.4 1.06 1.18
354 other:Oth75_Braniaq_DM5 1.1 1.06 1.4 1.14 1.18
355 other:Oth76_Destiny 1.2 1.02 1.4 1.17 1.15
356 other:Oth77_Prowler 1.4 1.16 1.4 1.3 1.35
357 other:Oth78_AC_17_P_Sterope 1.2 1.02 1.4 1.13 1.12
358 other:Oth80_AC_16_Celeno 1.2 1.02 1.4 1.13 1.12
359 other:Oth81_AC_15_Taygete 1.4 1.24 1.4 1.33 1.35
360 other:Oth82_AC_14_Pleione 1.4 1.15 1.4 1.29 1.35
361 other:Oth83_AC_13_Merope 1.7 1.4 1.4 1.32 1.61
362 other:Oth84_FVE_Defender 1.1 1.12 1.4 1.16 1.15
363 other:Oth85_Ghoul 1.3 1.36 1.5 1.28 1.35
364 other:Oth86_Survivalist 1.1 1.06 1.4 1.1 1.12
365 other:Oth92_JagdPantherII_Titan 1.4 1.17 1.4 1.24 1.35
366 other:Oth93_Convoyer 1.4 1.16 1.4 1.24 1.35
367 other:Sega_Edelweiss 1.5 1.62 1.4 1.28 1.61
368 other:Sega_Nameless 1.7 1.41 1.4 1.29 1.62
369 other:Strv_74A2 1.9 1.8 1.5 1.54 1.9
370 other:Van_Helsing 1.8 1.59 1.4 1.25 1.61
371 other:WZ-135G_FT 1.8 1.5 1.4 1.3 1.61
372 uk:GB03_Cruiser_Mk_I 4.4 4.9 2.7 3.96 9.12
373 uk:GB04_Valentine 2.9 1.85 1.9 2.53 3.37
374 uk:GB05_Vickers_Medium_Mk_II 6.5 3.84 2.2 3.8 12.37
375 uk:GB06_Vickers_Medium_Mk_III 4.9 3.9 1.9 2.88 5.74
376 uk:GB07_Matilda 4.1 2.83 1.6 2.56 3.64
377 uk:GB08_Churchill_I 3.3 1.8 1.5 1.96 2.13
378 uk:GB09_Churchill_VII 2.1 1.7 1.4 1.54 1.76
379 uk:GB09_Churchill_VII_BP 2.1 1.67 1.4 1.54 1.79
380 uk:GB101_FV1066_Senlac 1.4 1.16 1.5 1.24 1.33
381 uk:GB106_Cobra 1.1 1.05 1.4 1.14 1.16
382 uk:GB108_A46 1.5 1.56 1.5 1.25 1.6
383 uk:GB109_GSOR_1008 1.6 1.16 1.4 1.25 1.34
384 uk:GB109_GSOR_1008_Fearless 1.6 1.18 1.4 1.24 1.34
385 uk:GB10_Black_Prince 1.7 1.47 1.4 1.23 1.62
386 uk:GB110_FV4201_Chieftain_Prototype 1.1 1.02 1.4 1.13 1.15
387 uk:GB111_Charlemagne 1.4 1.13 1.4 1.31 1.35
388 uk:GB112_Caliban 1.4 1.16 1.4 1.45 1.35
389 uk:GB114_Vickers_MBT_Mk3 1.3 1.28 1.5 1.12 1.14
390 uk:GB116_Harry_Hopkins 2.5 1.97 2.3 1.96 2.12
391 uk:GB11_Caernarvon 1.3 1.13 1.4 1.23 1.35
392 uk:GB11_Caernarvon_D 1.4 1.16 1.4 1.3 1.35
393 uk:GB128_Nemesis 1.3 1.29 1.5 1.28 1.35
394 uk:GB12_Conqueror 1.1 1.02 1.4 1.1 1.13
395 uk:GB19_Sherman_Firefly 1.9 1.49 1.5 1.55 2.01
396 uk:GB20_Crusader 2.7 1.83 2.3 1.96 2.11
397 uk:GB21_A27BP 1.9 1.7 1.5 1.55 1.94
398 uk:GB21_Cromwell 1.9 1.7 1.5 1.55 1.94
399 uk:GB22_Comet 1.5 1.58 1.4 1.25 1.61
400 uk:GB23_Centurion 1.3 1.12 1.5 1.25 1.35
401 uk:GB24_Centurion_Mk3 1.1 1.02 1.4 1.14 1.15
402 uk:GB32_Tortoise 1.1 1.05 1.4 1.05 1.11
403 uk:GB35_Sentinel_AC_IV 1.9 1.7 1.5 1.56 1.94
404 uk:GB39_Universal_CarrierQF2 5.5 4.89 2.2 3.99 11.07
405 uk:GB40_Gun_Carrier_BP 2 1.56 1.4 1.52 1.77
406 uk:GB40_Gun_Carrier_Churchill 2.1 1.48 1.4 1.51 1.79
407 uk:GB41_Challenger 1.8 1.35 1.4 1.26 1.61
408 uk:GB42_Valentine_AT 4.3 2.68 1.9 2.43 5.48
409 uk:GB44_Archer_Custom 3.2 1.76 1.5 1.98 2.08
410 uk:GB51_Excelsior 3.2 2.27 1.5 1.97 2.13
411 uk:GB52_FV201_A45 1.7 1.47 1.4 1.24 1.61
412 uk:GB57_Alecto 3.7 2.93 1.8 2.54 1
413 uk:GB58_Cruiser_Mk_III 4.4 4.9 2.5 4.26 6.52
414 uk:GB59_Cruiser_Mk_IV 3.7 3.82 2.2 3.05 5.52
415 uk:GB60_Covenanter 3 3 1.9 2.55 2.68
416 uk:GB63_TOG_II 1.9 1.53 1.4 1.54 1.8
417 uk:GB63_TOG_II_BP 1.9 1.53 1.4 1.57 1.79
418 uk:GB68_Matilda_Black_Prince 2.5 1.96 1.7 1.98 2.12
419 uk:GB69_Cruiser_Mk_II 5.9 5.88 3.3 5.32 8.2
420 uk:GB71_AT_15A 1.7 1.58 1.4 1.22 1.62
421 uk:GB72_AT15 1.4 1.19 1.4 1.19 1.35
422 uk:GB73_AT2 3.2 2.45 1.5 1.97 2.13
423 uk:GB74_AT8 2 1.6 1.4 1.51 1.76
424 uk:GB75_AT7 1.7 1.33 1.4 1.23 1.62
425 uk:GB76_Mk_VIC 4.3 5.58 2.7 4.01 9.12
426 uk:GB80_Charioteer 1.6 1.16 1.4 1.27 1.34
427 uk:GB81_FV4004 1.2 1.05 1.4 1.11 1.2
428 uk:GB85_Cromwell_Berlin 1.9 1.7 1.5 1.55 1.94
429 uk:GB87_Chieftain_T95_turret 1.4 1.17 1.4 1.25 1.35
430 uk:GB93_Caernarvon_AX 1.3 1.13 1.4 1.25 1.35
431 uk:GB94_Centurion_Mk5-1_RAAC 1.3 1.12 1.5 1.28 1.35
432 uk:GB97_Chimera 1.3 1.15 1.5 1.32 1.35
433 uk:GB99_Turtle_Mk1 1.4 1.18 1.4 1.22 1.35
434 uk:GB_Centurion_MkDF 1.3 1.12 1.5 1.29 1.35
435 uk:GB_Churchill_MkVI 2.1 1.73 1.4 1.54 1.77
436 uk:GB_Churchill_MkVIII 2.1 1.64 1.4 1.56 1.79
437 uk:GB_FV301 1.4 1.07 1.5 1.25 1.35
438 uk:GB_Sherman_V 2.6 2.28 1.7 1.96 2.11
439 uk:GB_Vickers_Cruiser 1.1 1 1.5 1.11 1.2
440 uk:Sentinel_AC1 3 2.88 1.9 2.55 3.47
441 usa:A05_M4_105BD 2.7 2.4 1.7 1.96 2.12
442 usa:A100_T49 1.4 1.53 1.5 1.29 1.34
443 usa:A102_T28_concept 1.7 1.47 1.4 1.27 1.62
444 usa:A104_M4A3E8A 1.9 1.88 1.5 1.54 1.84
445 usa:A111_T25_Pilot 1.3 1.27 1.5 1.26 1.35
446 usa:A115_Chrysler_K 1.4 1.16 1.4 1.3 1.35
447 usa:A117_T26E5 1.4 1.15 1.4 1.27 1.35
448 usa:A121_T26E3 1.5 1.54 1.4 1.31 1.61
449 usa:A122_TS-5 1.4 1.15 1.4 1.23 1.35
450 usa:A124_T54E2 1.4 1.15 1.4 1.29 1.35
451 usa:A125_AEP_1 1.1 1.02 1.4 1.15 1.14
452 usa:A130_Super_Hellcat 1.7 1.51 1.4 1.24 1.6
453 usa:A132_T77 1.3 1.15 1.4 1.29 1.35
454 usa:A136_T42 1.5 1.54 1.4 1.29 1.61
455 usa:A140_ASTRON_REX_105 1.3 1.31 1.5 1.31 1.35
456 usa:A141_M-4-Y 1.4 1.16 1.4 1.27 1.35
457 usa:A142_Pawlack_Tank 2.1 1.68 1.4 1.58 1.85
458 usa:A146_TL_7_120 1.1 1.02 1.4 1.14 1.14
459 usa:A150_MBT_B 1.2 1.02 1.4 1.21 1.13
460 usa:A151_TS_60 1.3 1.14 1.4 1.06 1.15
461 usa:A175_ATAC 1.1 1.02 1.5 1.11 1.32
462 usa:A68_T28D 1.4 1.2 1.4 1.28 1.35
463 usa:A77_T6M 3 2.72 1.9 2.52 3.24
464 usa:A94_T37 1.9 1.73 1.6 1.55 2.21
465 usa:A99_T92E1 1.1 1.1 1.5 1.2 1.2
466 usa:M-3-Y 1.4 1.14 1.4 1.28 1.35
467 usa:M-3-Y-Midas 1.4 1.14 1.4 1.28 1.35
468 usa:M-5-Y 1.1 1.02 1.4 1.2 1.14
469 usa:M-7-Y 1.7 1.32 1.4 1.32 1.61
470 usa:M103 1.1 1.02 1.4 1.14 1.13
471 usa:M10_Wolverine 3.2 2.18 1.5 1.96 2.13
472 usa:M18E2BP 1.9 1.65 1.5 1.57 2.02
473 usa:M18_Hellcat 2.1 1.58 1.4 1.51 2.99
474 usa:M22_Locust 3.8 4.23 2.1 3 5.59
475 usa:M24_Chaffee 1.9 1.73 1.6 1.55 2.36
476 usa:M24_Chaffee_BP 1.9 1.73 1.6 1.55 2.36
477 usa:M2_lt 5.5 5.28 3.3 6.76 7.39
478 usa:M2_med 3.6 4.29 2.2 2.93 5.54
479 usa:M36_BP 2.1 1.58 1.4 1.53 1.85
480 usa:M36_Slagger 2.1 1.58 1.4 1.54 1.85
481 usa:M3_Grant 3 2.38 1.9 2.55 3.13
482 usa:M3_Stuart 4.4 4.29 2.7 4.23 5.89
483 usa:M41_Bulldog 1.6 1.49 1.5 1.25 1.61
484 usa:M46_CL 1.1 1.04 1.4 1.08 1.17
485 usa:M46_Patton 1.1 1.08 1.4 1.09 1.17
486 usa:M4A2E4 2.6 2.28 1.7 1.96 2.13
487 usa:M4A3E8_BP 1.9 1.88 1.5 1.55 1.84
488 usa:M4A3E8_Sherman 1.9 1.88 1.5 1.55 1.84
489 usa:M4_Sherman 2.7 1.98 1.7 1.95 2.12
490 usa:M5_Stuart 3.7 3.48 2.1 2.94 5.57
491 usa:M6 2.1 1.6 1.4 1.58 1.8
492 usa:M6A2E1 1.6 1.32 1.4 1.33 1.62
493 usa:M6A2E1_exp 1.4 1.07 1.4 1.33 1.35
494 usa:M6E2BP 2.1 1.6 1.4 1.57 1.77
495 usa:M6E2V2_BP 2.1 1.64 1.4 1.53 1.78
496 usa:M7_med 3.1 2.38 1.9 2.54 2.98
497 usa:M8A1 4 2.73 1.8 2.54 2.87
498 usa:Pershing 1.3 1.33 1.5 1.25 1.35
499 usa:Ram-II 2.5 2.22 1.7 1.97 2.12
500 usa:Sherman_Jumbo 1.9 1.88 1.5 1.56 1.8
501 usa:T14 3.2 2.35 1.5 1.97 2.13
502 usa:T18 5.5 4.48 2.2 4.43 5.65
503 usa:T1_E6 4.4 5 2.7 4.12 8.54
504 usa:T1_hvy 3.3 2 1.5 1.96 2.13
505 usa:T20 1.5 1.54 1.4 1.28 1.61
506 usa:T21 1.9 1.81 1.6 1.57 2.27
507 usa:T21_BP 1.9 1.81 1.6 1.56 2.24
508 usa:T23E3 1.5 1.54 1.4 1.25 1.61
509 usa:T25_2 1.7 1.4 1.4 1.23 1.61
510 usa:T25_AT 1.7 1.44 1.4 1.26 1.61
511 usa:T26_E4_SuperPershing 1.3 1.2 1.5 1.29 1.35
512 usa:T28 1.4 1.16 1.4 1.23 1.35
513 usa:T28_Prototype 1.4 1.16 1.4 1.24 1.35
514 usa:T29 1.7 1.32 1.4 1.31 1.61
515 usa:T2_lt 4.3 5.45 2.7 3.9 8.26
516 usa:T2_med 4.6 5 2.5 4.23 7.11
517 usa:T30 1.1 1.05 1.4 1.12 1.17
518 usa:T32 1.4 1.16 1.4 1.29 1.35
519 usa:T34_hvy 1.3 1.07 1.4 1.3 1.35
520 usa:T34_hvy_Freedom 1.4 1.07 1.4 1.31 1.35
521 usa:T40 4 2.38 1.8 2.54 3.13
522 usa:T49 3.2 2.18 1.5 1.97 2.08
523 usa:T49_Fearless 1.4 1.53 1.5 1.28 1.34
524 usa:T54E1 1.1 1.06 1.4 1.1 1.16
525 usa:T69 1.3 1.33 1.5 1.27 1.35
526 usa:T71 1.6 1.49 1.5 1.27 1.6
527 usa:T7_Combat_Car 4.4 5.58 2.7 4.26 8.54
528 usa:T82 4.1 13.5 1.9 2.78 5.78
529 usa:T95 1.2 1.05 1.4 1.11 1.12
530 usa:T95E2 1.3 1.33 1.5 1.25 1.35
531 ussr:A-20 3.1 2.72 1.9 2.54 3
532 ussr:A-32 3 2.85 1.9 2.52 3.43
533 ussr:AT-1 5.5 3.99 2.2 3.99 8.51
534 ussr:BT-2 4.3 4.71 2.7 4.18 6.52
535 ussr:BT-7 3.6 2.98 2.1 3.06 5.51
536 ussr:BT-SV 3.8 2.86 2.1 3.16 5.58
537 ussr:Churchill_LL 3.2 2.27 1.5 1.97 2.13
538 ussr:GAZ-74b 4 2.48 1.8 2.53 3.05
539 ussr:IS 1.6 1.44 1.4 1.32 1.62
540 ussr:IS-2Sh 1.4 1.15 1.4 1.31 1.35
541 ussr:IS-3 1.4 1.16 1.4 1.3 1.35
542 ussr:IS8 1.2 1.02 1.4 1.12 1.15
543 ussr:ISU-152 1.6 1.13 1.4 1.38 1.35
544 ussr:K-91 1.1 1.02 1.4 1.1 1.12
545 ussr:KV-13 1.5 1.67 1.4 1.26 1.61
546 ussr:KV-1s 2.3 1.6 1.4 1.6 1.82
547 ussr:KV-1s-BP 2.3 1.5 1.4 1.64 1.83
548 ussr:KV-220 3.2 2.27 1.5 1.96 2.13
549 ussr:KV-3 1.7 1.44 1.4 1.33 1.62
550 ussr:KV-5 1.5 1.15 1.4 1.29 1.35
551 ussr:KV1 3.2 2.11 1.5 1.95 2.13
552 ussr:KV2 2.4 2.25 1.4 1.52 1.85
553 ussr:KV4 1.4 1.13 1.4 1.3 1.35
554 ussr:LTP 3.8 2.88 2.1 3.16 5.56
555 ussr:M3_Stuart_LL 3.7 5.04 2.1 2.86 5.51
556 ussr:MS-1obr21 4.3 4.71 2.7 4.18 6.98
557 ussr:MT25 2 1.79 1.6 1.56 2.06
558 ussr:Matilda_II_LL 2.5 2.22 1.7 1.96 2.12
559 ussr:Object252 1.4 1.14 1.4 1.33 1.35
560 ussr:Object252_Fearless 1.4 1.41 1.4 1.33 1.35
561 ussr:Object_704 1.1 1.05 1.4 1.1 1.13
562 ussr:R105_BT_7A 3.8 6.27 2.1 3.86 5.64
563 ussr:R107_LTB 1.6 1.56 1.5 1.28 1.61
564 ussr:R109_T54S 1.4 1.34 1.5 1.29 1.34
565 ussr:R111_ISU130 1.5 1.16 1.4 1.23 1.35
566 ussr:R112_T54_45 1.3 1.37 1.5 1.31 1.35
567 ussr:R113_Object_730 1.4 1.15 1.4 1.31 1.35
568 ussr:R115_IS-3_auto 1.4 1.15 1.4 1.3 1.35
569 ussr:R116_ISU122C_Berlin 1.8 1.45 1.4 1.22 1.62
570 ussr:R127_T44_100_P 1.3 1.33 1.5 1.27 1.35
571 ussr:R131_LTG 1.6 1.61 1.5 1.28 1.61
572 ussr:R134_Object_252U 1.4 1.14 1.4 1.33 1.35
573 ussr:R140_M4A2_Loza 1.9 1.88 1.5 1.55 1.84
574 ussr:R146_STG 1.3 1.33 1.5 1.33 1.35
575 ussr:R154_T-34E-43 2.6 2.22 1.7 1.96 2.11
576 ussr:R158_LT_432 1.4 1.34 1.5 1.28 1.34
577 ussr:R159_SU_130PM 1.6 1.16 1.4 1.26 1.34
578 ussr:R160_object_244 2.1 1.71 1.4 1.55 1.82
579 ussr:R172_Object_752 1.1 1.02 1.4 1.13 1.13
580 ussr:R175_IS_2E 1.6 1.47 1.4 1.31 1.61
581 ussr:R180_Object_274A 1.3 1.31 1.5 1.3 1.35
582 ussr:R181_Object_452K 1.2 1.02 1.4 1.15 1.13
583 ussr:R187_Object_590 1.3 1.32 1.5 1.29 1.35
584 ussr:R192_K_2 1.4 1.12 1.4 1.33 1.35
585 ussr:R194_ST_62_var_2 1.3 1.31 1.5 1.28 1.35
586 ussr:R217_Object_Kust 1.5 1.16 1.4 1.27 1.35
587 ussr:R43_T-70_57 3.8 3.41 2.1 3.43 5.49
588 ussr:R63_ST_IBD 1.2 1.02 1.4 1.17 1.14
589 ussr:R71_IS_2B 1.6 1.44 1.4 1.33 1.61
590 ussr:R98_T44_85 1.5 1.64 1.4 1.26 1.61
591 ussr:R_Object_84 1.1 1.07 1.5 1.12 1.17
592 ussr:ST_I 1.2 1.02 1.4 1.16 1.13
593 ussr:SU-100 2.1 1.57 1.4 1.55 1.81
594 ussr:SU-101 1.5 1.15 1.4 1.26 1.35
595 ussr:SU-152 1.8 2 1.4 1.38 1.61
596 ussr:SU-76 4.2 3.6 1.9 3.18 5.56
597 ussr:SU-85 3.2 1.99 1.5 1.97 2.12
598 ussr:SU100M1 1.7 1.35 1.4 1.25 1.61
599 ussr:SU100Y 2.1 1.53 1.4 1.57 1.89
600 ussr:SU122_44 1.8 1.45 1.4 1.25 1.61
601 ussr:SU122_54 1.2 1.05 1.4 1.03 1.14
602 ussr:SU76I 4.1 3.31 1.9 3.18 5.47
603 ussr:SU_85I 3.1 2.3 1.5 1.98 2.12
604 ussr:T-127 3.8 2.76 2.1 3.16 5.52
605 ussr:T-26 5.5 5.07 3.3 6.16 8.2
606 ussr:T-28 3 2.85 1.9 2.54 3.09
607 ussr:T-34 2.7 2.01 1.7 1.96 2.12
608 ussr:T-34-85 1.9 1.71 1.5 1.58 1.87
609 ussr:T-34-85_Rudy 1.5 1.67 1.4 1.26 1.61
610 ussr:T-34-85_Victory 1.9 1.88 1.5 1.57 1.87
611 ussr:T-43 1.5 1.6 1.4 1.25 1.61
612 ussr:T-44 1.3 1.33 1.5 1.34 1.35
613 ussr:T-46 3.8 4.32 2.1 2.83 5.6
614 ussr:T-54 1.1 1.1 1.4 1.09 1.16
615 ussr:T150 2.1 1.55 1.4 1.59 1.82
616 ussr:T34_85BP 1.9 1.88 1.5 1.57 1.87
617 ussr:T34_85TUR 1.5 1.58 1.4 1.29 1.62
618 ussr:Tetrarch_LL 4.3 3.88 2.7 3.88 8.51
619 ussr:Valentine_LL 2.9 4.55 1.9 2.55 3.42
Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

@@ -1,3 +1,4 @@
import { alias } from "@blitzkit/core";
import { Flex, Heading, IconButton, Table } from "@radix-ui/themes";
import type { TankCharacteristics } from "../../core/blitzkit/tankCharacteristics";
import { useLocale } from "../../hooks/useLocale";
@@ -57,7 +58,10 @@ export function Body({ stats }: BodyProps) {
alt={unwrap(thisShell.name!)}
width={16}
height={16}
src={`/api/icons/shells/${thisShell.icon}.webp`}
src={alias(
"api",
`/icons/shells/${thisShell.icon}.webp`,
)}
/>
</IconButton>
))}
@@ -1,4 +1,4 @@
import { availableProvisions, permanentSkills } from "@blitzkit/core";
import { alias, availableProvisions, permanentSkills } from "@blitzkit/core";
import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
import { ComponentPlaceholderIcon } from "@radix-ui/react-icons";
import {
@@ -231,7 +231,10 @@ export function CompareTable({ stats }: CompareTableProps) {
alt={module}
width={24}
height={24}
src={`/api/icons/modules/${module}.webp`}
src={alias(
"api",
`/icons/modules/${module}.webp`,
)}
style={{
marginLeft: index > 0 ? -8 : 0,
objectFit: "contain",
@@ -320,7 +323,10 @@ export function CompareTable({ stats }: CompareTableProps) {
.name!,
)}
key={provision}
src={`/api//icons/provisions/${provision}.webp`}
src={alias(
"api",
`//icons/provisions/${provision}.webp`,
)}
style={{
left: "50%",
/**
@@ -525,7 +531,10 @@ export function CompareTable({ stats }: CompareTableProps) {
].name!,
)}
key={consumable}
src={`/api/icons/consumables/${consumable}.webp`}
src={alias(
"api",
`/icons/consumables/${consumable}.webp`,
)}
style={{
left: "50%",
/**
@@ -617,7 +626,7 @@ export function CompareTable({ stats }: CompareTableProps) {
variant={camouflage ? "solid" : "outline"}
>
<img
src={`/api/icons/camo.webp`}
src={alias("api", `/icons/camo.webp`)}
style={{
width: "1em",
height: "1em",
@@ -1,4 +1,4 @@
import { permanentSkills, romanize } from "@blitzkit/core";
import { alias, permanentSkills, romanize } from "@blitzkit/core";
import { Flex } from "@radix-ui/themes";
import { api } from "../core/blitzkit/api";
import { GenericTankComponentButton } from "./ModuleButtons/GenericTankComponentButton";
@@ -27,7 +27,7 @@ export function CrewSkillManager({
special={!permanentSkills.includes(skill)}
selected={level > 0}
discriminator={level === 0 ? undefined : romanize(level)}
icon={`/api/icons/skills/${skill}.webp`}
icon={alias("api", `/icons/skills/${skill}.webp`)}
onClick={(event) => {
if (!onChange) return;
+6 -6
View File
@@ -12,7 +12,7 @@ export interface Props {
}
const gameDefinitions = await api.gameDefinitions();
const PUBLIC_ASSET_BRANCH = assertSecret(import.meta.env.PUBLIC_BRANCH);
const environment = assertSecret(import.meta.env.PUBLIC_ENVIRONMENT);
const { locale } = Astro.props;
const strings = getStrings(locale);
---
@@ -135,7 +135,7 @@ const strings = getStrings(locale);
<Flex direction="column" gap="1" flexGrow="1" flexBasis="0" align="end">
<Text size="2" color="gray">BlitzKit</Text>
{
PUBLIC_ASSET_BRANCH !== "preview" && (
environment !== "preview" && (
<Text size="2" color="gray">
WoTB
</Text>
@@ -149,14 +149,14 @@ const strings = getStrings(locale);
{packageJson.version}
</Code>
{
PUBLIC_ASSET_BRANCH !== "preview" && (
environment !== "preview" && (
<Code size="2" color="gray">
{gameDefinitions.version}
</Code>
)
}
<Code size="2" color="gray">
{PUBLIC_ASSET_BRANCH}
{environment}
</Code>
</Flex>
</Flex>
@@ -168,7 +168,7 @@ const strings = getStrings(locale);
</Code>
{
PUBLIC_ASSET_BRANCH !== "preview" && (
environment !== "preview" && (
<>
<Text size="2" color="gray">
WoTB
@@ -182,7 +182,7 @@ const strings = getStrings(locale);
<Text size="2" color="gray">CDN</Text>
<Code size="2" color="gray">
{PUBLIC_ASSET_BRANCH}
{environment}
</Code>
</Flex>
</Flex>
@@ -1,4 +1,4 @@
import { Avatar } from "@blitzkit/core";
import { alias, Avatar } from "@blitzkit/core";
import { Cross1Icon, DownloadIcon } from "@radix-ui/react-icons";
import {
Dialog,
@@ -18,7 +18,10 @@ type GalleryCardProps = MaybeSkeletonComponentProps<{
}>;
export function GalleryCard(props: GalleryCardProps) {
const src = `/api/gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`;
const src = alias(
"api",
`/gallery/avatars/${!props.skeleton && `${props.avatar.id}${props.avatar.extension}`}`,
);
const card = useRef<HTMLDivElement>(null!);
const { unwrap } = useLocale();
@@ -1,3 +1,4 @@
import { useModel } from "../hooks/useModel";
import { Guess, GuessState } from "../stores/guess";
import { SmartCanvas } from "./SmartCanvas";
import { Controls } from "./Tankopedia/HeroSection/components/TankSandbox/components/Control";
@@ -12,6 +13,9 @@ export function GuessRenderer() {
const helpingReveal = Guess.use((state) => state.helpingReveal);
const isRevealed = helpingReveal || guessState !== GuessState.NotGuessed;
const id = Guess.use((state) => state.tank.id);
const { hasPbr } = useModel(id);
return (
<SmartCanvas
style={{
@@ -24,7 +28,7 @@ export function GuessRenderer() {
camera={{ position: [-5, 5, -5] }}
>
<Controls zoomable={false} />
<Lighting />
<Lighting hasPbr={hasPbr} />
<TankModel />
</SmartCanvas>
);
+5 -1
View File
@@ -1,4 +1,5 @@
import {
alias,
SEARCH_KEYS,
TankDefinition,
TIER_ROMAN_NUMERALS,
@@ -149,7 +150,10 @@ export function Guesser() {
prefix={
<img
style={{ width: "1em", height: "1em" }}
src={`/api/flags/circle/${result.nation}.webp`}
src={alias(
"api",
`/flags/circle/${result.nation}.webp`,
)}
/>
}
discriminator={
@@ -43,7 +43,7 @@ export const MixerScene = forwardRef<HTMLCanvasElement>((_, ref) => {
target={[0, 0.5, 0]}
/>
<Lighting />
<Lighting hasPbr={false} />
<SceneProps />
<Suspense fallback={<MixerFallback />}>
@@ -1,3 +1,4 @@
import { alias } from "@blitzkit/core";
import { ClockIcon, ReloadIcon } from "@radix-ui/react-icons";
import { Flex, Text } from "@radix-ui/themes";
import { api } from "../../core/blitzkit/api";
@@ -28,7 +29,7 @@ export function ConsumableButton({
<GenericTankComponentButton
style={{ width: "6rem" }}
tooltip={unwrap(consumableDefinitions.consumables[consumable].name!)}
icon={`/api/icons/consumables/${consumable}.webp`}
icon={alias("api", `/icons/consumables/${consumable}.webp`)}
{...props}
>
{(cooldown || duration) && (
@@ -1,3 +1,4 @@
import { alias } from "@blitzkit/core";
import { api } from "../../core/blitzkit/api";
import { useLocale } from "../../hooks/useLocale";
import { GenericTankComponentButton } from "./GenericTankComponentButton";
@@ -15,7 +16,7 @@ export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) {
return (
<GenericTankComponentButton
tooltip={unwrap(equipmentDefinitions.equipments[equipment].name!)}
icon={`/api/icons/equipment/${equipment}.webp`}
icon={alias("api", `/icons/equipment/${equipment}.webp`)}
{...props}
/>
);
@@ -1,3 +1,4 @@
import { alias } from "@blitzkit/core";
import { GenericTankComponentButton } from "./GenericTankComponentButton";
import type { TankComponentButtonProps } from "./TankComponentButton";
@@ -8,7 +9,7 @@ interface ModuleProps extends TankComponentButtonProps {
export function ModuleButton({ module, ...props }: ModuleProps) {
return (
<GenericTankComponentButton
icon={`/api/icons/modules/${module}.webp`}
icon={alias("api", `/icons/modules/${module}.webp`)}
iconStyles={{
top: "50%",
left: "50%",
@@ -1,3 +1,4 @@
import { alias } from "@blitzkit/core";
import { api } from "../../core/blitzkit/api";
import { useLocale } from "../../hooks/useLocale";
import { GenericTankComponentButton } from "./GenericTankComponentButton";
@@ -15,7 +16,7 @@ export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) {
return (
<GenericTankComponentButton
tooltip={unwrap(provisionDefinitions.provisions[provision].name!)}
icon={`/api/icons/provisions/${provision}.webp`}
icon={alias("api", `/icons/provisions/${provision}.webp`)}
{...props}
/>
);
@@ -1,3 +1,4 @@
import { alias } from "@blitzkit/core";
import { GenericTankComponentButton } from "./GenericTankComponentButton";
import type { TankComponentButtonProps } from "./TankComponentButton";
@@ -8,7 +9,7 @@ interface ShellButtonProps extends TankComponentButtonProps {
export function ShellButton({ shell, ...props }: ShellButtonProps) {
return (
<GenericTankComponentButton
icon={`/api/icons/shells/${shell}.webp`}
icon={alias("api", `/icons/shells/${shell}.webp`)}
{...props}
/>
);
+1 -1
View File
@@ -5,7 +5,7 @@ import { Button, Code, Flex, Link, Text } from "@radix-ui/themes";
export function Plugs() {
const promoteOpentest =
assertSecret(import.meta.env.PUBLIC_PROMOTE_OPENTEST) === "true" &&
assertSecret(import.meta.env.PUBLIC_BRANCH) !== "opentest";
assertSecret(import.meta.env.PUBLIC_ENVIRONMENT) !== "opentest";
const promotions = [promoteOpentest];
if (promotions.every((promotion) => !promotion)) return null;
@@ -1,3 +1,5 @@
import { alias } from "@blitzkit/core";
interface SmallTankIconProps {
id: number;
size?: number;
@@ -17,7 +19,7 @@ export function SmallTankIcon({ id, size = HEIGHT }: SmallTankIconProps) {
>
<img
alt={`Tank ${id}`}
src={`/api/tanks/${id}/icons/small.webp`}
src={alias("api", `/tanks/${id}/icons/small.webp`)}
style={{
transform: `scale(${(size / HEIGHT) * 100}%)`,
transformOrigin: "top left",
@@ -1,4 +1,4 @@
import { TankType, type TankDefinition } from "@blitzkit/core";
import { alias, TankType, type TankDefinition } from "@blitzkit/core";
import { Flex, Text, type TextProps } from "@radix-ui/themes";
import { uniq } from "lodash-es";
import { forwardRef, type ReactNode } from "react";
@@ -60,7 +60,10 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
className="tank-search-card"
data-provide-link={provideLink}
style={{
backgroundImage: `url(/api/flags/scratched/${tank.nation}.webp)`,
backgroundImage: `url(${alias(
"api",
`/flags/scratched/${tank.nation}.webp`,
)})`,
...style,
}}
{...props}
@@ -76,7 +79,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
>
<img
alt={unwrap(tank.name!)}
src={`/api/tanks/${tank.id}/icons/big.webp`}
src={alias("api", `/tanks/${tank.id}/icons/big.webp`)}
className="image"
draggable={false}
/>
@@ -1,4 +1,5 @@
import {
alias,
GunDefinition,
ShellType,
TANK_CLASSES,
@@ -309,7 +310,7 @@ function NationsFilter() {
width: "1.25em",
height: "1.25em",
}}
src={`/api/flags/circle/${nation}.webp`}
src={alias("api", `/flags/circle/${nation}.webp`)}
/>
))}
</Flex>
@@ -346,7 +347,7 @@ function NationsFilter() {
right="0"
mr="-2"
style={{
backgroundImage: `url(${`/api/flags/scratched/${nation}.webp`})`,
backgroundImage: `url(${alias("api", `/flags/scratched/${nation}.webp`)})`,
backgroundSize: "100%",
backgroundRepeat: "no-repeat",
backgroundPosition: "5rem center",
@@ -679,9 +680,10 @@ function IndividualShellFilter({
{shells[index] !== null && (
<img
style={{ width: "1em", height: "1em" }}
src={`/api/icons/shells/${shellTypeIcons[shells[index]]}${
premium ? "_premium" : ""
}.webp`}
src={alias(
"api",
`/icons/shells/${shellTypeIcons[shells[index]]}${premium ? "_premium" : ""}.webp`,
)}
/>
)}
</IconButton>
@@ -712,9 +714,12 @@ function IndividualShellFilter({
color={selected ? undefined : "gray"}
>
<img
src={`/api/icons/shells/${shellTypeIcons[shellType]}${
premium ? "_premium" : ""
}.webp`}
src={alias(
"api",
`/icons/shells/${shellTypeIcons[shellType]}${
premium ? "_premium" : ""
}.webp`,
)}
style={{ width: "1.25em", height: "1.25em" }}
/>
@@ -883,7 +888,7 @@ function ConsumablesFilter() {
height: "1.25em",
objectFit: "contain",
}}
src={`/api/icons/consumables/${consumable}.webp`}
src={alias("api", `/icons/consumables/${consumable}.webp`)}
/>
))}
@@ -928,7 +933,7 @@ function ConsumablesFilter() {
height: "1.25em",
objectFit: "contain",
}}
src={`/api/icons/consumables/${consumable}.webp`}
src={alias("api", `/icons/consumables/${consumable}.webp`)}
/>
{unwrap(consumableDefinition.name!)}
@@ -977,7 +982,7 @@ function ProvisionsFilter() {
height: "1.25em",
objectFit: "contain",
}}
src={`/api/icons/provisions/${provision}.webp`}
src={alias("api", `/icons/provisions/${provision}.webp`)}
/>
))}
@@ -1022,7 +1027,7 @@ function ProvisionsFilter() {
height: "1.25em",
objectFit: "contain",
}}
src={`/api/icons/provisions/${provision}.webp`}
src={alias("api", `/icons/provisions/${provision}.webp`)}
/>
{unwrap(provisionDefinition.name!)}
@@ -1065,8 +1070,10 @@ function GameModeAbilitiesFilter() {
: rawPowers;
const icons = [
...abilities.map((ability) => `api/icons/consumables/${ability}.webp`),
...powers.map((power) => `api/icons/provisions/${power}.webp`),
...abilities.map((ability) =>
alias("api", `/icons/consumables/${ability}.webp`),
),
...powers.map((power) => alias("api", `/icons/provisions/${power}.webp`)),
];
return (
@@ -1151,7 +1158,10 @@ function GameModeAbilitiesFilter() {
height: "1.25em",
objectFit: "contain",
}}
src={`/api/icons/consumables/${consumable}.webp`}
src={alias(
"api",
`/icons/consumables/${consumable}.webp`,
)}
/>
{unwrap(abilityDefinition.name!)}
@@ -1200,7 +1210,10 @@ function GameModeAbilitiesFilter() {
height: "1.25em",
objectFit: "contain",
}}
src={`/api/icons/provisions/${provision}.webp`}
src={alias(
"api",
`/icons/provisions/${provision}.webp`,
)}
/>
{unwrap(powerDefinition.name!)}
@@ -15,7 +15,7 @@ const tankDefinitions = await api.tankDefinitions();
export function CalloutsSection() {
const tank = Duel.use((state) => state.protagonist.tank);
const promoteAeson =
assertSecret(import.meta.env.PUBLIC_BRANCH) === "preview";
assertSecret(import.meta.env.PUBLIC_ENVIRONMENT) === "preview";
useEffect(() => {
TankopediaPersistent.mutate((draft) => {
@@ -1,4 +1,4 @@
import { isExplosive } from "@blitzkit/core";
import { alias, isExplosive } from "@blitzkit/core";
import { literals } from "@blitzkit/i18n";
import { Flex, Heading, IconButton, Tooltip } from "@radix-ui/themes";
import { useLocale } from "../../../../../../hooks/useLocale";
@@ -62,7 +62,7 @@ export function Firepower({
alt={unwrap(thisShell.name!)}
width={16}
height={16}
src={`/api/icons/shells/${thisShell.icon}.webp`}
src={alias("api", `/icons/shells/${thisShell.icon}.webp`)}
/>
</IconButton>
</Tooltip>
@@ -1,3 +1,4 @@
import { alias } from "@blitzkit/core";
import { Flex, IconButton, Tooltip } from "@radix-ui/themes";
import { useLocale } from "../../../../../../../../hooks/useLocale";
import { Duel } from "../../../../../../../../stores/duel";
@@ -37,7 +38,7 @@ export function ShellSwitcher() {
<img
alt={unwrap(thisShell.name!)}
style={{ width: "1.125rem", height: "1.125rem" }}
src={`/api/icons/shells/${thisShell.icon}.webp`}
src={alias("api", `/icons/shells/${thisShell.icon}.webp`)}
/>
</IconButton>
</Tooltip>
@@ -1,4 +1,4 @@
import { isExplosive } from "@blitzkit/core";
import { alias, isExplosive } from "@blitzkit/core";
import { literals } from "@blitzkit/i18n";
import { Box, Flex, Text } from "@radix-ui/themes";
import { useRef, useState } from "react";
@@ -124,7 +124,7 @@ export function RicochetVisualizer({ stats }: StatsAcceptorProps) {
/>
<img
src={`/api/icons/shells/${shell.icon}.webp`}
src={alias("api", `/icons/shells/${shell.icon}.webp`)}
style={{
objectFit: "contain",
transform: "translate(-50%, -50%) rotate(-45deg)",
@@ -1,3 +1,4 @@
import { alias } from "@blitzkit/core";
import { Badge, Box, Flex, Heading, Switch, Text } from "@radix-ui/themes";
import { useLocale } from "../../../../hooks/useLocale";
import { Duel } from "../../../../stores/duel";
@@ -33,7 +34,7 @@ export function Equalizer() {
style={{
cursor: "pointer",
borderRadius: "var(--radius-3)",
backgroundImage: "url(/api/gamemodes/45/banner.webp)",
backgroundImage: `url(${alias("api", `/gamemodes/45/banner.webp`)})`,
backgroundSize: "cover",
backgroundPosition: "center",
overflow: "hidden",
@@ -1,3 +1,4 @@
import { alias } from "@blitzkit/core";
import { Flex, Heading } from "@radix-ui/themes";
import { useLocale } from "../../../../hooks/useLocale";
import { Duel } from "../../../../stores/duel";
@@ -21,7 +22,7 @@ export function Miscellaneous() {
<Flex gap="2" align="center">
{!tank.fixed_camouflage && (
<GenericTankComponentButton
icon={"/api/icons/camo.webp"}
icon={alias("api", "/icons/camo.webp")}
selected={camouflage}
iconStyles={{
top: "50%",
@@ -36,7 +37,7 @@ export function Miscellaneous() {
/>
)}
<GenericTankComponentButton
icon={"/api/icons/boosters/equipment.webp"}
icon={alias("api", "/icons/boosters/equipment.webp")}
selected={cooldownBooster > 0}
banner={
cooldownBooster === 0
@@ -1,4 +1,5 @@
import {
alias,
EngineDefinition,
formatCompact,
GunDefinition,
@@ -110,7 +111,7 @@ function ModuleButton({
<img
alt={unlock.cost!.type}
src={`/api/icons/currencies/${unlock.cost!.type}.webp`}
src={alias("api", `/icons/currencies/${unlock.cost!.type}.webp`)}
style={{
width: 12,
height: 12,
@@ -125,7 +126,10 @@ function ModuleButton({
src={
isTank
? tankIcon(unlock.id)
: `/api/icons/modules/${moduleTypeTypeIconName[unlock.type]}.webp`
: alias(
"api",
`/icons/modules/${moduleTypeTypeIconName[unlock.type]}.webp`,
)
}
style={{
width: isTank ? 64 : 32,
@@ -1,3 +1,4 @@
import { alias } from "@blitzkit/core";
import { Box, Flex, Heading, Text } from "@radix-ui/themes";
import { api } from "../../core/blitzkit/api";
import { useLocale } from "../../hooks/useLocale";
@@ -37,7 +38,10 @@ export function GameModeSection() {
direction="column"
style={{
borderRadius: "var(--radius-3)",
background: `url(/api/gamemodes/${id}/banner.webp)`,
background: `url(${alias(
"api",
`/gamemodes/${id}/banner.webp`,
)})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
@@ -45,7 +49,10 @@ export function GameModeSection() {
<Box
flexGrow="1"
style={{
background: `url(/api/gamemodes/${id}/banner.webp)`,
background: `url(${alias(
"api",
`/gamemodes/${id}/banner.webp`,
)})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
@@ -80,7 +87,7 @@ export function GameModeSection() {
height: "1.5em",
objectFit: "contain",
}}
src={`/api/icons/provisions/${id}.webp`}
src={alias("api", `/icons/provisions/${id}.webp`)}
alt={unwrap(provisions.name!)}
/>
{unwrap(provisions.name!)}
@@ -101,7 +108,7 @@ export function GameModeSection() {
height: "1.5em",
objectFit: "contain",
}}
src={`/api/icons/consumables/${id}.webp`}
src={alias("api", `/icons/consumables/${id}.webp`)}
alt={unwrap(consumable.name!)}
/>
{unwrap(consumable.name!)}
@@ -1,3 +1,4 @@
import { alias } from "@blitzkit/core";
import { IconButton } from "@radix-ui/themes";
import { useArmor } from "../../../../../../hooks/useArmor";
import { Duel } from "../../../../../../stores/duel";
@@ -37,7 +38,7 @@ export function DynamicArmorSwitcher() {
>
<img
alt="Dynamic Armor"
src={"/api/icons/consumables/73.webp"}
src={alias("api", "/icons/consumables/73.webp")}
style={{
width: "50%",
height: "50%",
@@ -1,4 +1,5 @@
import {
alias,
resolvePenetrationCoefficient,
TIER_ROMAN_NUMERALS,
uniqueGuns,
@@ -139,7 +140,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
<img
alt="Antagonist Gun"
src={"/api/icons/modules/gun.webp"}
src={alias("api", "/icons/modules/gun.webp")}
style={{
width: "65%",
height: "65%",
@@ -208,7 +209,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
>
<img
alt={unwrap(thisShell.name!)}
src={`/api/icons/shells/${thisShell.icon}.webp`}
src={alias("api", `/icons/shells/${thisShell.icon}.webp`)}
style={{
width: "50%",
height: "50%",
@@ -245,7 +246,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
>
<img
alt="Calibrated Shells"
src={"/api/icons/equipment/103.webp"}
src={alias("api", "/icons/equipment/103.webp")}
style={{
width: "50%",
height: "50%",
@@ -270,7 +271,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
>
<img
alt="Enhanced Armor"
src={"/api/icons/equipment/110.webp"}
src={alias("api", "/icons/equipment/110.webp")}
style={{
width: "50%",
height: "50%",
@@ -1,4 +1,6 @@
import { useFrame } from "@react-three/fiber";
import { blueDark, orangeDark } from "@radix-ui/colors";
import { ContactShadows } from "@react-three/drei";
import { invalidate, useFrame } from "@react-three/fiber";
import { clamp, times } from "lodash-es";
import { Quicklime } from "quicklime";
import {
@@ -12,25 +14,35 @@ import { HemisphereLight, SpotLight, type Group } from "three";
import { degToRad, lerp } from "three/src/math/MathUtils.js";
import { Tankopedia } from "../../../../../../stores/tankopedia";
import { TankopediaPersistent } from "../../../../../../stores/tankopediaPersistent";
import { HelpingSpotLight } from "../../../../../HelpingSpotLight";
import { TankopediaDisplay } from "../../../../../../stores/tankopediaPersistent/constants";
const ANGLE = degToRad(12);
const ANGLE = degToRad(15);
const REVEAL_ANIMATION_TIME = 3;
const TRANSITION_ANIMATION_TIME = 0.5;
const LIGHTS_COUNT = 4;
const LIGHTS_COUNT = 3;
const THETA_OFFSET = degToRad(180 - 45);
const LIGHT_DISTANCE = 20;
const LIGHT_HEIGHT_0 = 4;
const LIGHT_HEIGHT_1 = 6;
const INTENSITY_0 = 6 * 9;
const INTENSITY_1 = 3 * 9;
const HEMISPHERE_INTENSITY = 2;
const LIGHT_HEIGHT_0 = 7;
const LIGHT_HEIGHT_1 = 10;
const INTENSITY_0 = 2 ** 6;
const INTENSITY_1 = 2 ** 3;
const HEMISPHERE_INTENSITY = 2 ** 0.7;
const NON_PBR_FACTOR = 2;
const SHADOW_RESOLUTION = 2 ** 9;
const SHADOW_FOCUS = 2 ** 0;
export const transitionEvent = new Quicklime<number>(0);
export function Lighting() {
const wrapper = useRef<Group>(null!);
interface Props {
hasPbr: boolean;
}
export function Lighting({ hasPbr }: Props) {
const wrapper = useRef<Group>(null);
const highGraphics = TankopediaPersistent.use((state) => state.highGraphics);
const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay);
@@ -61,8 +73,20 @@ export function Lighting() {
return (
<>
<ContactShadows
near={0}
far={2 ** 6}
width={1.5}
height={1.5}
blur={2 ** 0.5}
opacity={2 ** -0.4}
resolution={2 ** 7}
depthWrite={false}
/>
{animate && (
<Animator
hasPbr={hasPbr}
t0={t0}
animationTime={animationTime}
wrapper={wrapper}
@@ -73,21 +97,29 @@ export function Lighting() {
)}
<group ref={wrapper}>
<hemisphereLight intensity={0} color="#ffffff" groundColor="#afafaf" />
<hemisphereLight
intensity={0}
color={blueDark.blue12}
groundColor={orangeDark.orange8}
/>
{times(LIGHTS_COUNT, (index) => {
const x = index / (LIGHTS_COUNT - 1);
const theta = 2 * Math.PI * (index / LIGHTS_COUNT) + THETA_OFFSET;
const position = [
LIGHT_DISTANCE * Math.sin(theta),
-LIGHT_DISTANCE * Math.sin(theta),
lerp(LIGHT_HEIGHT_0, LIGHT_HEIGHT_1, x),
LIGHT_DISTANCE * Math.cos(theta),
] as const;
const intensity = lerp(INTENSITY_0, INTENSITY_1, x);
const light = useRef<SpotLight>(null!);
// useHelper(light, SpotLightHelper);
return (
<HelpingSpotLight
userData={{ index }}
<spotLight
ref={light}
key={index}
position={position}
intensity={intensity}
@@ -96,6 +128,9 @@ export function Lighting() {
decay={1}
color="#ffffff"
angle={0}
target-position={[0, 1.5, 0]}
shadow-mapSize={[SHADOW_RESOLUTION, SHADOW_RESOLUTION]}
shadow-focus={SHADOW_FOCUS}
/>
);
})}
@@ -109,18 +144,29 @@ interface AnimatorProps {
t0: RefObject<number>;
animationTime: RefObject<number>;
wrapper: RefObject<Group>;
wrapper: RefObject<Group | null>;
hasPbr: boolean;
}
function Animator({ stop, t0, animationTime, wrapper }: AnimatorProps) {
function Animator({ stop, t0, animationTime, wrapper, hasPbr }: AnimatorProps) {
const requestedDisplay = Tankopedia.use((state) => state.requestedDisplay);
const display = Tankopedia.use((state) => state.display);
const factor =
!hasPbr && display !== TankopediaDisplay.StaticArmor ? NON_PBR_FACTOR : 1;
useEffect(() => {
invalidate();
}, [requestedDisplay === display]);
const apply = useCallback((t: number) => {
if (!wrapper.current) return;
for (const child of wrapper.current.children) {
if (child instanceof SpotLight) {
child.angle = ANGLE * t;
child.angle = ANGLE * t * factor;
} else if (child instanceof HemisphereLight) {
child.intensity = HEMISPHERE_INTENSITY * t;
child.intensity = HEMISPHERE_INTENSITY * t * factor;
}
}
@@ -1,7 +1,8 @@
import { mauveDark } from "@radix-ui/colors";
import { invalidate } from "@react-three/fiber";
import { Quicklime, type QuicklimeEvent } from "quicklime";
import { useEffect, useRef } from "react";
import { Mesh, MeshStandardMaterial } from "three";
import { useCallback, useEffect, useRef } from "react";
import { Mesh, type WebGLProgramParametersWithUniforms } from "three";
const SIZE = 2 ** 4;
@@ -9,7 +10,6 @@ export const screenshotReadyEvent = new Quicklime(false);
export function SceneProps() {
const mesh = useRef<Mesh>(null!);
const material = useRef<MeshStandardMaterial>(null);
useEffect(() => {
function handleScreenshotReady(event: QuicklimeEvent<boolean>) {
@@ -24,19 +24,62 @@ export function SceneProps() {
};
}, []);
const handleBeforeCompile = useCallback(
(shader: WebGLProgramParametersWithUniforms) => {
shader.uniforms.size = { value: SIZE / 2 };
shader.vertexShader = `
varying vec3 vWorldPosition;
${shader.vertexShader}
`.replace(
"#include <worldpos_vertex>",
`
#include <worldpos_vertex>
vWorldPosition = (modelMatrix * vec4(transformed, 1.0)).xyz;
`,
);
shader.fragmentShader = `
varying vec3 vWorldPosition;
uniform float size;
${shader.fragmentShader}
`.replace(
"#include <color_fragment>",
`
#include <color_fragment>
vec3 scaled = vWorldPosition / size;
float radius = length(scaled.xz);
if (radius >= 1.0) {
discard;
}
diffuseColor.a *= (1.0 - radius);
`,
);
},
[],
);
return (
<mesh
position={[0, -(2 ** -8), 0]}
rotation={[-Math.PI / 2, 0, 0]}
receiveShadow
visible={!screenshotReadyEvent.last!}
ref={mesh}
receiveShadow
>
<planeGeometry args={[SIZE, SIZE]} />
<meshStandardMaterial
ref={material}
color="black"
color={mauveDark.mauve6}
roughness={1}
metalness={0}
transparent
onBeforeCompile={handleBeforeCompile}
/>
</mesh>
);
@@ -5,11 +5,11 @@ import {
useImperativeHandle,
useRef,
} from "react";
import { Fog } from "three";
import { applyPitchYawLimits } from "../../../../../core/blitz/applyPitchYawLimits";
import { modelTransformEvent } from "../../../../../core/blitzkit/modelTransform";
import { Pose, poseEvent } from "../../../../../core/blitzkit/pose";
import { useEquipment } from "../../../../../hooks/useEquipment";
import { useModel } from "../../../../../hooks/useModel";
import { useTankModelDefinition } from "../../../../../hooks/useTankModelDefinition";
import { Duel } from "../../../../../stores/duel";
import { Tankopedia } from "../../../../../stores/tankopedia";
@@ -50,6 +50,9 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
(state) => state.hideTankModelUnderArmor,
);
const id = Duel.use((state) => state.protagonist.tank.id);
const { hasPbr } = useModel(id);
useImperativeHandle(ref, () => canvas.current!, []);
function handlePointerDown() {
@@ -150,7 +153,6 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
<SmartCanvas
resize={{ debounce: 0 }}
ref={canvas}
scene={{ fog: new Fog("black", 30, 50) }}
gl={{
clippingPlanes: [],
localClippingEnabled: true,
@@ -171,7 +173,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
outline: undefined,
}}
>
<Lighting />
<Lighting hasPbr={hasPbr} />
<SceneProps />
{(display === TankopediaDisplay.Model ||
Loaded 100 of 153 files, more files were not shown because too many files have changed in this diff. Show more