Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
080f0a0eb7 | ||
|
|
a646cc1e57 | ||
|
|
d107813c37 | ||
|
|
27d3bbf346 | ||
|
|
50c77e0ae1 | ||
|
|
6e0429332d | ||
|
|
7afce6cbcb | ||
|
|
17c7a5c7f5 | ||
|
|
f5aa2ff9de | ||
|
|
96a8f225fc | ||
|
|
d15a5aee69 | ||
|
|
84dc5e4960 | ||
|
|
58b5ee7f83 | ||
|
|
8a99d34af9 | ||
|
|
7cd7bd83dc | ||
|
|
c29d36a4b7 | ||
|
|
7d3b6221d7 | ||
|
|
fd66f9ab17 | ||
|
|
6fbb0e81cb | ||
|
|
6dc82588c1 | ||
|
|
c6eae9ad51 | ||
|
|
ebdd7629ff | ||
|
|
646bccf370 | ||
|
|
86206f2d89 | ||
|
|
bc0dd87e31 | ||
|
|
e26bb4efe0 | ||
|
|
66cf1fd223 | ||
|
|
335cd4d699 | ||
|
|
95fa5bc1a5 | ||
|
|
50eac9eba6 | ||
|
|
1598ae973f | ||
|
|
69f3a95379 | ||
|
|
3846cbab4a | ||
|
|
30e87743fc | ||
|
|
fe4afdcbf0 | ||
|
|
ef2a47e756 | ||
|
|
c2d625e1ed | ||
|
|
d33990bdb4 | ||
|
|
96482d4268 | ||
|
|
8b527f2333 | ||
|
|
f56c31fb48 | ||
|
|
7d164e415a | ||
|
|
b3a9f0889e | ||
|
|
6d24ed92d0 | ||
|
|
d287e66265 | ||
|
|
ac29d1ead0 | ||
|
|
b6c3fdc4e9 | ||
|
|
4efdaaf7c9 | ||
|
|
87b6a36520 | ||
|
|
cf38cb3a6e | ||
|
|
97fc386bc7 | ||
|
|
abfb3f8ddb | ||
|
|
9cb5113f6e | ||
|
|
7dd1c0c32a | ||
|
|
03233f3fdc | ||
|
|
377159c298 | ||
|
|
d8431fb620 | ||
|
|
0546ddefbd | ||
|
|
26f6889012 | ||
|
|
3ad6088565 | ||
|
|
b6fd568cc6 | ||
|
|
3861b07aef | ||
|
|
2bf57e2c72 | ||
|
|
744c6654cf | ||
|
|
b82e905757 | ||
|
|
c42e5b7dc3 | ||
|
|
b716218ecd | ||
|
|
511346e4bb | ||
|
|
5c2e49453e | ||
|
|
5d397de766 | ||
|
|
aa763f65d1 | ||
|
|
94cbd95509 | ||
|
|
f3762e356f | ||
|
|
5747d7549f | ||
|
|
aea4905d20 | ||
|
|
1558b69931 | ||
|
|
a560a3d3ae | ||
|
|
b620110d0f | ||
|
|
23ea72bf40 | ||
|
|
aaee0b7965 | ||
|
|
347523f9e7 | ||
|
|
3205630a0f | ||
|
|
8446e3fbbd | ||
|
|
6d25a97fc8 | ||
|
|
bdbfce0e18 | ||
|
|
8033e8f7cc | ||
|
|
f98c801d6c | ||
|
|
339663adaf | ||
|
|
2b15357dee | ||
|
|
1e05717daf | ||
|
|
c20f8c5040 | ||
|
|
f22f4eeccd | ||
|
|
79c450798c | ||
|
|
d247e67690 | ||
|
|
69b2035cfc | ||
|
|
62fed63310 | ||
|
|
7c29046390 | ||
|
|
b65c3c1d93 | ||
|
|
87f5d8fe8f | ||
|
|
525b9377cc | ||
|
|
94cba0c9ba | ||
|
|
4ddf77fc7f | ||
|
|
ae6dc259dc | ||
|
|
c2e95f6ef6 | ||
|
|
868812e578 | ||
|
|
b5064f9e8b | ||
|
|
a0eb0d54f0 | ||
|
|
e47b1315a9 | ||
|
|
cde6904513 | ||
|
|
997d1df8f9 | ||
|
|
9f15ded0c9 | ||
|
|
b2ee97b3d4 | ||
|
|
03d819a6c1 | ||
|
|
8ff26d57f6 | ||
|
|
c243ebd19a | ||
|
|
40f5042896 | ||
|
|
21130b29f8 | ||
|
|
d50b054243 | ||
|
|
6b7f8049fe | ||
|
|
e6029157aa | ||
|
|
ba656eb603 | ||
|
|
e8dd26b2ba | ||
|
|
897da9479d | ||
|
|
291395718f | ||
|
|
42e860b171 | ||
|
|
3b6d339310 | ||
|
|
728fd5b824 | ||
|
|
34217b2754 | ||
|
|
0b5c88531f | ||
|
|
e145d7c6e8 | ||
|
|
86f4712b87 | ||
|
|
ea90ca21b1 | ||
|
|
b3046d8655 |
No files matched your search
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"indentSize": 2
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
name: Build Assets
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: main
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
cdn_branch:
|
||||
type: string
|
||||
required: false
|
||||
default: main
|
||||
description: What CDN branch to write to
|
||||
zip_url:
|
||||
type: string
|
||||
required: false
|
||||
description: ZIP URL to download instead of Steam client
|
||||
zip_root:
|
||||
type: string
|
||||
required: false
|
||||
description: Root dir of ZIP which houses the executable and Data dir
|
||||
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.cdn_branch }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||
WOTB_DLC_CDN: ${{ secrets.WOTB_DLC_CDN }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_ASSET_REPO: blitzkit/assets
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
|
||||
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
|
||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||
ALLOW_ROBOTS: true
|
||||
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
||||
PUBLIC_PATREON_CLIENT_ID: ${{ secrets.PUBLIC_PATREON_CLIENT_ID }}
|
||||
PUBLIC_PATREON_REDIRECT_URI: ${{ secrets.PUBLIC_PATREON_REDIRECT_URI }}
|
||||
PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }}
|
||||
PUBLIC_NITROPAY_ID: ${{ secrets.PUBLIC_NITROPAY_ID }}
|
||||
PUBLIC_DEBUG_MISSING_I18N: false
|
||||
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||
PUBLIC_ASSET_FAST_USER: blitzkit
|
||||
PUBLIC_ASSET_FAST_REPO: assets
|
||||
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
|
||||
|
||||
jobs:
|
||||
download_client:
|
||||
name: Download client
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: (ZIP) Maximize build space
|
||||
if: github.event.inputs.zip_url != ''
|
||||
uses: easimon/maximize-build-space@master
|
||||
with:
|
||||
root-reserve-mb: 512
|
||||
swap-size-mb: 1024
|
||||
remove-dotnet: 'true'
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: (Steam) Setup bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
if: github.event.inputs.zip_url == ''
|
||||
|
||||
- name: (Steam) Install dependencies
|
||||
if: github.event.inputs.zip_url == ''
|
||||
run: bun install
|
||||
|
||||
- name: (Steam) Setup DepotDownloader
|
||||
if: github.event.inputs.zip_url == ''
|
||||
run: bun run setup:depotdownloader
|
||||
|
||||
- name: (Steam) Download client
|
||||
if: github.event.inputs.zip_url == ''
|
||||
run: |
|
||||
chmod +x ./temp/depotDownloader/DepotDownloader
|
||||
./temp/depotDownloader/DepotDownloader -q -os windows -app 444200 -username ${{ secrets.DEPOT_DOWNLOADER_USERNAME }} -password "${{ secrets.DEPOT_DOWNLOADER_PASSWORD }}"
|
||||
|
||||
- name: (Steam) Apply patches
|
||||
if: github.event.inputs.zip_url == ''
|
||||
run: bun setup:patches --depot
|
||||
|
||||
- name: (ZIP) Download client
|
||||
if: github.event.inputs.zip_url != ''
|
||||
env:
|
||||
ZIP_URL: ${{ github.event.inputs.zip_url }}
|
||||
ZIP_ROOT: ${{ github.event.inputs.zip_root }}
|
||||
run: |
|
||||
wget -q -O client.zip $ZIP_URL
|
||||
unzip -q client.zip -d client
|
||||
rm client.zip
|
||||
mkdir -p depots/444202/0
|
||||
mv client/$ZIP_ROOT/* depots/444202/0
|
||||
|
||||
- name: Archive depots
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: depots
|
||||
path: depots
|
||||
|
||||
build_assets:
|
||||
name: Build assets
|
||||
runs-on: ubuntu-latest
|
||||
needs: download_client
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Setup bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Lint
|
||||
run: bun run lint:website
|
||||
|
||||
- name: Download depots
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: depots
|
||||
path: depots
|
||||
|
||||
- name: AppArmor hack
|
||||
run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
|
||||
|
||||
- name: Start server and build assets
|
||||
run: |
|
||||
bun dev:website --verbosepa &
|
||||
SERVER_PID=$!
|
||||
|
||||
bun run build:assets --all-targets --depot --production
|
||||
|
||||
kill $SERVER_PID || true
|
||||
@@ -0,0 +1,49 @@
|
||||
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 }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Build averages
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
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
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Discover IDs
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * 1'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Discover IDs
|
||||
run: bun build:ids --production
|
||||
@@ -22,6 +22,19 @@ env:
|
||||
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: ${{ secrets.ALLOW_ROBOTS }}
|
||||
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 }}
|
||||
PUBLIC_GUIDE_WEBSITE: ${{ secrets.PUBLIC_GUIDE_WEBSITE }}
|
||||
SERVICING: ${{ secrets.SERVICING }}
|
||||
|
||||
jobs:
|
||||
host:
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Rating Asia
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 17 * * *'
|
||||
|
||||
jobs:
|
||||
host:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate leaderboard
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
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 }}
|
||||
run: bun build:ratingleaderboard --region=asia
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Rating Europe
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 23 * * *'
|
||||
|
||||
jobs:
|
||||
host:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate leaderboard
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
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 }}
|
||||
run: bun build:ratingleaderboard --region=eu
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Rating North America
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 5 * * *'
|
||||
|
||||
jobs:
|
||||
host:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Generate leaderboard
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
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 }}
|
||||
run: bun build:ratingleaderboard --region=com
|
||||
@@ -17,34 +17,33 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
env:
|
||||
AUTO_PROXY_ORIGIN: ${{ secrets.AUTO_PROXY_ORIGIN }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||
ALLOW_ROBOTS: ${{ secrets.ALLOW_ROBOTS }}
|
||||
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 }}
|
||||
PUBLIC_GUIDE_WEBSITE: ${{ secrets.PUBLIC_GUIDE_WEBSITE }}
|
||||
SERVICING: ${{ secrets.SERVICING }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- name: Configure pages
|
||||
uses: actions/configure-pages@v5
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: actions/configure-pages@v5
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
@@ -58,12 +57,13 @@ jobs:
|
||||
path: dist/website
|
||||
|
||||
deploy-github-pages:
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
name: Deploy to GitHub Pages
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: >
|
||||
(github.repository == 'tresabhi/blitzkit' && github.ref == 'refs/heads/main') ||
|
||||
(github.repository == 'blitzkit/host-redux' && github.ref == 'refs/heads/ue4')
|
||||
environment:
|
||||
name: Production
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
|
||||
@@ -22,232 +22,9 @@ depots
|
||||
public/sitemap*
|
||||
/firebase.json
|
||||
target
|
||||
# packages/core/src/protos/*.ts
|
||||
/.vercel
|
||||
/.astro
|
||||
.vercel
|
||||
/packages/bot/src/prisma
|
||||
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.userprefs
|
||||
mono_crash.*
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
[Aa][Rr][Mm]64[Ee][Cc]/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
.vs/
|
||||
Generated\ Files/
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
BenchmarkDotNet.Artifacts/
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
ScaffoldingReadMe.txt
|
||||
StyleCopReport.xml
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.idb
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
_Chutzpah*
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
*.e2e
|
||||
$tf/
|
||||
*.gpState
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
_TeamCity*
|
||||
*.dotCover
|
||||
.axoCover/*
|
||||
|
||||
|
||||
!.axoCover/settings.json
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
_NCrunch_*
|
||||
.NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
.sass-cache/
|
||||
[Ee]xpress/
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
publish/
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
PublishScripts/
|
||||
|
||||
*.nupkg
|
||||
*.snupkg
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
csx/
|
||||
|
||||
*.build.csdef
|
||||
ecf/
|
||||
rcf/
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
*.[Cc]ache
|
||||
!?*.[Cc]ache/
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
Generated_Code/
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
FakesAssemblies/
|
||||
*.GhostDoc.xml
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
*.plg
|
||||
*.opt
|
||||
*.vbw
|
||||
*.vbp
|
||||
*.dsw
|
||||
*.dsp
|
||||
*.ncb
|
||||
*.aps
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
.fake/
|
||||
.cr/personal
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
*.tss
|
||||
*.jmconfig
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
OpenCover/
|
||||
ASALocalRun/
|
||||
*.binlog
|
||||
.aws-sam
|
||||
*.nvuser
|
||||
.mfractor/
|
||||
.localhistory/
|
||||
.vshistory/
|
||||
healthchecksdb
|
||||
MigrationBackup/
|
||||
.ionide/
|
||||
FodyWeavers.xsd
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
.history/
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
*.sln.iml
|
||||
/src
|
||||
/packages/bot/src/prisma
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 80
|
||||
}
|
||||
@@ -51,7 +51,6 @@
|
||||
"CUBEMAP",
|
||||
"CUBETEXCOORD",
|
||||
"customperiod",
|
||||
"Datacard",
|
||||
"datasource",
|
||||
"Dava",
|
||||
"dbid",
|
||||
@@ -69,7 +68,6 @@
|
||||
"DIDS",
|
||||
"donotremove",
|
||||
"drei",
|
||||
"dsapp",
|
||||
"dvpd",
|
||||
"dvpl",
|
||||
"DVPLCRC",
|
||||
@@ -131,7 +129,6 @@
|
||||
"noout",
|
||||
"normalmap",
|
||||
"oneof",
|
||||
"oodle",
|
||||
"oopsies",
|
||||
"Oopsy",
|
||||
"opentest",
|
||||
@@ -170,6 +167,7 @@
|
||||
"searchtanks",
|
||||
"SHAREDEXP",
|
||||
"SKLLD",
|
||||
"smoothstep",
|
||||
"Smri",
|
||||
"Specularity",
|
||||
"SSAO",
|
||||
@@ -196,8 +194,6 @@
|
||||
"Turretless",
|
||||
"TYPELESS",
|
||||
"ubjson",
|
||||
"Uncompiled",
|
||||
"undiscriminated",
|
||||
"Unicum",
|
||||
"UNLOCALIZED",
|
||||
"UNORM",
|
||||
@@ -227,12 +223,7 @@
|
||||
"yolo",
|
||||
"youtuber",
|
||||
"youtubers",
|
||||
"zustand"
|
||||
"zoomable"
|
||||
],
|
||||
"deno.enable": false,
|
||||
"deno.unstable": false,
|
||||
"cSpell.enableFiletypes": ["glsl", "proto3"],
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"liveServer.settings.multiRootWorkspaceName": "blitzkit",
|
||||
"git.ignoreLimitWarning": true
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/BlitzKit.sln",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/BlitzKit.sln",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"watch",
|
||||
"run",
|
||||
"--project",
|
||||
"${workspaceFolder}/BlitzKit.sln"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F23F76D4-C350-4BBA-9DF1-E67C5CD391B1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlitzKit.FFI", "src\BlitzKit.FFI\BlitzKit.FFI.csproj", "{8E798C66-C863-4A67-87EA-853109EEE037}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{8E798C66-C863-4A67-87EA-853109EEE037}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8E798C66-C863-4A67-87EA-853109EEE037}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8E798C66-C863-4A67-87EA-853109EEE037}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8E798C66-C863-4A67-87EA-853109EEE037}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{8E798C66-C863-4A67-87EA-853109EEE037} = {F23F76D4-C350-4BBA-9DF1-E67C5CD391B1}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -11,12 +11,3 @@ Your premier source for all things Blitz.
|
||||
Go to [application directory for BlitzKit](https://discord.com/application-directory/1097673957865443370) and click "Add to Server." Voila! You're ready to go.
|
||||
|
||||

|
||||
|
||||
## Naming Guide
|
||||
|
||||
| Type | Style | Example |
|
||||
| --------- | -------------- | ---------------------------------------------------------------- |
|
||||
| In-text | CamelCase | BlitzKit |
|
||||
| Code | lowerCamelCase | `let blitzkit = { ... }`, `function fetchBlitzkitData() { ... }` |
|
||||
| URLs | kebab-case | `https://example.com/blitzkit/` |
|
||||
| Constants | SNAKE_CASE | `const BLITZKIT_VERSION = "1.0.0"` |
|
||||
@@ -1,96 +0,0 @@
|
||||
{
|
||||
"name": "blitzkit",
|
||||
"private": true,
|
||||
"version": "1.27.11",
|
||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||
"type": "module",
|
||||
"types": "./types.d.ts",
|
||||
"workspaces": ["packages/*", "submodules/*"],
|
||||
"scripts": {
|
||||
"build:assets": "cd packages/scripts && bun run build:assets",
|
||||
"build:averages": "bun --filter=@blitzkit/scripts run build:averages",
|
||||
"build:bot": "cd packages/scripts && bun run build:bot",
|
||||
"build:website": "bun build:prisma --no-hints && cd packages/website && bun run build",
|
||||
"build:prisma": "prisma generate",
|
||||
"build:sitemap": "bun scripts/sitemap.ts",
|
||||
"build:docs": "vitepress build docs",
|
||||
"build:ratingleaderboard": "cd packages/scripts && bun run build:ratingleaderboard",
|
||||
"dev:bot": "bun run build:bot --dev && bun start:bot",
|
||||
"start:bot": "bun dist/bot/main.js",
|
||||
"dev:docs": "vitepress dev docs",
|
||||
"dev:website": "cd packages/website-ue && bun run dev",
|
||||
"dev:api": "bun --filter=@blitzkit/api run dev",
|
||||
"setup:depotdownloader": "bun --filter=@blitzkit/scripts run setup:depotdownloader",
|
||||
"setup:patches": "bun --filter=@blitzkit/scripts run setup:patches",
|
||||
"start:website": "bun --filter=@blitzkit/website run start",
|
||||
"host:docs": "vitepress preview docs",
|
||||
"lint": "tsc",
|
||||
"lint:website": "cd packages/website && bun run lint",
|
||||
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"",
|
||||
"update": "yarn upgrade --latest",
|
||||
"tunnel": "cloudflared tunnel --url http://localhost:3000",
|
||||
"discoverids": "scripts/discoverIds.ts",
|
||||
"build:protos": "bun packages/scripts/src/protos.ts",
|
||||
"preview:website": "cd packages/website && bun run preview",
|
||||
"build:reviews": "cd packages/scripts && bun build:reviews",
|
||||
"build:ids": "cd packages/scripts && bun build:ids",
|
||||
"prunestrings": "cd packages/scripts && bun prunestrings"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"satori": "^0.10.14",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"discord.js": "^14.16.3",
|
||||
"bepaint": "^0.1.0-beta.3",
|
||||
"markdown-escape": "^2.0.0",
|
||||
"@types/markdown-escape": "^1.1.3",
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/lz4js": "^0.2.1",
|
||||
"@octokit/plugin-throttling": "^8.2.0",
|
||||
"@octokit/rest": "^20.1.1",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"crc": "^4.3.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"lz4js": "^0.2.0",
|
||||
"yaml": "^2.5.0",
|
||||
"ts-deepmerge": "^7.0.2",
|
||||
"@types/decompress": "^4.2.7",
|
||||
"@types/progress": "^2.0.7",
|
||||
"@gltf-transform/core": "^4.0.10",
|
||||
"@gltf-transform/extensions": "^4.0.10",
|
||||
"@gltf-transform/functions": "^4.0.10",
|
||||
"csv-parse": "^5.5.6",
|
||||
"decompress": "^4.2.1",
|
||||
"esbuild": "^0.19.12",
|
||||
"fast-xml-parser": "^4.4.1",
|
||||
"progress": "^2.0.3",
|
||||
"puppeteer": "^23.10.4",
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@playform/compress": "^0.1.9",
|
||||
"@playform/inline": "^0.1.2",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"astro-compressor": "^1.0.0",
|
||||
"@fontsource/inter": "^5.2.5",
|
||||
"@nanostores/persistent": "^0.10.2",
|
||||
"@nanostores/react": "^0.7.3",
|
||||
"@nivo/bar": "^0.87.0",
|
||||
"@nivo/line": "^0.88.0",
|
||||
"compare-versions": "^6.1.1",
|
||||
"echarts": "^5.5.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"markdown-to-jsx": "^7.5.0",
|
||||
"nanostores": "^0.11.3",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-promise-suspense": "^0.3.4",
|
||||
"urlon": "^3.1.0",
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^22.14.1",
|
||||
"prisma": "^6.6.0",
|
||||
"typescript": "^5.8.3",
|
||||
"googleapis": "^144.0.0",
|
||||
"protobufjs": "^7.5.2",
|
||||
"quicklime": "^1.2.0",
|
||||
"sharp": "^0.33.5"
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
{
|
||||
"name": "blitzkit",
|
||||
"private": true,
|
||||
"version": "1.27.11",
|
||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||
"type": "module",
|
||||
"types": "./types.d.ts",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"submodules/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build:assets": "cd packages/scripts && bun run build:assets",
|
||||
"build:averages": "bun --filter=@blitzkit/scripts run build:averages",
|
||||
"build:bot": "cd packages/scripts && bun run build:bot",
|
||||
"build:website": "bun build:prisma --no-hints && cd packages/website && bun run build",
|
||||
"build:prisma": "prisma generate",
|
||||
"build:sitemap": "bun scripts/sitemap.ts",
|
||||
"build:docs": "vitepress build docs",
|
||||
"build:ratingleaderboard": "cd packages/scripts && bun run build:ratingleaderboard",
|
||||
"dev:bot": "bun run build:bot --dev && bun start:bot",
|
||||
"start:bot": "bun dist/bot/main.js",
|
||||
"dev:docs": "vitepress dev docs",
|
||||
"dev:website": "cd packages/website-ue && bun run dev",
|
||||
"dev:api": "bun --filter=@blitzkit/api run dev",
|
||||
"setup:depotdownloader": "bun --filter=@blitzkit/scripts run setup:depotdownloader",
|
||||
"setup:patches": "bun --filter=@blitzkit/scripts run setup:patches",
|
||||
"start:website": "bun --filter=@blitzkit/website run start",
|
||||
"host:docs": "vitepress preview docs",
|
||||
"lint": "tsc",
|
||||
"lint:website": "cd packages/website && bun run lint",
|
||||
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"",
|
||||
"update": "yarn upgrade --latest",
|
||||
"tunnel": "cloudflared tunnel --url http://localhost:3000",
|
||||
"discoverids": "scripts/discoverIds.ts",
|
||||
"build:protos": "bun packages/scripts/src/protos.ts",
|
||||
"preview:website": "cd packages/website && bun run preview",
|
||||
"build:reviews": "cd packages/scripts && bun build:reviews",
|
||||
"build:ids": "cd packages/scripts && bun build:ids",
|
||||
"prunestrings": "cd packages/scripts && bun prunestrings"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"satori": "^0.10.14",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"discord.js": "^14.16.3",
|
||||
"fuzzysort": "^2.0.4",
|
||||
"bepaint": "^0.1.0-beta.3",
|
||||
"markdown-escape": "^2.0.0",
|
||||
"@types/markdown-escape": "^1.1.3",
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/lz4js": "^0.2.1",
|
||||
"@octokit/plugin-throttling": "^8.2.0",
|
||||
"@octokit/rest": "^20.1.1",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"crc": "^4.3.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"lz4js": "^0.2.0",
|
||||
"ts-proto": "^2.2.2",
|
||||
"yaml": "^2.5.0",
|
||||
"ts-deepmerge": "^7.0.2",
|
||||
"@types/decompress": "^4.2.7",
|
||||
"@types/progress": "^2.0.7",
|
||||
"@gltf-transform/core": "^4.0.10",
|
||||
"@gltf-transform/extensions": "^4.0.10",
|
||||
"@gltf-transform/functions": "^4.0.10",
|
||||
"csv-parse": "^5.5.6",
|
||||
"decompress": "^4.2.1",
|
||||
"esbuild": "^0.19.12",
|
||||
"fast-xml-parser": "^4.4.1",
|
||||
"progress": "^2.0.3",
|
||||
"puppeteer": "^23.10.4",
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"@playform/compress": "^0.1.9",
|
||||
"@playform/inline": "^0.1.2",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/three": "^0.160.0",
|
||||
"astro-compressor": "^1.0.0",
|
||||
"@astrojs/partytown": "^2.1.4",
|
||||
"@astrolib/seo": "^1.0.0-beta.8",
|
||||
"@fontsource/inter": "^5.2.5",
|
||||
"@nanostores/persistent": "^0.10.2",
|
||||
"@nanostores/react": "^0.7.3",
|
||||
"@nivo/bar": "^0.87.0",
|
||||
"@nivo/line": "^0.88.0",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/themes": "^3.1.4",
|
||||
"@react-three/drei": "^9.115.0",
|
||||
"@react-three/fiber": "^8.17.10",
|
||||
"compare-versions": "^6.1.1",
|
||||
"echarts": "^5.5.1",
|
||||
"immer": "^10.1.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"markdown-to-jsx": "^7.5.0",
|
||||
"nanostores": "^0.11.3",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-promise-suspense": "^0.3.4",
|
||||
"three": "^0.169.0",
|
||||
"urlon": "^3.1.0",
|
||||
"zustand": "^4.5.5",
|
||||
"@types/bun": "latest",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.14.1",
|
||||
"prettier": "^3.5.3",
|
||||
"prisma": "^6.6.0",
|
||||
"typescript": "^5.8.3",
|
||||
"googleapis": "^144.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"protobufjs": "^7.5.2",
|
||||
"quicklime": "^1.2.0",
|
||||
"sharp": "^0.33.5",
|
||||
"@blitzkit/core": "workspace:*",
|
||||
"@blitzkit/i18n": "workspace:*",
|
||||
"@astrojs/react": "^4.3.0",
|
||||
"@types/react": "^19.1.5",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"astro": "^5.8.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# BlitzKit 1.27.16
|
||||
|
||||
## Features
|
||||
|
||||
- Added links to GuidesBlitz to Tankopedia to aid users understand tanks beyond just numbers and stats
|
||||
- Added a gun depression and yaw visualizer
|
||||
|
||||
## Changes
|
||||
|
||||
- Made the colors of the advanced highlighting mode more subtle
|
||||
- Made the Tankopedia stat lines more condensed
|
||||
- You can now scroll the page even with your cursor on the Tankopedia sandbox
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fixed a bug where the range of increased gun depression was incorrectly doubled
|
||||
- Removed an unintended space before the "x" in the "Gun damaged" stat
|
||||
- Fixed an issue causing the 3D renderer to render frames even when off screen
|
||||
- Fixed a differential issue causing excessive rerendering of the stat lines
|
||||
- Images no longer exceed the width of the device in the docs
|
||||
- Assault shells no longer scale the module damage with distance
|
||||
|
||||
## Technical Changes
|
||||
|
||||
- Removed all code relating to the canceled "Shooting Range" mode
|
||||
@@ -0,0 +1,18 @@
|
||||
# BlitzKit 1.27.17
|
||||
|
||||
This update was released early for the website. Today, it'll be officially announced and released for the bot
|
||||
|
||||
## Features
|
||||
|
||||
- Added the shell reload visualizer to aid people in getting a feel for how long shell reloads really are
|
||||
- Added lines of stats for the burst shells
|
||||
|
||||
## Changes
|
||||
|
||||
- Removed the redundant word "Shell" from "Shell reload" and "Shell reloads"
|
||||
- Removed the penetration over distance slider
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fixed the DPM of tanks with burst shells like the ATAC
|
||||
- The DPM will still be different from the game due to a bug in World of Tanks Blitz; I am trying to get Wargaming to fix it; trust BlitzKit!
|
||||
@@ -0,0 +1,11 @@
|
||||
# BlitzKit 1.27.18
|
||||
|
||||
## Features
|
||||
|
||||
- Added a mini-game to BlitzKit similar to "Guess Who's That Pokemon" but with tanks: http://blitzkit.vercel.app/guess
|
||||
- Switched to a new renderer for better lighting for the Tankopedia
|
||||
|
||||
## Fixes
|
||||
|
||||
- The loading bar for the Tankopedia sandbox now shows up again
|
||||
- Removed a lot of dead code, improving performance
|
||||
@@ -0,0 +1,18 @@
|
||||
# BlitzKit 1.27.19
|
||||
|
||||
Two updates in a day? I just got a little excited by the new Tankopedia lighting.
|
||||
|
||||
## Features
|
||||
|
||||
- The camera is now lower down in the Tankopedia, giving the tank a more heroic feel
|
||||
- Tanks are better framed across all devices, minimizing whitespace
|
||||
- Added a skip button for the reload visualizer to skip to the next event
|
||||
|
||||
## Fixes
|
||||
|
||||
- Removed the useless "show grid" setting
|
||||
- Taking screenshots now no longer includes the scene props like the floor
|
||||
- The subtler color contrast for the advanced highlighting has now been reverted back to the higher contrast
|
||||
- The shot display card is now properly aligned as a proper table again
|
||||
- Tank modules are once again considered in the penetration tester
|
||||
- Tank filters in the antagonist selector in the Tankopedia sandbox are now properly aligned
|
||||
@@ -0,0 +1,13 @@
|
||||
# BlitzKit 1.27.20
|
||||
|
||||
It's all technical updates! The HD tanks will be rolled out over the span of few days.
|
||||
|
||||
## Technical Changes
|
||||
|
||||
- Shadows are disabled if your framerate drops below 30fps
|
||||
- Pages that redirect to other pages (like the old /tools/tankopedia) now load a few hundred times faster
|
||||
- Replaced Zustand with a custom state manager called Varuna with the goal to rewrite a significant portion of the website to not require any React for rendering increasing performance
|
||||
- The transition wireframe in the Tankopedia sandbox now uses a lower quality model for performance reasons
|
||||
- Tanks with PBR now use the correct maximum resolution textures
|
||||
- This is a significant update as you can now literally see individual splats of dust on tanks and scratches haha
|
||||
- Baked the ambient occlusion maps into the textures, hence reducing the overall model load times even with higher resolution textures
|
||||
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"name": "blitzkit",
|
||||
"private": true,
|
||||
"version": "1.27.15",
|
||||
"version": "1.27.20",
|
||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||
"type": "module",
|
||||
"types": "./types.d.ts",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"submodules/*"
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build:assets": "cd packages/scripts && bun run build:assets",
|
||||
"build:averages": "bun --filter=@blitzkit/scripts run build:averages",
|
||||
"build:bot": "cd packages/scripts && bun run build:bot",
|
||||
"build:website": "cd packages/website-ue && bun run build",
|
||||
"build:website": "bun build:prisma --no-hints && cd packages/website && bun run build",
|
||||
"build:prisma": "prisma generate",
|
||||
"build:sitemap": "bun scripts/sitemap.ts",
|
||||
"build:docs": "vitepress build docs",
|
||||
"build:proxyclient": "cd packages/scripts && bun run build:proxyclient",
|
||||
"build:ratingleaderboard": "cd packages/scripts && bun run build:ratingleaderboard",
|
||||
"dev:bot": "bun run build:bot --dev && bun start:bot",
|
||||
"start:bot": "bun dist/bot/main.js",
|
||||
@@ -31,21 +31,32 @@
|
||||
"lint": "tsc",
|
||||
"lint:website": "cd packages/website && bun run lint",
|
||||
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"",
|
||||
"update": "yarn upgace --latest",
|
||||
"update": "yarn upgrade --latest",
|
||||
"tunnel": "cloudflared tunnel --url http://localhost:3000",
|
||||
"discoverids": "scripts/discoverIds.ts",
|
||||
"build:protos": "bun packages/scripts/src/protos.ts",
|
||||
"preview:website": "cd packages/website && bun run preview",
|
||||
"build:reviews": "cd packages/scripts && bun build:reviews",
|
||||
"build:ids": "cd packages/scripts && bun build:ids",
|
||||
"prunestrings": "cd packages/scripts && bun prunestrings",
|
||||
"build:ffi": "bun build:windows:ffi",
|
||||
"build:windows:ffi": "dotnet publish src/BlitzKit.FFI -r win-x64 /p:PublishAot=true",
|
||||
"build:linux:ffi": "dotnet publish src/BlitzKit.FFI -r linux-x64 /p:PublishAot=true"
|
||||
"prunestrings": "cd packages/scripts && bun prunestrings"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/bun": "^1.2.15",
|
||||
"prettier": "^3.5.3"
|
||||
"@types/xmpp__client": "^0.13.3",
|
||||
"@xmpp/client": "^0.13.6",
|
||||
"cheerio": "^1.1.2",
|
||||
"googleapis": "^144.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"protobufjs": "^7.5.2",
|
||||
"quicklime": "^1.2.0",
|
||||
"sharp": "^0.33.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.14.1",
|
||||
"prettier": "^3.5.3",
|
||||
"prisma": "^6.6.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,24 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"types": "src/types.d.ts",
|
||||
"dependencies": {
|
||||
"@blitzkit/core": "workspace:*",
|
||||
"@blitzkit/i18n": "workspace:*",
|
||||
"satori": "^0.10.14",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"discord.js": "^14.16.3",
|
||||
"react": "^18.3.1",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"fuzzysort": "^2.0.4",
|
||||
"bepaint": "^0.1.0-beta.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"markdown-escape": "^2.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "bun --watch src/main.ts",
|
||||
"start": "bun src/main.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-escape": "^1.1.3"
|
||||
}
|
||||
}
|
||||
@@ -197,15 +197,10 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
|
||||
turret.guns.forEach((gun) => {
|
||||
if (
|
||||
gun.gun_type!.value.base.research_cost !== undefined &&
|
||||
typeof gun.gun_type!.value.base.research_cost
|
||||
.research_cost_type!.value === 'number'
|
||||
gun.research_cost !== undefined &&
|
||||
typeof gun.research_cost.research_cost_type!.value === 'number'
|
||||
) {
|
||||
gunXps.set(
|
||||
gun.gun_type!.value.base.id,
|
||||
gun.gun_type!.value.base.research_cost.research_cost_type!
|
||||
.value,
|
||||
);
|
||||
gunXps.set(gun.id, gun.research_cost.research_cost_type!.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ReadStream } from '@blitzkit/core';
|
||||
import AdmZip from 'adm-zip';
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
import { Parser } from 'pickleparser';
|
||||
import protobuf from 'protobufjs';
|
||||
import { ReadStream } from "@blitzkit/core";
|
||||
import AdmZip from "adm-zip";
|
||||
import { readFile } from "fs/promises";
|
||||
import { Parser } from "pickleparser";
|
||||
import protobuf from "protobufjs";
|
||||
|
||||
interface WotbReplayMetaJson {
|
||||
version: string;
|
||||
@@ -24,26 +24,24 @@ export class WotbReplayReadStream extends ReadStream {
|
||||
async wotbReplay() {
|
||||
const pickleParser = new Parser();
|
||||
const zip = new AdmZip(Buffer.from(this.buffer));
|
||||
const metaJsonBuffer = zip.getEntry('meta.json')?.getData();
|
||||
const battleResultsDat = zip.getEntry('battle_results.dat')?.getData();
|
||||
const metaJsonBuffer = zip.getEntry("meta.json")?.getData();
|
||||
const battleResultsDat = zip.getEntry("battle_results.dat")?.getData();
|
||||
|
||||
if (!metaJsonBuffer || !battleResultsDat)
|
||||
throw new SyntaxError('meta.json not found');
|
||||
throw new SyntaxError("meta.json not found");
|
||||
|
||||
const metaJson = JSON.parse(
|
||||
metaJsonBuffer.toString(),
|
||||
metaJsonBuffer.toString()
|
||||
) as WotbReplayMetaJson;
|
||||
const unpickledBattleResults = pickleParser.parse(battleResultsDat);
|
||||
const [, battleResultsString] = unpickledBattleResults as [number, string];
|
||||
const root = await protobuf.load(
|
||||
`${__dirname}/../../protos/BattleResultsClient.proto`,
|
||||
`${__dirname}/../../protos/BattleResultsClient.proto`
|
||||
);
|
||||
const BattleResults = root.lookupType(
|
||||
'BattleResultsGenerated.BattleResultsClient',
|
||||
"BattleResultsGenerated.BattleResultsClient"
|
||||
);
|
||||
|
||||
writeFile('test.dat', Buffer.from(battleResultsString));
|
||||
|
||||
const reader = protobuf.Reader.create(Buffer.from(battleResultsString));
|
||||
|
||||
// const decodedBattleResults = BattleResults.decodeDelimited(
|
||||
@@ -53,7 +51,7 @@ export class WotbReplayReadStream extends ReadStream {
|
||||
}
|
||||
}
|
||||
|
||||
const replay = await readFile('temp/test3.wotbreplay');
|
||||
const replay = await readFile("temp/test3.wotbreplay");
|
||||
const stream = new WotbReplayReadStream(replay.buffer as ArrayBuffer);
|
||||
|
||||
stream.wotbReplay();
|
||||
@@ -1,11 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"../core/src/blitzkit/supportedLocales.ts",
|
||||
"../i18n/src/unwrapper.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../../"
|
||||
}
|
||||
"include": ["src/**/*", "../core/src/blitzkit/supportedLocales.ts", "../i18n/src/unwrapper.ts"]
|
||||
}
|
||||
@@ -4,9 +4,20 @@
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"dependencies": {
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"js-yaml": "^4.1.0",
|
||||
"@octokit/plugin-throttling": "^8.2.0",
|
||||
"@octokit/rest": "^20.1.1",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"crc": "^4.3.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"lz4js": "^0.2.0",
|
||||
"pickleparser": "^0.2.1",
|
||||
"protobufjs": "^7.4.0",
|
||||
"ts-proto": "^2.2.2",
|
||||
"@blitzkit/closed": "workspace:*"
|
||||
"yaml": "^2.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/lz4js": "^0.2.1"
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
import { CatalogItem } from '@protos/blitz_items';
|
||||
import { Any } from '@protos/google/protobuf/any';
|
||||
import { lowerFirst } from 'lodash-es';
|
||||
import { compareUint8Arrays } from '../../../scripts/src/core/github/compareUint8Arrays';
|
||||
import { MessageFns } from '../protos';
|
||||
|
||||
export class CatalogItemAccessor {
|
||||
public id: string;
|
||||
components: Record<string, Any> = {};
|
||||
count: number;
|
||||
|
||||
constructor(private item: CatalogItem) {
|
||||
this.id = item.catalog_id;
|
||||
this.count = item.components.length;
|
||||
|
||||
for (const component of item.components) {
|
||||
if (
|
||||
component.value === undefined ||
|
||||
component.value.component === undefined
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.components[component.key] = component.value.component;
|
||||
}
|
||||
}
|
||||
|
||||
undiscriminatedId() {
|
||||
return this.id.split('.').slice(1).join('.');
|
||||
}
|
||||
|
||||
static fromComponent(id: string, component: Any) {
|
||||
return this.fromComponents(id, [component]);
|
||||
}
|
||||
|
||||
static fromComponents(id: string, components: Any[]) {
|
||||
return new CatalogItemAccessor({
|
||||
catalog_id: id,
|
||||
components: components.map((component) => ({
|
||||
key: lowerFirst(
|
||||
component.type_url.slice(2).split('.').slice(1).join('.'),
|
||||
),
|
||||
value: { type_url: component.type_url, component },
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
addComponent(key: string, value: Any) {
|
||||
this.components[key] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
getOptional<Type>(Message: MessageFns<Type>, component: string) {
|
||||
if (component in this.components) {
|
||||
return Message.decode(this.components[component].value);
|
||||
}
|
||||
}
|
||||
|
||||
get<Type>(Message: MessageFns<Type>, component: string) {
|
||||
const value = this.getOptional(Message, component);
|
||||
if (value === undefined) throw new Error(`Unknown component: ${component}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
pack() {
|
||||
const item: CatalogItem = { catalog_id: this.id, components: [] };
|
||||
|
||||
for (const componentId in this.components) {
|
||||
const component = this.components[componentId];
|
||||
|
||||
item.components.push({
|
||||
key: componentId,
|
||||
value: { type_url: component.type_url, component },
|
||||
});
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
encode() {
|
||||
return CatalogItem.encode(this.pack()).finish();
|
||||
}
|
||||
|
||||
equals(other: CatalogItemAccessor) {
|
||||
return (
|
||||
this.count === other.count &&
|
||||
this.item.components.every(
|
||||
(component) =>
|
||||
component.value === undefined ||
|
||||
(component.key in other.components &&
|
||||
(component.value.component === undefined ||
|
||||
compareUint8Arrays(
|
||||
other.components[component.key].value,
|
||||
component.value.component.value,
|
||||
))),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ export * from './getTankStats';
|
||||
export * from './idToRegion';
|
||||
export * from './isOnGoingRatingSeason';
|
||||
export * from './isValidBlitzId';
|
||||
export * from './metadata';
|
||||
export * from './nations';
|
||||
export * from './normalizeExtra';
|
||||
export * from './normalizeIds';
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
export class SocketMetadataAccessor {
|
||||
items: Record<string, CatalogItemAccessor> = {};
|
||||
|
||||
constructor(catalogItems: CatalogItem[]) {
|
||||
for (const item of catalogItems) {
|
||||
if (item.catalog_id in this.items) {
|
||||
throw new Error(`Duplicate catalog ID: ${item.catalog_id}`);
|
||||
}
|
||||
|
||||
this.items[item.catalog_id] = new CatalogItemAccessor(item);
|
||||
}
|
||||
}
|
||||
|
||||
get(item: string) {
|
||||
if (item in this.items) return this.items[item];
|
||||
throw new Error(`Unknown catalog ID: ${item}`);
|
||||
}
|
||||
|
||||
add(item: CatalogItemAccessor) {
|
||||
this.items[item.id] = item;
|
||||
return this;
|
||||
}
|
||||
|
||||
getAllTanks() {
|
||||
return this.get('CatalogItemsGroupEntity.all_tanks').get(
|
||||
CatalogItemsComponent,
|
||||
'catalogItemsGroupComponent',
|
||||
);
|
||||
}
|
||||
|
||||
injectBlitzkit() {
|
||||
this.injectBlitzKitTankComponents();
|
||||
this.injectBlitzKitAllTanksEntity();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
injectBlitzKitTankComponents() {
|
||||
for (const item of this.getAllTanks().catalog_items) {
|
||||
const tank = this.get(item);
|
||||
const value = BlitzKitTankCatalogComponent.encode({
|
||||
id: 'TEST',
|
||||
set: 'TEST',
|
||||
}).finish();
|
||||
|
||||
tank.addComponent('blitzkitTankCatalogComponent', {
|
||||
type_url:
|
||||
'./blitzkit_static_tank_component.BlitzKitTankCatalogComponent',
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
injectBlitzKitAllTanksEntity() {
|
||||
const allTanksEntities: BlitzkitAllTanksComponent = { tanks: [] };
|
||||
|
||||
for (const item of this.getAllTanks().catalog_items) {
|
||||
allTanksEntities.tanks.push(this.get(item).pack());
|
||||
}
|
||||
|
||||
this.add(
|
||||
CatalogItemAccessor.fromComponent(
|
||||
'BlitzKitAllTanksEntity.blitzkit_all_tanks',
|
||||
{
|
||||
type_url:
|
||||
'./blitzkit_static_all_tanks_component.BlitzkitAllTanksComponent',
|
||||
value: BlitzkitAllTanksComponent.encode(allTanksEntities).finish(),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoteMetadataAccessor {
|
||||
constructor(private base: string) {}
|
||||
|
||||
async get(item: string) {
|
||||
const response = await fetch(`${this.base}/${item}.pb`);
|
||||
|
||||
if (!response.ok) throw new Error(`Unknown catalog ID: ${item}`);
|
||||
|
||||
const buffer = await response.arrayBuffer();
|
||||
const catalogItem = CatalogItem.decode(new Uint8Array(buffer));
|
||||
|
||||
return new CatalogItemAccessor(catalogItem);
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -1,131 +0,0 @@
|
||||
import { CatalogItem } from '@protos/blitz_items';
|
||||
import { Metadata } from '@protos/blitz_metadata';
|
||||
import { LocalizationResourcesComponent } from '@protos/blitz_static_localization_resources_component';
|
||||
import { RemoteStorageComponent } from '@protos/blitz_static_remote_storage_component';
|
||||
import { load } from 'js-yaml';
|
||||
import { minimatch } from 'minimatch';
|
||||
import { CatalogItemAccessor } from 'packages/core/src/blitz/catalogItemAccessor';
|
||||
|
||||
interface LocalizationConfig {
|
||||
namespaces: string[];
|
||||
}
|
||||
|
||||
export class MetadataAccessor {
|
||||
items: Record<string, CatalogItem> = {};
|
||||
|
||||
constructor(metadata: Metadata) {
|
||||
for (const item of metadata.catalog_items) {
|
||||
this.items[item.catalog_id] = item;
|
||||
}
|
||||
}
|
||||
|
||||
get(item: string) {
|
||||
if (item in this.items) return new CatalogItemAccessor(this.items[item]);
|
||||
throw new Error(`Item ${item} does not exist in socket metadata`);
|
||||
}
|
||||
|
||||
add(item: CatalogItemAccessor) {
|
||||
this.items[item.id] = item.pack();
|
||||
}
|
||||
|
||||
filter(predicate: (item: string) => boolean) {
|
||||
const items: CatalogItemAccessor[] = [];
|
||||
|
||||
for (const item in this.items) {
|
||||
if (predicate(item)) {
|
||||
items.push(new CatalogItemAccessor(this.items[item]));
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
async strings(locale: string) {
|
||||
const clientConfig = await this.get(
|
||||
'ClientConfigsEntity.default_client_config',
|
||||
);
|
||||
const localizationResources = clientConfig.get(
|
||||
LocalizationResourcesComponent,
|
||||
'localizationResourcesComponent',
|
||||
);
|
||||
|
||||
if (!localizationResources.remote_storage) {
|
||||
throw new Error('Localization resources not found');
|
||||
}
|
||||
|
||||
const { relative_path } = localizationResources.remote_storage;
|
||||
|
||||
let remoteStorage: RemoteStorageComponent | undefined = undefined;
|
||||
|
||||
for (const candidateId of localizationResources.remote_storage.production) {
|
||||
const item = await this.get(candidateId);
|
||||
const candidate = item.get(
|
||||
RemoteStorageComponent,
|
||||
'remoteStorageComponent',
|
||||
);
|
||||
|
||||
if (
|
||||
remoteStorage === undefined ||
|
||||
candidate.download_speed_weight > remoteStorage.download_speed_weight
|
||||
) {
|
||||
remoteStorage = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (remoteStorage === undefined) {
|
||||
throw new Error('No suitable production remote storage found');
|
||||
}
|
||||
|
||||
const config = await fetch(
|
||||
`${remoteStorage.url}/${relative_path}/config.yaml`,
|
||||
)
|
||||
.then((response) => response.text())
|
||||
.then((text) => load(text) as LocalizationConfig);
|
||||
const strings: Record<string, string> = {};
|
||||
|
||||
for (const namespace of config.namespaces) {
|
||||
Object.assign(
|
||||
strings,
|
||||
await fetch(
|
||||
`${remoteStorage.url}/${relative_path}/${namespace}/${locale}.yaml`,
|
||||
)
|
||||
.then((response) => response.text())
|
||||
.then((text) => {
|
||||
const strings: Record<string, string> = {};
|
||||
const parsed = load(text) as Record<string, string>;
|
||||
|
||||
for (const key in parsed) {
|
||||
strings[key] = parsed[key].replaceAll('\\"', '"');
|
||||
}
|
||||
|
||||
return strings;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return strings;
|
||||
}
|
||||
|
||||
async stringsPartial(locale: string, patterns: string[]) {
|
||||
const strings = await this.strings(locale);
|
||||
const filtered: Record<string, string> = {};
|
||||
|
||||
for (const key in strings) {
|
||||
if (patterns.some((pattern) => minimatch(key, pattern))) {
|
||||
filtered[key] = strings[key];
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
toArray() {
|
||||
return Object.values(this.items);
|
||||
}
|
||||
|
||||
toSortedArray() {
|
||||
return this.toArray().sort((a, b) =>
|
||||
a.catalog_id.localeCompare(b.catalog_id),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { BlitzKitAvatars } from '@protos/blitzkit';
|
||||
import { MetadataAccessor } from '../../blitz/metadata';
|
||||
|
||||
export abstract class BlitzkitAPI {
|
||||
abstract metadata: MetadataAccessor;
|
||||
|
||||
abstract avatars(): Promise<BlitzKitAvatars>;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { BlitzKitAvatars } from '@protos/blitzkit';
|
||||
import { MetadataAccessor } from '../../blitz/metadata';
|
||||
import { BlitzkitAPI } from './abstract';
|
||||
|
||||
export class BlitzkitClientAPI extends BlitzkitAPI {
|
||||
get metadata(): MetadataAccessor {
|
||||
throw new Error('Cannot access metadata from client');
|
||||
}
|
||||
|
||||
async avatars() {
|
||||
const response = await fetch('/api/internal/avatars.pb');
|
||||
const buffer = await response.arrayBuffer();
|
||||
return BlitzKitAvatars.decode(new Uint8Array(buffer));
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './abstract';
|
||||
export * from './client';
|
||||
export * from './server';
|
||||
@@ -1,37 +0,0 @@
|
||||
import { ProfileAvatarComponent } from '@protos/blitz_static_profile_avatar_component';
|
||||
import { SellableComponent } from '@protos/blitz_static_sellable_component';
|
||||
import { StuffUIComponent } from '@protos/blitz_static_stuff_ui_component';
|
||||
import { BlitzKitAvatars } from '@protos/blitzkit';
|
||||
import { BlitzkitAPI } from '.';
|
||||
import { MetadataAccessor } from '../../blitz/metadata';
|
||||
|
||||
export class BlitzkitServerAPI extends BlitzkitAPI {
|
||||
constructor(public metadata: MetadataAccessor) {
|
||||
super();
|
||||
}
|
||||
|
||||
async avatars() {
|
||||
const avatars: BlitzKitAvatars = { avatars: [] };
|
||||
|
||||
for (const item of this.metadata.filter((item) =>
|
||||
item.startsWith('ProfileAvatarEntity.'),
|
||||
)) {
|
||||
const stuff = item.get(StuffUIComponent, 'UIComponent');
|
||||
const avatar = item.get(ProfileAvatarComponent, 'profileAvatarComponent');
|
||||
const sellable = item.getOptional(SellableComponent, 'sellableComponent');
|
||||
|
||||
avatars.avatars.push({
|
||||
id: item.undiscriminatedId(),
|
||||
name: stuff.display_name,
|
||||
obtaining: stuff.obtaining_methods,
|
||||
description: stuff.description,
|
||||
grade: stuff.grade,
|
||||
category: avatar.category,
|
||||
hidden_if_not_obtained: avatar.hidden_if_not_obtained,
|
||||
sale: sellable?.reward,
|
||||
});
|
||||
}
|
||||
|
||||
return avatars;
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,6 @@ export * from './uniqueGuns';
|
||||
export * from './unwrapBlitzkitResponse';
|
||||
export * from './votes';
|
||||
// export * from './withCORSProxy';
|
||||
export * from './api';
|
||||
export * from './createDefaultProvisions';
|
||||
export * from './gallery';
|
||||
export * from './metaSortTank';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
GunDefinition,
|
||||
GunDefinitionAutoReloaderProperties,
|
||||
GunDefinitionAutoReloader,
|
||||
ShellDefinition,
|
||||
} from '../protos';
|
||||
|
||||
@@ -15,34 +15,46 @@ export function resolveDpm(
|
||||
let dps: number;
|
||||
|
||||
if (gun.gun_type!.$case === 'regular') {
|
||||
dps = alpha / (reloadCoefficient * gun.gun_type!.value.extension.reload);
|
||||
dps = alpha / (reloadCoefficient * gun.gun_type!.value.reload);
|
||||
} else if (gun.gun_type!.$case === 'auto_loader') {
|
||||
dps =
|
||||
(alpha * gun.gun_type!.value.extension.shell_count) /
|
||||
(gun.gun_type!.value.extension.clip_reload * reloadCoefficient +
|
||||
(gun.gun_type!.value.extension.shell_count - 1) *
|
||||
gun.gun_type!.value.extension.intra_clip *
|
||||
intraClipCoefficient);
|
||||
} else {
|
||||
const mostOptimalShell =
|
||||
gun.gun_type!.value.extension.shell_reloads.reduce<null | {
|
||||
index: number;
|
||||
reload: number;
|
||||
}>((current, reloadRaw, index) => {
|
||||
const reload =
|
||||
reloadRaw * reloadCoefficient +
|
||||
(index > 0
|
||||
? (
|
||||
gun.gun_type!.value
|
||||
.extension as GunDefinitionAutoReloaderProperties
|
||||
).intra_clip * intraClipCoefficient
|
||||
: 0);
|
||||
const damage = alpha * gun.gun_type!.value.shell_count;
|
||||
let time = gun.gun_type!.value.clip_reload * reloadCoefficient;
|
||||
|
||||
if (current === null || reload < current.reload) {
|
||||
return { index, reload };
|
||||
}
|
||||
return current;
|
||||
}, null)!;
|
||||
if (gun.burst) {
|
||||
time +=
|
||||
(gun.gun_type!.value.shell_count / gun.burst.count - 1) *
|
||||
gun.gun_type!.value.intra_clip *
|
||||
intraClipCoefficient;
|
||||
|
||||
time +=
|
||||
(gun.gun_type!.value.shell_count / gun.burst.count) *
|
||||
(gun.burst.count - 1) *
|
||||
gun.burst.interval;
|
||||
} else {
|
||||
time +=
|
||||
(gun.gun_type!.value.shell_count - 1) *
|
||||
gun.gun_type!.value.intra_clip *
|
||||
intraClipCoefficient;
|
||||
}
|
||||
|
||||
dps = damage / time;
|
||||
} else {
|
||||
const mostOptimalShell = gun.gun_type!.value.shell_reloads.reduce<null | {
|
||||
index: number;
|
||||
reload: number;
|
||||
}>((current, reloadRaw, index) => {
|
||||
const reload =
|
||||
reloadRaw * reloadCoefficient +
|
||||
(index > 0
|
||||
? (gun.gun_type!.value as GunDefinitionAutoReloader).intra_clip *
|
||||
intraClipCoefficient
|
||||
: 0);
|
||||
|
||||
if (current === null || reload < current.reload) {
|
||||
return { index, reload };
|
||||
}
|
||||
return current;
|
||||
}, null)!;
|
||||
|
||||
dps = alpha / mostOptimalShell?.reload;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
|
||||
import { deburr } from 'lodash-es';
|
||||
import { SUPPORTED_LOCALES } from '../../../i18n/src/strings';
|
||||
import { I18nString } from '../protos';
|
||||
import { fetchCamouflageDefinitions } from './camouflageDefinitions';
|
||||
import { fetchTankDefinitions } from './tankDefinitions';
|
||||
@@ -26,8 +26,8 @@ export async function fetchTankNames() {
|
||||
searchableNameDeburr,
|
||||
camouflages: tank.camouflages
|
||||
?.map((id) =>
|
||||
locales.supported.map(
|
||||
({ locale }) =>
|
||||
SUPPORTED_LOCALES.map(
|
||||
(locale) =>
|
||||
camouflageDefinitions.camouflages[id]?.name.locales[locale],
|
||||
),
|
||||
)
|
||||
@@ -42,11 +42,9 @@ export async function fetchTankNames() {
|
||||
}
|
||||
|
||||
export const SEARCH_KEYS = [
|
||||
...locales.supported
|
||||
.map(({ locale }) => [
|
||||
`searchableName.locales.${locale}`,
|
||||
`searchableNameDeburr.locales.${locale}`,
|
||||
])
|
||||
.flat(),
|
||||
...SUPPORTED_LOCALES.map((locale) => [
|
||||
`searchableName.locales.${locale}`,
|
||||
`searchableNameDeburr.locales.${locale}`,
|
||||
]).flat(),
|
||||
'camouflages',
|
||||
];
|
||||
@@ -7,8 +7,8 @@ export function uniqueGuns(turrets: TurretDefinition[]) {
|
||||
|
||||
forEachRight(turrets, (turret) => {
|
||||
forEachRight(turret.guns, (gun) => {
|
||||
if (!gunIds.has(gun.gun_type!.value.base.id)) {
|
||||
gunIds.add(gun.gun_type!.value.base.id);
|
||||
if (!gunIds.has(gun.id)) {
|
||||
gunIds.add(gun.id);
|
||||
guns.push({ turret, gun });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -108,58 +108,47 @@ message GunDefinition {
|
||||
GunDefinitionAutoLoader auto_loader = 2;
|
||||
GunDefinitionAutoReloader auto_reloader = 3;
|
||||
}
|
||||
|
||||
required uint32 id = 4;
|
||||
required float rotation_speed = 5;
|
||||
optional ResearchCost research_cost = 6;
|
||||
required uint32 weight = 7;
|
||||
required I18nString name = 8;
|
||||
required uint32 tier = 9;
|
||||
repeated ShellDefinition shells = 10;
|
||||
required float camouflage_loss = 11;
|
||||
required float aim_time = 12;
|
||||
required float dispersion_base = 13;
|
||||
required float dispersion_traverse = 14;
|
||||
required float dispersion_shot = 15;
|
||||
required float dispersion_damaged = 16;
|
||||
repeated Unlock unlocks = 17;
|
||||
required uint32 shell_capacity = 18;
|
||||
optional AssaultRanges assault_ranges = 19;
|
||||
optional Burst burst = 20;
|
||||
}
|
||||
|
||||
message GunDefinitionRegular {
|
||||
required GunDefinitionBase base = 1;
|
||||
required GunDefinitionRegularProperties extension = 2;
|
||||
}
|
||||
|
||||
message GunDefinitionRegularProperties {
|
||||
required float reload = 1;
|
||||
}
|
||||
|
||||
message GunDefinitionAutoLoader {
|
||||
required GunDefinitionBase base = 1;
|
||||
required GunDefinitionAutoLoaderProperties extension = 2;
|
||||
}
|
||||
|
||||
message GunDefinitionAutoLoaderProperties {
|
||||
required float clip_reload = 1;
|
||||
required float intra_clip = 2;
|
||||
required float shell_count = 3;
|
||||
}
|
||||
|
||||
message GunDefinitionAutoReloader {
|
||||
required GunDefinitionBase base = 1;
|
||||
required GunDefinitionAutoReloaderProperties extension = 2;
|
||||
message Burst {
|
||||
required uint32 count = 1;
|
||||
required float interval = 2;
|
||||
}
|
||||
|
||||
message GunDefinitionAutoReloaderProperties {
|
||||
message GunDefinitionAutoReloader {
|
||||
repeated float shell_reloads = 1;
|
||||
required float intra_clip = 2;
|
||||
required float shell_count = 3;
|
||||
}
|
||||
|
||||
message GunDefinitionBase {
|
||||
required uint32 id = 1;
|
||||
required float rotation_speed = 2;
|
||||
optional ResearchCost research_cost = 3;
|
||||
required uint32 weight = 4;
|
||||
required I18nString name = 5;
|
||||
required uint32 tier = 6;
|
||||
repeated ShellDefinition shells = 7;
|
||||
required float camouflage_loss = 8;
|
||||
required float aim_time = 9;
|
||||
required float dispersion_base = 10;
|
||||
required float dispersion_traverse = 11;
|
||||
required float dispersion_shot = 12;
|
||||
required float dispersion_damaged = 13;
|
||||
repeated Unlock unlocks = 14;
|
||||
required uint32 shell_capacity = 15;
|
||||
optional AssaultRanges assault_ranges = 16;
|
||||
}
|
||||
|
||||
message AssaultRanges {
|
||||
repeated AssaultRange ranges = 1;
|
||||
repeated ShellType types = 2;
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src/**/*"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../../"
|
||||
}
|
||||
"include": ["src/**/*", "../i18n/src/strings.ts"]
|
||||
}
|
||||
@@ -2,5 +2,9 @@
|
||||
"name": "@blitzkit/i18n",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts"
|
||||
"main": "src/index.ts",
|
||||
"dependencies": {
|
||||
"ts-deepmerge": "^7.0.2"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
@@ -1,8 +1,89 @@
|
||||
import type en from '../strings/en.json';
|
||||
import { merge } from 'ts-deepmerge';
|
||||
import en from '../strings/en.json';
|
||||
import es from '../strings/es.json';
|
||||
import fr from '../strings/fr.json';
|
||||
import ja from '../strings/ja.json';
|
||||
import pl from '../strings/pl.json';
|
||||
import pt from '../strings/pt.json';
|
||||
import ru from '../strings/ru.json';
|
||||
import uk from '../strings/uk.json';
|
||||
import zh from '../strings/zh.json';
|
||||
|
||||
export type BlitzKitStrings = typeof en;
|
||||
|
||||
export const SUPPORTED_LOCALE_FLAGS: Record<string, string> = {
|
||||
const stringsRaw = { en, es, fr, ja, pl, pt, ru, uk, zh };
|
||||
|
||||
export const SUPPORTED_LOCALES = Object.keys(stringsRaw) as SupportedLocale[];
|
||||
|
||||
export const DEFAULT_LOCALE = SUPPORTED_LOCALES[0];
|
||||
|
||||
function redact(string: string) {
|
||||
const matches = [...string.matchAll(/%s\d+/g)];
|
||||
const characters = string
|
||||
.split('')
|
||||
.map((char) => (char === ' ' ? ' ' : '█')) as string[];
|
||||
|
||||
matches.forEach((match) => {
|
||||
const start = match.index;
|
||||
const matchText = match[0];
|
||||
for (let i = 0; i < matchText.length; i++) {
|
||||
characters[start + i] = matchText[i];
|
||||
}
|
||||
});
|
||||
|
||||
return characters.join('');
|
||||
}
|
||||
|
||||
function nuke(object: TranslationTree) {
|
||||
for (const key in object) {
|
||||
const typedKey = key as keyof typeof object;
|
||||
|
||||
if (typeof object[typedKey] === 'string') {
|
||||
const value = object[typedKey];
|
||||
object[typedKey] = redact(value as string);
|
||||
} else nuke(object[typedKey] as TranslationTree);
|
||||
}
|
||||
}
|
||||
|
||||
const stringsPartial: Partial<Record<SupportedLocale, TranslationTree>> = {};
|
||||
|
||||
for (const locale in stringsRaw) {
|
||||
if (locale === DEFAULT_LOCALE) {
|
||||
stringsPartial[DEFAULT_LOCALE] = stringsRaw[DEFAULT_LOCALE];
|
||||
continue;
|
||||
}
|
||||
|
||||
const localeStrings = stringsRaw[locale as SupportedLocale];
|
||||
stringsPartial[locale as SupportedLocale] = merge(
|
||||
stringsRaw[DEFAULT_LOCALE],
|
||||
localeStrings,
|
||||
);
|
||||
|
||||
if (import.meta.env.PUBLIC_DEBUG_MISSING_I18N === 'true') {
|
||||
nuke(stringsPartial[locale as SupportedLocale]!);
|
||||
}
|
||||
}
|
||||
|
||||
export const STRINGS = stringsPartial as Record<
|
||||
SupportedLocale,
|
||||
BlitzKitStrings
|
||||
>;
|
||||
|
||||
export type SupportedLocale = keyof typeof stringsRaw;
|
||||
|
||||
export const SUPPORTED_LOCALE_BLITZ_MAP: Record<SupportedLocale, string> = {
|
||||
en: 'en',
|
||||
es: 'es',
|
||||
fr: 'fr',
|
||||
ja: 'ja',
|
||||
pt: 'pt',
|
||||
ru: 'ru',
|
||||
uk: 'uk',
|
||||
zh: 'zh-Hans',
|
||||
pl: 'pl',
|
||||
};
|
||||
|
||||
export const SUPPORTED_LOCALE_FLAGS: Record<SupportedLocale, string> = {
|
||||
en: '🇺🇸',
|
||||
es: '🇪🇸',
|
||||
fr: '🇫🇷',
|
||||
@@ -14,14 +95,6 @@ export const SUPPORTED_LOCALE_FLAGS: Record<string, string> = {
|
||||
pl: '🇵🇱',
|
||||
};
|
||||
|
||||
export const LOCALE_NAMES: Record<string, string> = {
|
||||
en: 'English',
|
||||
es: 'Español',
|
||||
fr: 'Français',
|
||||
ja: '日本語',
|
||||
pt: 'Português',
|
||||
ru: 'Русский',
|
||||
uk: 'Українська',
|
||||
zh: '中文',
|
||||
pl: 'Polski',
|
||||
};
|
||||
export type TranslationTree = { [key: string]: TranslationNode };
|
||||
|
||||
export type TranslationNode = string | TranslationTree;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { I18nString } from '@blitzkit/core';
|
||||
import locales from '../locales.json' with { type: 'json' };
|
||||
import { DEFAULT_LOCALE } from './strings';
|
||||
|
||||
export function unwrapper(locale: string) {
|
||||
return function (i18nString: I18nString) {
|
||||
@@ -7,6 +7,6 @@ export function unwrapper(locale: string) {
|
||||
return i18nString.locales[locale];
|
||||
}
|
||||
|
||||
return i18nString.locales[locales.default];
|
||||
return i18nString.locales[DEFAULT_LOCALE];
|
||||
};
|
||||
}
|
||||
@@ -6,14 +6,6 @@
|
||||
},
|
||||
|
||||
"common": {
|
||||
"grades": {
|
||||
"0": "Unspecified grade",
|
||||
"1": "Common",
|
||||
"2": "Rare",
|
||||
"3": "Epic",
|
||||
"4": "Legendary"
|
||||
},
|
||||
|
||||
"units": {
|
||||
"m": "%s1m",
|
||||
"mm": "%s1mm",
|
||||
@@ -21,8 +13,7 @@
|
||||
"hp": "%s1hp",
|
||||
"x": "x%s1",
|
||||
"percentage": "%s1%",
|
||||
"deg_s": "%s1°/s",
|
||||
"times": "x%s1"
|
||||
"deg_s": "%s1°/s"
|
||||
},
|
||||
|
||||
"gun_types": {
|
||||
@@ -772,6 +763,8 @@
|
||||
},
|
||||
|
||||
"tank_search": {
|
||||
"select_dialog_title": "Search for tanks",
|
||||
"swap_dialog_title": "Swap tank",
|
||||
"clear_filters": "Clear filters",
|
||||
"search_bar_hint": "Search tanks...",
|
||||
"recent": "Recently viewed",
|
||||
@@ -870,11 +863,13 @@
|
||||
},
|
||||
|
||||
"tools": {
|
||||
"internal": {
|
||||
"name": "Internal",
|
||||
"button": "Internal",
|
||||
"description": "BlitzKit internal tools",
|
||||
"seo_description": "BlitzKit internal tools"
|
||||
"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"
|
||||
},
|
||||
|
||||
"guess": {
|
||||
@@ -903,6 +898,10 @@
|
||||
"tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
|
||||
|
||||
"visualizers": {
|
||||
"reload": {
|
||||
"skip": "Skip"
|
||||
},
|
||||
|
||||
"assault": {
|
||||
"title": "Assault shells"
|
||||
},
|
||||
@@ -929,6 +928,13 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -944,9 +950,7 @@
|
||||
"green_penetration": "Green penetration",
|
||||
"hide_model_under_armor": "Hide model under armor",
|
||||
"opaque": "Opaque",
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Environment",
|
||||
"show_grid": "Show grid"
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
},
|
||||
|
||||
"screenshot": {
|
||||
@@ -1042,7 +1046,6 @@
|
||||
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
"disclaimer": "BlitzKit does not endorse any review videos.",
|
||||
"join": "Apply to %s1 YouTuber program"
|
||||
},
|
||||
|
||||
@@ -1106,8 +1109,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Shells",
|
||||
"mostOptimalShellIndex": "Most optimal shell",
|
||||
"shellReloads": "Shell reloads",
|
||||
"shellReload": "Shell reload",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Reload",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Caliber",
|
||||
"penetration": "Penetration",
|
||||
@@ -1165,7 +1168,9 @@
|
||||
"shellRange": "Shell range",
|
||||
"shellCapacity": "Shell capacity",
|
||||
"gunType": "Gun type",
|
||||
"penetrationAt250m": "Penetration at 250m"
|
||||
"penetrationAt250m": "Penetration at 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1273,33 +1278,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"avatars": {
|
||||
"name": "Avatars",
|
||||
"gallery": {
|
||||
"name": "Gallery",
|
||||
"button": "Browse all",
|
||||
"seo_description": "All World of Tanks Blitz player profile avatars",
|
||||
"description": "Player profile avatars",
|
||||
|
||||
"no_description": "This avatar has no description.",
|
||||
"download": "Download",
|
||||
"enlarge": "Enlarge",
|
||||
"hidden": "Hidden",
|
||||
"unnamed_avatars": "Unnamed avatars",
|
||||
"description": "Blitz player avatars",
|
||||
"seo_description": "All World of Tanks Blitz player avatars",
|
||||
"last_updated": "Last updated on %s1",
|
||||
"search": {
|
||||
"hint": "Search avatars...",
|
||||
"results": "%s1 avatars"
|
||||
},
|
||||
|
||||
"categories": {
|
||||
"0": "Unspecified",
|
||||
"1": "Premium",
|
||||
"2": "Special",
|
||||
"3": "Accumulative",
|
||||
"4": "Other",
|
||||
"5": "Temporary"
|
||||
}
|
||||
},
|
||||
|
||||
"session": {
|
||||
"name": "Session",
|
||||
"button": "Start tracking",
|
||||
@@ -1461,17 +1450,11 @@
|
||||
"description": "Get started with BlitzKit",
|
||||
"seo_description": "Get started with BlitzKit"
|
||||
},
|
||||
"blitz_analysis": {
|
||||
"name": "BlitzAnalysis[]",
|
||||
"button": "Analyze now",
|
||||
"description": "Rich historic average stats",
|
||||
"seo_description": "Rich historic average stats in World of Tanks Blitz"
|
||||
},
|
||||
"blitz_insider": {
|
||||
"name": "Blitz Insider",
|
||||
"button": "Discover today",
|
||||
"description": "In-game session stats",
|
||||
"seo_description": "In-game session stats in World of Tanks Blitz"
|
||||
"api": {
|
||||
"name": "API",
|
||||
"button": "To the docs",
|
||||
"description": "BlitzKit hosted API",
|
||||
"seo_description": "BlitzKit hosted static API for World of Tanks Blitz"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -764,6 +764,12 @@
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"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"
|
||||
},
|
||||
"guess": {
|
||||
"name": "Guess the tank",
|
||||
"button": "Guess away",
|
||||
@@ -787,6 +793,9 @@
|
||||
"tank_seo_title": "%s1 - Nivel %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)",
|
||||
"tank_seo_description": "%s1 blindaje, estadisticas, equipamiento, proviciones y mas.\n%s2 DPM, %3hp alfa, %s4mm penetracion, %s5kph velocidad maxima, %s6 hp salud",
|
||||
"visualizers": {
|
||||
"reload": {
|
||||
"skip": "Skip"
|
||||
},
|
||||
"assault": {
|
||||
"title": "Assault shells"
|
||||
},
|
||||
@@ -810,6 +819,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -823,9 +838,7 @@
|
||||
"green_penetration": "Penetración verde",
|
||||
"hide_model_under_armor": "Ocultar modelo bajo la armadura",
|
||||
"opaque": "Opaco",
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Entorno",
|
||||
"show_grid": "Mostrar cuadrícula"
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "Descargar",
|
||||
@@ -912,7 +925,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Vídeos de reseña",
|
||||
"disclaimer": "BlitzKit no respalda ningún vídeo de reseñas.",
|
||||
"join": "Aplica para el programa de YouTube de %s1"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,8 +985,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Proyectiles",
|
||||
"mostOptimalShellIndex": "Proyectil más óptimo",
|
||||
"shellReloads": "Tiempos de recarga",
|
||||
"shellReload": "Tiempo de recarga",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Recarga",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Calibre",
|
||||
"penetration": "Penetración",
|
||||
@@ -1032,7 +1044,9 @@
|
||||
"shellRange": "Rango del proyectil",
|
||||
"shellCapacity": "Capacidad de proyectiles",
|
||||
"gunType": "Tipo de cañón",
|
||||
"penetrationAt250m": "Penetración a 250 m"
|
||||
"penetrationAt250m": "Penetración a 250 m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -364,9 +364,9 @@
|
||||
"non_tech_tree": "# %s1 n'est pas dans l'arbre technologique\n\nVous ne pouvez pas rechercher un char prenium/de collection.",
|
||||
"tier_1": "# %s1 est un char de tier I\n\nLe char de tier 1 sont déverrouillé gratuitement pour tous les joueurs.",
|
||||
"already_researched": "# %s1 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": "# %s1 was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
|
||||
"tanks_not_on_line": "# %s1 and %s2 are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
|
||||
"unordered": "# %s1 higher tiered to %s2\n\nMake sure to provide %s3 as the `starting-tank` instead as it is the lower tiered tank."
|
||||
"start_end_equal": "# %s1 a été fourni deux fois\n\nAssurez-vous que `starting-tank` et `target-tank` sont différents.",
|
||||
"tanks_not_on_line": "%s1 et %s2 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": "# %s1 niveau supérieur à %s2\n\nAssurez-vous de fournir %s3 comme le `starting-tank` à la place car c'est le réservoir de niveau inférieur."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
@@ -380,7 +380,7 @@
|
||||
},
|
||||
"embeds": {
|
||||
"name": "intègre",
|
||||
"description": "How embeds work"
|
||||
"description": "Comment fonctionnent les intégrations"
|
||||
},
|
||||
"introduction": {
|
||||
"name": "introduction",
|
||||
@@ -402,7 +402,7 @@
|
||||
},
|
||||
"breakdown": {
|
||||
"name": "décomposition",
|
||||
"description": "A period's breakdown by tanks played",
|
||||
"description": "Répartition d'une période par chars joués",
|
||||
"body": {
|
||||
"total": "Total",
|
||||
"battles": "Batailles",
|
||||
@@ -471,7 +471,7 @@
|
||||
"accuracy": "Précision"
|
||||
},
|
||||
"lethality": {
|
||||
"title": "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",
|
||||
@@ -499,20 +499,20 @@
|
||||
},
|
||||
"embed": {
|
||||
"title": "Compte invalide ?",
|
||||
"description": "Use the command again and select a username from the search results."
|
||||
"description": "Utilisez à nouveau la commande et sélectionnez un nom d'utilisateur à partir des résultats de recherche."
|
||||
}
|
||||
},
|
||||
"search_clans": {
|
||||
"name": "search-clans",
|
||||
"description": "Search clans",
|
||||
"name": "clans de recherche",
|
||||
"description": "Rechercher des clans",
|
||||
"options": {
|
||||
"limit": {
|
||||
"name": "limite",
|
||||
"description": "The max number of clan results (default: %s1)"
|
||||
"description": "Nombre maximum de résultats du clan (par défaut: %s1)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"title": "Clan search for %s1 in region %s2",
|
||||
"title": "Recherche de clan %s1 dans la région %s2",
|
||||
"no_results": "Pas de clan trouvé"
|
||||
}
|
||||
},
|
||||
@@ -535,69 +535,69 @@
|
||||
"description": "Statistiques des batailles de cotés",
|
||||
"subcommands": {
|
||||
"neighbors": {
|
||||
"name": "neighbors",
|
||||
"description": "Neighboring players on the leaderboard"
|
||||
"name": "voisins",
|
||||
"description": "Joueurs voisins dans le classement"
|
||||
},
|
||||
"league": {
|
||||
"name": "ligue",
|
||||
"description": "Top players of a league"
|
||||
"description": "Meilleurs joueurs d'une ligue"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"limit": {
|
||||
"name": "limite",
|
||||
"description": "How many neighbors to display (default: %s1)"
|
||||
"description": "Combien de voisins afficher (par défaut : %s1)"
|
||||
},
|
||||
"season": {
|
||||
"name": "season",
|
||||
"description": "Season number (default: current)",
|
||||
"name": "saison",
|
||||
"description": "Numéro de saison (défaut : courant)",
|
||||
"choices": {
|
||||
"current": "%s1 (current)"
|
||||
"current": "%s1 (actuel)"
|
||||
}
|
||||
},
|
||||
"league": {
|
||||
"name": "ligue",
|
||||
"description": "The league to display"
|
||||
"description": "La ligue à afficher"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"top_players": "Top %s1 players",
|
||||
"no_participation": "# %s1 didn't play rating in season %s2\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 %s1",
|
||||
"neighbors": "Rating neighbors",
|
||||
"more": "+ %s1 more"
|
||||
"top_players": "Top %s1 joueurs",
|
||||
"no_participation": "%s1 n'a pas joué au classement dans la saison %s2",
|
||||
"no_ongoing_season": "Pas de saison en cours\n\nIl n'y a pas de saison de notation en cours actuellement.",
|
||||
"deleted_player": "%s1 supprimé",
|
||||
"neighbors": "évaluer les voisins",
|
||||
"more": "+ %s1 de plus"
|
||||
}
|
||||
},
|
||||
"today": {
|
||||
"name": "aujourd'hui",
|
||||
"description": "Today's played tanks and statistics"
|
||||
"description": "Les chars et les statistiques joués aujourd'hui"
|
||||
},
|
||||
"stats": {
|
||||
"name": "statistiques",
|
||||
"description": "Quick statistics for a period"
|
||||
"description": "Statistiques rapides pour une période"
|
||||
},
|
||||
"owned_tanks": {
|
||||
"name": "owned-tanks",
|
||||
"description": "Shows all own tanks in a tier",
|
||||
"name": "chars possédés",
|
||||
"description": "Affiche tous les chars d'un niveau",
|
||||
"body": {
|
||||
"title": "# %s1's owned tier %s2 tanks\n\n"
|
||||
"title": "# %s1 possédait des chars de rang %s2"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"name": "permissions",
|
||||
"description": "Checks wether the bot has the required permissions to function",
|
||||
"description": "Vérifie si le bot a les permissions nécessaires pour fonctionner",
|
||||
"body": {
|
||||
"title": "# Permissions\n\n%s1",
|
||||
"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"
|
||||
"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": "Check wether the bot is alive.",
|
||||
"description": "Vérifiez si le bot est en vie.",
|
||||
"body": {
|
||||
"pong": "Pong 🏓",
|
||||
"pong_with_time": "Pong 🏓 - %s1ms"
|
||||
@@ -605,7 +605,7 @@
|
||||
"subcommands": {
|
||||
"blitzkit": {
|
||||
"name": "blitzkit",
|
||||
"description": "Ping BlitzKit"
|
||||
"description": "Ping blitzkit"
|
||||
},
|
||||
"wotb": {
|
||||
"name": "wotb",
|
||||
@@ -614,10 +614,10 @@
|
||||
}
|
||||
},
|
||||
"player_info": {
|
||||
"name": "player-info",
|
||||
"description": "Basic information about a player",
|
||||
"name": "info-joueur",
|
||||
"description": "Informations de base sur un joueur",
|
||||
"body": {
|
||||
"title": "%s1's information",
|
||||
"title": "Informations de %s1",
|
||||
"nickname": "Nickname",
|
||||
"battles": "Batailles",
|
||||
"winrate": "Taux de victoires",
|
||||
@@ -635,7 +635,7 @@
|
||||
"description": "Today's rating statistics"
|
||||
},
|
||||
"season": {
|
||||
"name": "season",
|
||||
"name": "saison",
|
||||
"description": "This season's rating statistics"
|
||||
}
|
||||
},
|
||||
@@ -764,6 +764,12 @@
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"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"
|
||||
},
|
||||
"guess": {
|
||||
"name": "Guess the tank",
|
||||
"button": "Guess away",
|
||||
@@ -787,6 +793,9 @@
|
||||
"tank_seo_title": "%s1 - Tier %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)",
|
||||
"tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
|
||||
"visualizers": {
|
||||
"reload": {
|
||||
"skip": "Skip"
|
||||
},
|
||||
"assault": {
|
||||
"title": "Assault shells"
|
||||
},
|
||||
@@ -810,6 +819,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -823,9 +838,7 @@
|
||||
"green_penetration": "Green penetration",
|
||||
"hide_model_under_armor": "Hide model under armor",
|
||||
"opaque": "Opaque",
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Environment",
|
||||
"show_grid": "Show grid"
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "Download",
|
||||
@@ -912,7 +925,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
"disclaimer": "BlitzKit does not endorse any review videos.",
|
||||
"join": "Apply to %s1 YouTuber program"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,8 +985,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Shells",
|
||||
"mostOptimalShellIndex": "Most optimal shell",
|
||||
"shellReloads": "Shell reloads",
|
||||
"shellReload": "Shell reload",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Reload",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Caliber",
|
||||
"penetration": "Penetration",
|
||||
@@ -1032,7 +1044,9 @@
|
||||
"shellRange": "Shell range",
|
||||
"shellCapacity": "Shell capacity",
|
||||
"gunType": "Gun type",
|
||||
"penetrationAt250m": "Penetration at 250m"
|
||||
"penetrationAt250m": "Penetration at 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -764,6 +764,12 @@
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"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"
|
||||
},
|
||||
"guess": {
|
||||
"name": "Guess the tank",
|
||||
"button": "Guess away",
|
||||
@@ -787,6 +793,9 @@
|
||||
"tank_seo_title": "%s1 - Tier %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)",
|
||||
"tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
|
||||
"visualizers": {
|
||||
"reload": {
|
||||
"skip": "Skip"
|
||||
},
|
||||
"assault": {
|
||||
"title": "Assault shells"
|
||||
},
|
||||
@@ -810,6 +819,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -823,9 +838,7 @@
|
||||
"green_penetration": "貫通可能域を緑色表示",
|
||||
"hide_model_under_armor": "車輌モデルを非表示",
|
||||
"opaque": "貫通アシストを不透明にする",
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "環境",
|
||||
"show_grid": "グリッドを表示"
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "保存する",
|
||||
@@ -912,7 +925,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
"disclaimer": "BlitzKitはいかなるレビュー動画も後援していません。",
|
||||
"join": "%s1 YouTuberプログラムに応募する"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,7 +985,7 @@
|
||||
"dpmEffective": "60秒での有効値",
|
||||
"shells": "砲弾",
|
||||
"mostOptimalShellIndex": "最適な弾数",
|
||||
"shellReloads": "自動装填時間",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "装填時間",
|
||||
"intraClip": "砲弾再装填時間",
|
||||
"caliber": "口径",
|
||||
@@ -1032,7 +1044,9 @@
|
||||
"shellRange": "射程",
|
||||
"shellCapacity": "総弾数",
|
||||
"gunType": "主砲タイプ",
|
||||
"penetrationAt250m": "250m での貫通力"
|
||||
"penetrationAt250m": "250m での貫通力",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -764,6 +764,12 @@
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"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"
|
||||
},
|
||||
"guess": {
|
||||
"name": "Guess the tank",
|
||||
"button": "Guess away",
|
||||
@@ -787,6 +793,9 @@
|
||||
"tank_seo_title": "%s1 - Tier %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)",
|
||||
"tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.",
|
||||
"visualizers": {
|
||||
"reload": {
|
||||
"skip": "Skip"
|
||||
},
|
||||
"assault": {
|
||||
"title": "Assault shells"
|
||||
},
|
||||
@@ -810,6 +819,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -823,9 +838,7 @@
|
||||
"green_penetration": "Green penetration",
|
||||
"hide_model_under_armor": "Hide model under armor",
|
||||
"opaque": "Opaque",
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Environment",
|
||||
"show_grid": "Show grid"
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "Download",
|
||||
@@ -912,7 +925,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
"disclaimer": "BlitzKit does not endorse any review videos.",
|
||||
"join": "Apply to %s1 YouTuber program"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,8 +985,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Shells",
|
||||
"mostOptimalShellIndex": "Most optimal shell",
|
||||
"shellReloads": "Shell reloads",
|
||||
"shellReload": "Shell reload",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Reload",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Caliber",
|
||||
"penetration": "Penetration",
|
||||
@@ -1032,7 +1044,9 @@
|
||||
"shellRange": "Shell range",
|
||||
"shellCapacity": "Shell capacity",
|
||||
"gunType": "Gun type",
|
||||
"penetrationAt250m": "Penetration at 250m"
|
||||
"penetrationAt250m": "Penetration at 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -764,6 +764,12 @@
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"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"
|
||||
},
|
||||
"guess": {
|
||||
"name": "Guess the tank",
|
||||
"button": "Guess away",
|
||||
@@ -787,6 +793,9 @@
|
||||
"tank_seo_title": "%s1 - Nível %s2 %s3 %s4 | %s5 Tanquepedia | World of Tanks Blitz (WoTB)",
|
||||
"tank_seo_description": "%s1 blindagem, estatísticas, equipamentos, provisões e mais. %s2 DPM, %s3hp alfa, %s4mm de penetração, %s5km/h velocidade máxima, %s6hp vida.",
|
||||
"visualizers": {
|
||||
"reload": {
|
||||
"skip": "Skip"
|
||||
},
|
||||
"assault": {
|
||||
"title": "Assault shells"
|
||||
},
|
||||
@@ -810,6 +819,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -823,9 +838,7 @@
|
||||
"green_penetration": "Penetração verde",
|
||||
"hide_model_under_armor": "Ocultar modelo sob blindagem",
|
||||
"opaque": "Opaco",
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Ambiente",
|
||||
"show_grid": "Exibir grade"
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "Download",
|
||||
@@ -912,7 +925,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Vídeos de análise",
|
||||
"disclaimer": "BlitzKit não aprova nenhum vídeo de análise.",
|
||||
"join": "Candidate-se para %s1 um programa YouTuber"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,8 +985,8 @@
|
||||
"dpmEffective": "Efetivo aos 60s",
|
||||
"shells": "Projéteis",
|
||||
"mostOptimalShellIndex": "Projétil mais ideal",
|
||||
"shellReloads": "Recarga de projétil",
|
||||
"shellReload": "Recarregamento de projétil",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Recarga",
|
||||
"intraClip": "Intra-clipe",
|
||||
"caliber": "Calibre",
|
||||
"penetration": "Penetração",
|
||||
@@ -1032,7 +1044,9 @@
|
||||
"shellRange": "Alcance de projétil",
|
||||
"shellCapacity": "Capacidade de projéteis",
|
||||
"gunType": "Tipo de arma",
|
||||
"penetrationAt250m": "Penetração à 250m"
|
||||
"penetrationAt250m": "Penetração à 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"test": {
|
||||
"default": "тест",
|
||||
"long": "Это длинная тестовая строка. Вы НИКОГДА не должны видеть её в пользовательском интерфейсе. Мой любимый фрукт - %s1, потому что его цвет %s2. Он мне так нравится, что я произнесу его имя 3 раза! %s1! %s1! %s1! И ещё раз, это %s2, а не %s3.",
|
||||
"fox": "Это длинная тестовая строка. Вы НИКОГДА не должны видеть её в пользовательском интерфейсе. Мой любимый фрукт - %s1, потому что его цвет %s2. Он мне так нравится, что я произнесу его имя 3 раза! %s1! %s1! %s1! И ещё раз, это %s2, а не %s3."
|
||||
"long": "Это длинная тестовая строка. Вы НИКОГДА не должны видеть её в пользовательском интерфейсе. Мой любимый фрукт — %s1, потому что это цвет %s2. Он мне так нравится, что я произнесу его имя 3 раза! %s1! %s1! %s1! И ещё раз, это %s2, а не %s3.",
|
||||
"fox": "Съешь же ещё этих мягких французских булок, да выпей чаю."
|
||||
},
|
||||
"common": {
|
||||
"units": {
|
||||
@@ -283,15 +283,15 @@
|
||||
"name": "уровень",
|
||||
"description": "Уровень",
|
||||
"choices": {
|
||||
"tier": "Уровень %s - %s"
|
||||
"tier": "Уровень %s1 - %s2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"periods": {
|
||||
"career": "Карьера",
|
||||
"today": "Cегодня",
|
||||
"days": "%s дней",
|
||||
"custom": "%s - %s дней"
|
||||
"days": "%s1 дней",
|
||||
"custom": "%s1 - %s2 дней"
|
||||
},
|
||||
"filters": {
|
||||
"none": "Без фильтров",
|
||||
@@ -323,9 +323,9 @@
|
||||
"description": "Танки доступные на аукционе сейчас",
|
||||
"body": {
|
||||
"title": "Blitz аукцион",
|
||||
"subtitle": "%s доступные • следующие цены %s\nПокупка сейчас: %s",
|
||||
"next": "Следующая цена: %s",
|
||||
"available": "Доступно: %s / %s"
|
||||
"subtitle": "%s1 доступные • следующие цены %s2\nПокупка сейчас: %s3",
|
||||
"next": "Следующая цена: %s1",
|
||||
"available": "Доступно: %s1 / %s2"
|
||||
},
|
||||
"errors": {
|
||||
"no_auction": "# Аукцион сейчас не проходит\n\nНа данный момент нет танков для покупки на аукционе. Если считаете, что это ошибка, пожалуйста о несоответствии на [официальном сервере Discord](https://discord.gg/nDt7AjGJQH)."
|
||||
@@ -354,19 +354,19 @@
|
||||
"body": {
|
||||
"subtitle": "Исследование",
|
||||
"total": "Всего",
|
||||
"research": "Исследование: %s",
|
||||
"upgrades": "Улучшения: %s",
|
||||
"purchase": "Покупка: %s",
|
||||
"equipment": "Снаряжение: %s",
|
||||
"research": "Исследование: %s1",
|
||||
"upgrades": "Улучшения: %s1",
|
||||
"purchase": "Покупка: %s1",
|
||||
"equipment": "Снаряжение: %s1",
|
||||
"estimation": "# Это приблизительная оценка!\n\nВ этот список могут входить уже исследованные танки из-за неточной информации, предоставляемой Wargaming. Попробуйте указать «стартовый-танк» в строке при использовании команды, если сможете."
|
||||
},
|
||||
"errors": {
|
||||
"non_tech_tree": "# %s отсутствует в дереве танков\n\nВы не можете исследовать премиум / коллекционный танк.",
|
||||
"tier_1": "# %s - это уровень I\n\nУровень I всегда открыт бесплатно для всех игроков.",
|
||||
"already_researched": "# %s уже исследован\n\nПопробуйте предоставить «starting-tank» при использовании команды для просмотра итогов уже исследованной ветки.",
|
||||
"start_end_equal": "# %s был предоставлен дважды\n\nУбедитесь, что «starting-tank» и «target-tank» различаются.",
|
||||
"tanks_not_on_line": "# %s и %s находятся не в одной ветке\n\nУбедитесь, что «starting-tank» и «target-tank» находятся на той же ветке дерева танков.",
|
||||
"unordered": "# У %s более высокий уровень чем у %s\n\nОбязательно укажите %s в качестве «starting-tank», так как это танк более низкого уровня."
|
||||
"non_tech_tree": "# %s1 отсутствует в дереве танков\n\nВы не можете исследовать премиум / коллекционный танк.",
|
||||
"tier_1": "# %s1 - это уровень I\n\nУровень I всегда открыт бесплатно для всех игроков.",
|
||||
"already_researched": "# %s1 уже исследован\n\nПопробуйте предоставить «starting-tank» при использовании команды для просмотра итогов уже исследованной ветки.",
|
||||
"start_end_equal": "# %s1 был предоставлен дважды\n\nУбедитесь, что «starting-tank» и «target-tank» различаются.",
|
||||
"tanks_not_on_line": "# %s1 и %s2 находятся не в одной ветке\n\nУбедитесь, что «starting-tank» и «target-tank» находятся на той же ветке дерева танков.",
|
||||
"unordered": "# У %s1 более высокий уровень чем у %s2\n\nОбязательно укажите %s3 в качестве «starting-tank», так как это танк более низкого уровня."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
@@ -409,7 +409,7 @@
|
||||
"wn8": "WN8",
|
||||
"winrate": "Коэффициент побед",
|
||||
"damage": "Урон",
|
||||
"unknown_tank": "Неизвестный танк %s"
|
||||
"unknown_tank": "Неизвестный танк %s1"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
@@ -445,12 +445,12 @@
|
||||
"options": {
|
||||
"threshold": {
|
||||
"name": "порог",
|
||||
"description": "Количество дней не активности (по умолчанию: %s дней)"
|
||||
"description": "Количество дней не активности (по умолчанию: %s1 дней)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"subtitle": "Неактивен %s+ дней",
|
||||
"listing": "%s дней"
|
||||
"subtitle": "Неактивен %s1+ дней",
|
||||
"listing": "%s1 дней"
|
||||
}
|
||||
},
|
||||
"full_stats": {
|
||||
@@ -508,11 +508,11 @@
|
||||
"options": {
|
||||
"limit": {
|
||||
"name": "лимит",
|
||||
"description": "Максимальное количество результатов клана (по умолчанию: %s)"
|
||||
"description": "Максимальное количество результатов клана (по умолчанию: %s1)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"title": "Поиск клана %s в регионе %s",
|
||||
"title": "Поиск клана %s1 в регионе %s2",
|
||||
"no_results": "Кланов не найдено"
|
||||
}
|
||||
},
|
||||
@@ -522,11 +522,11 @@
|
||||
"options": {
|
||||
"limit": {
|
||||
"name": "лимит",
|
||||
"description": "Максимальное число результатов игрока (по умолчанию: %s)"
|
||||
"description": "Максимальное число результатов игрока (по умолчанию: %s1)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"title": "Поиск игрока %s в регионе %s",
|
||||
"title": "Поиск игрока %s1 в регионе %s2",
|
||||
"no_results": "Игроков не найдено"
|
||||
}
|
||||
},
|
||||
@@ -546,13 +546,13 @@
|
||||
"options": {
|
||||
"limit": {
|
||||
"name": "лимит",
|
||||
"description": "Количество отображаемых ближайших игроков (по умолчанию: %s)"
|
||||
"description": "Количество отображаемых ближайших игроков (по умолчанию: %s1)"
|
||||
},
|
||||
"season": {
|
||||
"name": "сезон",
|
||||
"description": "Номер сезона (по умолчанию: текущий)",
|
||||
"choices": {
|
||||
"current": "%s (текущая)"
|
||||
"current": "%s1 (текущая)"
|
||||
}
|
||||
},
|
||||
"league": {
|
||||
@@ -561,12 +561,12 @@
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"top_players": "Топ %s игроков",
|
||||
"no_participation": "# %s не играл в рейтинг в сезоне %s\n\nЭтот игрок не участвовал в этом сезоне или не получил прошлую калибровку.",
|
||||
"top_players": "Топ %s1 игроков",
|
||||
"no_participation": "# %s1 не играл в рейтинг в сезоне %s2\n\nЭтот игрок не участвовал в этом сезоне или не получил прошлую калибровку.",
|
||||
"no_ongoing_season": "# Нет текущего сезона\n\nВ данный момент нет рейтингового сезона.",
|
||||
"deleted_player": "Удалено %s",
|
||||
"deleted_player": "Удалено %s1",
|
||||
"neighbors": "Рейтинг ближайших игроков",
|
||||
"more": "+ ещё %s"
|
||||
"more": "+ ещё %s1"
|
||||
}
|
||||
},
|
||||
"today": {
|
||||
@@ -581,14 +581,14 @@
|
||||
"name": "cобственные-танки",
|
||||
"description": "Показывает все принадлежащие танки на уровне",
|
||||
"body": {
|
||||
"title": "# %s принадлежал уровень %s танков\n\n"
|
||||
"title": "# %s1 принадлежал уровень %s2 танков\n\n"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"name": "разрешения",
|
||||
"description": "Проверяет, есть ли у бота необходимые разрешения для работы",
|
||||
"body": {
|
||||
"title": "# Разрешения\n\n%s",
|
||||
"title": "# Разрешения\n\n%s1",
|
||||
"view_channels": "Просмотр каналов: необходимо найти сообщение, на котором была использована кнопка обновления",
|
||||
"read_message_history": "Просмотр истории сообщений: необходимо найти сообщение для обновления",
|
||||
"attach_files": "Прикрепить файлы: необходимо для добавления изображений в команды на основе изображения",
|
||||
@@ -600,7 +600,7 @@
|
||||
"description": "Проверьте, что бот жив.",
|
||||
"body": {
|
||||
"pong": "Понг 🏓",
|
||||
"pong_with_time": "Понг 🏓 - %sмс"
|
||||
"pong_with_time": "Понг 🏓 - %s1мс"
|
||||
},
|
||||
"subcommands": {
|
||||
"blitzkit": {
|
||||
@@ -617,7 +617,7 @@
|
||||
"name": "информация-об-игроке",
|
||||
"description": "Основная информация о игроке",
|
||||
"body": {
|
||||
"title": "Информация о %s",
|
||||
"title": "Информация о %s1",
|
||||
"nickname": "Никнейм",
|
||||
"battles": "Боёв",
|
||||
"winrate": "Коэффициент побед",
|
||||
@@ -646,7 +646,7 @@
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"no_participation": "# %s не получил рейтинга\n\nЭтот игрок в этом сезоне не участвовал в рейтинговом режиме.",
|
||||
"no_participation": "# %s1 не получил рейтинга\n\nЭтот игрок в этом сезоне не участвовал в рейтинговом режиме.",
|
||||
"unrecorded_stats": "# Статистика недоступна в настоящее время\n\nВ настоящее время Wargaming, возможно, сталкивается с простоями сервера. Если сезон продолжается, статистика должна быть доступна в течение 24 часов."
|
||||
}
|
||||
}
|
||||
@@ -676,7 +676,7 @@
|
||||
"recent": "Недавние",
|
||||
"select_all": "Выбрать всё",
|
||||
"count_singular": "1 танк",
|
||||
"count_plural": "%s1 танк",
|
||||
"count_plural": "Танков: %s1",
|
||||
"sorting_by": "Сортировать по %s1",
|
||||
"no_tanks_found": {
|
||||
"body": "Танков не найдено.",
|
||||
@@ -693,7 +693,7 @@
|
||||
"survivability.volume": "объём",
|
||||
"survivability.length": "длина",
|
||||
"fire.dpm": "базовый ДПМ",
|
||||
"fire.dpmPremium": "улучшенный ДПМ",
|
||||
"fire.dpmPremium": "премиумный ДПМ",
|
||||
"fire.reload": "перезарядка",
|
||||
"fire.caliber": "калибр",
|
||||
"fire.standardPenetration": "базовое пробитие",
|
||||
@@ -764,6 +764,12 @@
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"mixer": {
|
||||
"name": "Смешиватель",
|
||||
"button": "Смешать и собрать",
|
||||
"description": "Модули смешанного танка",
|
||||
"seo_description": "Смешивание и сбор корпусов, башен и пушек танков из World of Tanks Blitz"
|
||||
},
|
||||
"guess": {
|
||||
"name": "Угадайте танк",
|
||||
"button": "Угадать",
|
||||
@@ -784,11 +790,14 @@
|
||||
"button": "Поиск танков",
|
||||
"description": "Танковая энциклопедия Blitz",
|
||||
"seo_description": "Статистика, броня и многое другое по всем танкам World of Tanks Blitz",
|
||||
"tank_seo_title": "%s1 - %s4 %s3 %s2 уровня | Танкопедия %s5 | World of Tanks Blitz (WoTB)",
|
||||
"tank_seo_title": "%s1 — %s4 %s3 %s2 уровня | Танкопедия %s5 | World of Tanks Blitz (WoTB)",
|
||||
"tank_seo_description": "%s1 броня, статистика, оборудование, снабжение и многое другое. %s2 УВМ, %s3hp альфа, %s4mm пробитие, %s5км/ч лучшая скорость, %s6hp здоровье.",
|
||||
"visualizers": {
|
||||
"reload": {
|
||||
"skip": "Пропуск"
|
||||
},
|
||||
"assault": {
|
||||
"title": "Assault shells"
|
||||
"title": "Штурмовые снаряды"
|
||||
},
|
||||
"rotator": {
|
||||
"hull": "Корпус",
|
||||
@@ -810,6 +819,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Промзона",
|
||||
"dead_rail": "Эшелон"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Поворот",
|
||||
"pitch": "Наклон",
|
||||
"min": "Вниз",
|
||||
"max": "Вверх"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -823,9 +838,7 @@
|
||||
"green_penetration": "Зелёное пробитие",
|
||||
"hide_model_under_armor": "Скрыть модель под броней",
|
||||
"opaque": "Прозрачность",
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Окружение",
|
||||
"show_grid": "Показать сетку"
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "Загрузить",
|
||||
@@ -896,7 +909,7 @@
|
||||
"tier": "Уровень",
|
||||
"type": "Тип",
|
||||
"dev_id": "DEV: ID",
|
||||
"purchase": "Стоимость покупки",
|
||||
"purchase": "Цена покупки",
|
||||
"restoration": "Цена восстановления",
|
||||
"sale": "Цена продажи",
|
||||
"research": "Необходимо опыта"
|
||||
@@ -912,7 +925,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Видеообзоры",
|
||||
"disclaimer": "BlitzKit не несёт ответственности за видеообзоры.",
|
||||
"join": "Предложить ютубера для программы %s1"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,13 +985,13 @@
|
||||
"dpmEffective": "Эффективность за 60с",
|
||||
"shells": "Снарядов в барабане",
|
||||
"mostOptimalShellIndex": "Самый эффективный снаряд",
|
||||
"shellReloads": "Перезарядка снарядов",
|
||||
"shellReloads": "Перезарядка",
|
||||
"shellReload": "Перезарядка",
|
||||
"intraClip": "Между снарядами",
|
||||
"caliber": "Калибр",
|
||||
"penetration": "Пробитие",
|
||||
"damage": "Урон",
|
||||
"damageWithoutAssault": "Damage without assault shells",
|
||||
"damageWithoutAssault": "Урон без штурмовых снарядов",
|
||||
"clipDamage": "Урон с барабана",
|
||||
"moduleDamage": "Урон по модулям",
|
||||
"explosionRadius": "Разлет осколков",
|
||||
@@ -1032,7 +1044,9 @@
|
||||
"shellRange": "Дальность снаряда",
|
||||
"shellCapacity": "Боезапас",
|
||||
"gunType": "Тип орудия",
|
||||
"penetrationAt250m": "Пробитие на 250м"
|
||||
"penetrationAt250m": "Пробитие на 250м",
|
||||
"burstShells": "Кол-во разрывных снарядов",
|
||||
"burstInterShell": "Взрыв между снарядами"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
@@ -1129,7 +1143,7 @@
|
||||
"hits": "Уничтожен",
|
||||
"damage_ratio": "Коэф. урона",
|
||||
"damage_taken": "Получено урона",
|
||||
"capture_points": "Ограничительные точки"
|
||||
"capture_points": "Точки ограничения"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1141,7 +1155,7 @@
|
||||
"last_updated": "Последнее обновление %s1",
|
||||
"search": {
|
||||
"hint": "Поиск аватаров...",
|
||||
"results": "%s1 аватар"
|
||||
"results": "Аватаров: %s1"
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
@@ -1150,7 +1164,7 @@
|
||||
"description": "Статистика сеанса игры",
|
||||
"seo_description": "Отслеживайте свою статистику в реальном времени во время игры World of Tanks Blitz",
|
||||
"search": {
|
||||
"lookup": "Искать игрока, чтобы начать",
|
||||
"lookup": "Введите ник игрока",
|
||||
"hint": "Поиск игроков...",
|
||||
"searching": "Поиск...",
|
||||
"no_results": "Игроков не найдено"
|
||||
@@ -1191,7 +1205,7 @@
|
||||
"button": "Создать",
|
||||
"description": "Встраиваемые элементы для стримов",
|
||||
"seo_description": "Создать вставки для стримов по World of Tanks Blitz с невероятной кастомизации",
|
||||
"coming_soon": "Скоро появятся новые встраиваемые элементы.",
|
||||
"coming_soon": "В скором времени появятся новые встраиваемые элементы.",
|
||||
"suggest": "Предложить идеи.",
|
||||
"preview": {
|
||||
"title": "Предпросмотр",
|
||||
|
||||
@@ -764,6 +764,12 @@
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"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"
|
||||
},
|
||||
"guess": {
|
||||
"name": "Guess the tank",
|
||||
"button": "Guess away",
|
||||
@@ -787,6 +793,9 @@
|
||||
"tank_seo_title": "%s1 - Рівень %s2 %s3 %s4 | %s5 Танкопедія | WoT Blitz",
|
||||
"tank_seo_description": "%s1 броня, характеристики, обладнання, амуніція та більше. %s2 шкода на хвилину, %s3 разова шкода, %s4 мм пробиття, %s5 км/год максимальна швидкість, %s6 міцність.",
|
||||
"visualizers": {
|
||||
"reload": {
|
||||
"skip": "Skip"
|
||||
},
|
||||
"assault": {
|
||||
"title": "Assault shells"
|
||||
},
|
||||
@@ -810,6 +819,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -823,9 +838,7 @@
|
||||
"green_penetration": "Зелене пробиття",
|
||||
"hide_model_under_armor": "Hide model under armor",
|
||||
"opaque": "Opaque",
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Environment",
|
||||
"show_grid": "Show grid"
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "Завантажити",
|
||||
@@ -912,7 +925,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
"disclaimer": "BlitzKit does not endorse any review videos.",
|
||||
"join": "Apply to %s1 YouTuber program"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,8 +985,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Снаряди",
|
||||
"mostOptimalShellIndex": "Most optimal shell",
|
||||
"shellReloads": "Shell reloads",
|
||||
"shellReload": "Shell reload",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Час перезаряджання",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Калібр",
|
||||
"penetration": "Бронепробиття",
|
||||
@@ -1032,7 +1044,9 @@
|
||||
"shellRange": "Shell range",
|
||||
"shellCapacity": "Shell capacity",
|
||||
"gunType": "Тип гармати",
|
||||
"penetrationAt250m": "Penetration at 250m"
|
||||
"penetrationAt250m": "Penetration at 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -764,6 +764,12 @@
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"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"
|
||||
},
|
||||
"guess": {
|
||||
"name": "猜坦克",
|
||||
"button": "猜猜看",
|
||||
@@ -787,6 +793,9 @@
|
||||
"tank_seo_title": "%s1 - %s2 阶 %s3 %s4 | %s5 战车百科 | 战车世界闪击战 (WoTB)",
|
||||
"tank_seo_description": "%s1 装甲,统计设备,装备,供给等。 %s2 每分钟伤害, %s3生命值, %s4毫米穿透, %s5kph 最高速度, %s6hp 生命值。",
|
||||
"visualizers": {
|
||||
"reload": {
|
||||
"skip": "跳过"
|
||||
},
|
||||
"assault": {
|
||||
"title": "Assault shells"
|
||||
},
|
||||
@@ -810,6 +819,12 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "幽灵工厂",
|
||||
"dead_rail": "死亡铁道"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -823,9 +838,7 @@
|
||||
"green_penetration": "可击穿区域",
|
||||
"hide_model_under_armor": "隐藏装甲下的模型",
|
||||
"opaque": "不透明",
|
||||
"dev_wireframe": "开发选项:网格",
|
||||
"environment": "环境",
|
||||
"show_grid": "显示网格"
|
||||
"dev_wireframe": "开发选项:网格"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "下载",
|
||||
@@ -912,7 +925,6 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "回顾影片",
|
||||
"disclaimer": "BlitzKit 不支援任何影片回顾。",
|
||||
"join": "应用到 %s1 YouTuber程序"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -973,8 +985,8 @@
|
||||
"dpmEffective": "60秒内生效",
|
||||
"shells": "炮弹 ",
|
||||
"mostOptimalShellIndex": "最优炮弹",
|
||||
"shellReloads": "弹匣装填",
|
||||
"shellReload": "炮弹装填",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "装填时间",
|
||||
"intraClip": "炮弹装填",
|
||||
"caliber": "口径",
|
||||
"penetration": "穿深",
|
||||
@@ -1032,7 +1044,9 @@
|
||||
"shellRange": "射程",
|
||||
"shellCapacity": "携弹量",
|
||||
"gunType": "炮管类型",
|
||||
"penetrationAt250m": "两百五十米处的穿透力"
|
||||
"penetrationAt250m": "两百五十米处的穿透力",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
|
||||
@@ -11,6 +11,24 @@
|
||||
"build:ids": "bun src/discoverIds.ts",
|
||||
"build:bot": "bun src/buildBot.ts",
|
||||
"build:ratingleaderboard": "bun src/buildRatingLeaderboard.ts",
|
||||
"build:proxyclient": "bun src/buildProxyClient.ts",
|
||||
"prunestrings": "bun src/pruneStrings.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzkit/core": "workspace:*",
|
||||
"@blitzkit/i18n": "workspace:*",
|
||||
"@gltf-transform/core": "^4.0.10",
|
||||
"@gltf-transform/extensions": "^4.0.10",
|
||||
"@gltf-transform/functions": "^4.0.10",
|
||||
"csv-parse": "^5.5.6",
|
||||
"decompress": "^4.2.1",
|
||||
"esbuild": "^0.19.12",
|
||||
"fast-xml-parser": "^4.4.1",
|
||||
"progress": "^2.0.3",
|
||||
"puppeteer": "^23.10.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/decompress": "^4.2.7",
|
||||
"@types/progress": "^2.0.7"
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
EquipmentSlot,
|
||||
GameDefinitions,
|
||||
GunDefinition,
|
||||
GunDefinitionBase,
|
||||
I18nString,
|
||||
MapDefinitions,
|
||||
ModelDefinitions,
|
||||
@@ -274,6 +273,7 @@ interface VehicleDefinitions {
|
||||
pumpGunMode?: boolean;
|
||||
pumpGunReloadTimes?: string;
|
||||
clip?: { count: number; rate: number };
|
||||
burst?: { count: number; rate: number };
|
||||
models: { undamaged: string };
|
||||
extras?: {
|
||||
trayShell?: {
|
||||
@@ -1304,7 +1304,7 @@ export async function definitions() {
|
||||
}
|
||||
});
|
||||
|
||||
const base = {
|
||||
tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({
|
||||
id: gunId,
|
||||
weight: gunListEntry.weight,
|
||||
rotation_speed: gunListEntry.rotationSpeed,
|
||||
@@ -1324,41 +1324,38 @@ export async function definitions() {
|
||||
unlocks: resolveUnlocks(gun.unlocks),
|
||||
shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo,
|
||||
assault_ranges,
|
||||
} satisfies GunDefinitionBase;
|
||||
|
||||
tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({
|
||||
burst:
|
||||
gun.burst && gun.burst.count > 1
|
||||
? {
|
||||
count: gun.burst.count,
|
||||
interval: 60 / gun.burst.rate,
|
||||
}
|
||||
: undefined,
|
||||
gun_type:
|
||||
gunType === 'regular'
|
||||
? {
|
||||
$case: 'regular',
|
||||
value: {
|
||||
base,
|
||||
extension: { reload: gun.reloadTime },
|
||||
reload: gun.reloadTime,
|
||||
},
|
||||
}
|
||||
: gunType === 'autoReloader'
|
||||
? {
|
||||
$case: 'auto_reloader',
|
||||
value: {
|
||||
base,
|
||||
extension: {
|
||||
intra_clip: 60 / gun.clip!.rate,
|
||||
shell_count: gunClipCount,
|
||||
shell_reloads: gun
|
||||
.pumpGunReloadTimes!.split(' ')
|
||||
.map(Number),
|
||||
},
|
||||
intra_clip: 60 / gun.clip!.rate,
|
||||
shell_count: gunClipCount,
|
||||
shell_reloads: gun
|
||||
.pumpGunReloadTimes!.split(' ')
|
||||
.map(Number),
|
||||
},
|
||||
}
|
||||
: {
|
||||
$case: 'auto_loader',
|
||||
value: {
|
||||
base,
|
||||
extension: {
|
||||
intra_clip: 60 / gun.clip!.rate,
|
||||
clip_reload: gun.reloadTime,
|
||||
shell_count: gunClipCount,
|
||||
},
|
||||
intra_clip: 60 / gun.clip!.rate,
|
||||
clip_reload: gun.reloadTime,
|
||||
shell_count: gunClipCount,
|
||||
},
|
||||
},
|
||||
} satisfies GunDefinition);
|
||||
@@ -1407,7 +1404,7 @@ export async function definitions() {
|
||||
|
||||
tankDefinitions.tanks[tankId].turrets[turretIndex].guns[
|
||||
gunIndex
|
||||
].gun_type!.value.base!.shells.push({
|
||||
].shells.push({
|
||||
id: shellId,
|
||||
name: shellName,
|
||||
velocity: gunShellEntry.speed,
|
||||
@@ -1495,9 +1492,7 @@ export async function definitions() {
|
||||
turret.research_cost = turretXps.get(turret.id);
|
||||
|
||||
Object.values(turret.guns).forEach((gunRaw) => {
|
||||
gunRaw.gun_type!.value.base!.research_cost = gunXps.get(
|
||||
gunRaw.gun_type!.value.base!.id,
|
||||
);
|
||||
gunRaw.research_cost = gunXps.get(gunRaw.id);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ export async function tankIcons() {
|
||||
|
||||
const nationVehicleId = tank.id;
|
||||
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
|
||||
|
||||
const parameters = await readYAMLDVPL<TankParameters>(
|
||||
`${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
|
||||
);
|
||||
|
||||
@@ -1,49 +1,52 @@
|
||||
import { toUniqueId } from '@blitzkit/core';
|
||||
import { NodeIO } from '@gltf-transform/core';
|
||||
import { ALL_EXTENSIONS } from '@gltf-transform/extensions';
|
||||
import { readdir, writeFile } from 'fs/promises';
|
||||
import { extractModel } from '../core/blitz/extractModel';
|
||||
import { readXMLDVPL } from '../core/blitz/readXMLDVPL';
|
||||
import { readYAMLDVPL } from '../core/blitz/readYAMLDVPL';
|
||||
import { commitAssets } from '../core/github/commitAssets';
|
||||
import { FileChange } from '../core/github/commitMultipleFiles';
|
||||
import { DATA } from './constants';
|
||||
import { VehicleDefinitionList } from './definitions';
|
||||
import { TankParameters } from './tankIcons';
|
||||
import { toUniqueId } from "@blitzkit/core";
|
||||
import { NodeIO } from "@gltf-transform/core";
|
||||
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
|
||||
import { readdir } from "fs/promises";
|
||||
import { extractModel } from "../core/blitz/extractModel";
|
||||
import { readXMLDVPL } from "../core/blitz/readXMLDVPL";
|
||||
import { readYAMLDVPL } from "../core/blitz/readYAMLDVPL";
|
||||
import { commitAssets } from "../core/github/commitAssets";
|
||||
import { FileChange } from "../core/github/commitMultipleFiles";
|
||||
import { DATA } from "./constants";
|
||||
import { VehicleDefinitionList } from "./definitions";
|
||||
import { TankParameters } from "./tankIcons";
|
||||
|
||||
export async function tankModels() {
|
||||
console.log('Building tank models...');
|
||||
console.log("Building tank models...");
|
||||
|
||||
const nodeIO = new NodeIO().registerExtensions(ALL_EXTENSIONS);
|
||||
const nations = await readdir(`${DATA}/XML/item_defs/vehicles`).then(
|
||||
(nations) => nations.filter((nation) => nation !== 'common'),
|
||||
(nations) => nations.filter((nation) => nation !== "common")
|
||||
);
|
||||
|
||||
for (const nationIndex in nations) {
|
||||
const nation = nations[nationIndex];
|
||||
const changes: FileChange[] = [];
|
||||
const tanks = await readXMLDVPL<{ root: VehicleDefinitionList }>(
|
||||
`${DATA}/XML/item_defs/vehicles/${nation}/list.xml`,
|
||||
`${DATA}/XML/item_defs/vehicles/${nation}/list.xml`
|
||||
);
|
||||
const entries = Object.entries(tanks.root);
|
||||
|
||||
console.log(`Building models for ${nation}`);
|
||||
|
||||
for (const [tankKey, tank] of Object.entries(tanks.root)) {
|
||||
if (tankKey.includes('tutorial_bot')) continue;
|
||||
let index = 0;
|
||||
for (const [tankKey, tank] of entries) {
|
||||
console.log(` ${++index} / ${entries.length} : ${tankKey}`);
|
||||
|
||||
if (tankKey.includes("tutorial_bot")) continue;
|
||||
|
||||
const id = toUniqueId(nation, tank.id);
|
||||
|
||||
const parameters = await readYAMLDVPL<TankParameters>(
|
||||
`${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
|
||||
`${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`
|
||||
);
|
||||
const model = await extractModel(
|
||||
DATA,
|
||||
parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ''),
|
||||
parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, "")
|
||||
);
|
||||
const content = Buffer.from(await nodeIO.writeBinary(model));
|
||||
|
||||
changes.push({
|
||||
path: `3d/tanks/models/${id}.glb`,
|
||||
content: Buffer.from(await nodeIO.writeBinary(model)),
|
||||
});
|
||||
changes.push({ path: `3d/tanks/models/${id}.glb`, content });
|
||||
}
|
||||
|
||||
await commitAssets(`tank models ${nation}`, changes);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { readFile, writeFile } from "fs/promises";
|
||||
|
||||
const IMPORT = "@protos/auto_legacy_proxy_client";
|
||||
const SOURCE =
|
||||
"../../submodules/blitzkit-closed/protos/auto_legacy_proxy_client.proto";
|
||||
const TARGET =
|
||||
"../../submodules/blitzkit-closed/src/unreal/methodPopulatedProxyClient.ts";
|
||||
|
||||
const requestPattern = /message (\w+)Request {/g;
|
||||
const source = await readFile(SOURCE).then((buffer) => buffer.toString());
|
||||
const requests = source.matchAll(requestPattern);
|
||||
|
||||
const imports: string[] = [];
|
||||
const methods: { name: string; method: string }[] = [];
|
||||
|
||||
for (const match of requests) {
|
||||
const name = match[1];
|
||||
// const method =
|
||||
// name.charAt(0).toLowerCase() + name.slice(1).replace(/Async$/, "");
|
||||
const method = name;
|
||||
|
||||
imports.push(`${name}Request`, `${name}Response`);
|
||||
methods.push({ name, method });
|
||||
}
|
||||
|
||||
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 += methods
|
||||
.map(({ name, method }) => {
|
||||
return ` ${method}(data: ${name}Request) {
|
||||
return this.request("${name}Request", ${name}Request, data, ${name}Response);
|
||||
}\n`;
|
||||
})
|
||||
.join("");
|
||||
content += "}\n";
|
||||
|
||||
await writeFile(TARGET, content);
|
||||
@@ -1,13 +1,13 @@
|
||||
import { VertexAttribute, vertexAttributeVectorSizes } from '@blitzkit/core';
|
||||
import { VertexAttribute, vertexAttributeVectorSizes } from "@blitzkit/core";
|
||||
|
||||
export const vertexAttributeGLTFName: Partial<Record<VertexAttribute, string>> =
|
||||
{
|
||||
[VertexAttribute.VERTEX]: 'POSITION',
|
||||
[VertexAttribute.NORMAL]: 'NORMAL',
|
||||
[VertexAttribute.TEXCOORD0]: 'TEXCOORD_0',
|
||||
[VertexAttribute.TEXCOORD1]: 'TEXCOORD_0',
|
||||
[VertexAttribute.TEXCOORD2]: 'TEXCOORD_0',
|
||||
[VertexAttribute.TEXCOORD3]: 'TEXCOORD_0',
|
||||
[VertexAttribute.VERTEX]: "POSITION",
|
||||
[VertexAttribute.NORMAL]: "NORMAL",
|
||||
[VertexAttribute.TEXCOORD0]: "TEXCOORD_0",
|
||||
[VertexAttribute.TEXCOORD1]: "TEXCOORD_1",
|
||||
[VertexAttribute.TEXCOORD2]: "TEXCOORD_2",
|
||||
[VertexAttribute.TEXCOORD3]: "TEXCOORD_3",
|
||||
};
|
||||
export const vertexAttributeGltfVectorSizes = {
|
||||
...vertexAttributeVectorSizes,
|
||||
|
||||
@@ -4,35 +4,35 @@ import {
|
||||
ScgReadStream,
|
||||
Textures,
|
||||
VertexAttribute,
|
||||
} from '@blitzkit/core';
|
||||
import { Document, Material, Node, Scene } from '@gltf-transform/core';
|
||||
import { dedup, prune } from '@gltf-transform/functions';
|
||||
import { times } from 'lodash-es';
|
||||
import { dirname } from 'path';
|
||||
import { Matrix4, Quaternion, Vector3, Vector4Tuple } from 'three';
|
||||
import { readDVPLFile } from '../readDVPLFile';
|
||||
import { readTexture } from '../readTexture';
|
||||
import { TextureMutation } from '../readTexture/constants';
|
||||
} from "@blitzkit/core";
|
||||
import { Document, Material, Node, Scene } from "@gltf-transform/core";
|
||||
import { dedup, prune } from "@gltf-transform/functions";
|
||||
import { times } from "lodash-es";
|
||||
import { dirname } from "path";
|
||||
import { readBaseColor } from "../readBaseColor";
|
||||
import { readDVPLFile } from "../readDVPLFile";
|
||||
import { readNormal } from "../readNormal";
|
||||
import { readRoughnessMetallic } from "../readRoughnessMetallic";
|
||||
import {
|
||||
vertexAttributeGLTFName,
|
||||
vertexAttributeGltfVectorSizes,
|
||||
} from './constants';
|
||||
} from "./constants";
|
||||
|
||||
const ERROR_ON_UNKNOWN_COMPONENT = false;
|
||||
|
||||
const omitMeshNames = {
|
||||
start: ['chassis_chassis_', 'chassis_track_crash_', 'HP_'],
|
||||
end: ['_POINT'],
|
||||
start: ["chassis_chassis_", "chassis_track_crash_", "HP_"],
|
||||
end: ["_POINT"],
|
||||
};
|
||||
|
||||
export async function extractModel(data: string, path: string) {
|
||||
const sc2Path = `${data}/3d/${path}.sc2`;
|
||||
const scgPath = `${data}/3d/${path}.scg`;
|
||||
const sc2 = new Sc2ReadStream(
|
||||
(await readDVPLFile(sc2Path)).buffer as ArrayBuffer,
|
||||
(await readDVPLFile(sc2Path)).buffer as ArrayBuffer
|
||||
).sc2();
|
||||
const scg = new ScgReadStream(
|
||||
(await readDVPLFile(scgPath)).buffer as ArrayBuffer,
|
||||
(await readDVPLFile(scgPath)).buffer as ArrayBuffer
|
||||
).scg();
|
||||
const document = new Document();
|
||||
const scene = document.createScene();
|
||||
@@ -41,8 +41,8 @@ export async function extractModel(data: string, path: string) {
|
||||
|
||||
// create materials
|
||||
await Promise.all(
|
||||
sc2['#dataNodes'].map(async (node) => {
|
||||
const id = new DataView(node['#id']).getBigUint64(0, true);
|
||||
sc2["#dataNodes"].map(async (node) => {
|
||||
const id = new DataView(node["#id"]).getBigUint64(0, true);
|
||||
|
||||
if (node.parentMaterialKey !== undefined) {
|
||||
/**
|
||||
@@ -58,7 +58,7 @@ export async function extractModel(data: string, path: string) {
|
||||
|
||||
if (node.textures) {
|
||||
textures = node.textures;
|
||||
} else if (typeof node.configCount === 'number') {
|
||||
} else if (typeof node.configCount === "number") {
|
||||
textures = node.configArchive_0.textures;
|
||||
}
|
||||
|
||||
@@ -66,26 +66,27 @@ export async function extractModel(data: string, path: string) {
|
||||
material.setBaseColorTexture(
|
||||
document
|
||||
.createTexture(node.materialName)
|
||||
.setMimeType('image/jpeg')
|
||||
.setMimeType("image/jpeg")
|
||||
.setImage(
|
||||
await readTexture(
|
||||
`${data}/3d/${dirname(path)}/${textures.albedo}`,
|
||||
TextureMutation.Albedo,
|
||||
),
|
||||
),
|
||||
await readBaseColor(
|
||||
`${data}/3d/${dirname(path)}/${textures.baseColorMap ?? textures.albedo}`,
|
||||
textures.miscMap
|
||||
? `${data}/3d/${dirname(path)}/${textures.miscMap}`
|
||||
: undefined
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if (textures.baseRMMap) {
|
||||
material.setMetallicRoughnessTexture(
|
||||
document
|
||||
.createTexture(node.materialName)
|
||||
.setMimeType('image/jpeg')
|
||||
.setMimeType("image/jpeg")
|
||||
.setImage(
|
||||
await readTexture(
|
||||
`${data}/3d/${dirname(path)}/${textures.baseRMMap}`,
|
||||
TextureMutation.RoughnessMetallicness,
|
||||
),
|
||||
),
|
||||
await readRoughnessMetallic(
|
||||
`${data}/3d/${dirname(path)}/${textures.baseRMMap}`
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -95,47 +96,33 @@ export async function extractModel(data: string, path: string) {
|
||||
material.setNormalTexture(
|
||||
document
|
||||
.createTexture(node.materialName)
|
||||
.setMimeType('image/jpeg')
|
||||
.setMimeType("image/jpeg")
|
||||
.setImage(
|
||||
await readTexture(
|
||||
await readNormal(
|
||||
`${data}/3d/${dirname(path)}/${
|
||||
textures.baseNormalMap ?? textures.normalmap
|
||||
}`,
|
||||
isBase ? TextureMutation.Normal : undefined,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (textures.miscMap) {
|
||||
material.setOcclusionTexture(
|
||||
document
|
||||
.createTexture(node.materialName)
|
||||
.setMimeType('image/jpeg')
|
||||
.setImage(
|
||||
await readTexture(
|
||||
`${data}/3d/${dirname(path)}/${textures.miscMap}`,
|
||||
TextureMutation.Miscellaneous,
|
||||
),
|
||||
),
|
||||
isBase
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
materials.set(
|
||||
new DataView(node['#id']).getBigUint64(0, true),
|
||||
material,
|
||||
new DataView(node["#id"]).getBigUint64(0, true),
|
||||
material
|
||||
);
|
||||
}
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
// replace children materials with parents
|
||||
materials.forEach((material, id) => {
|
||||
if (typeof material !== 'bigint') return;
|
||||
if (typeof material !== "bigint") return;
|
||||
|
||||
let resolvedMaterial: Material | bigint | undefined = material;
|
||||
|
||||
while (typeof resolvedMaterial === 'bigint') {
|
||||
while (typeof resolvedMaterial === "bigint") {
|
||||
const linkedParentMaterial = materials.get(resolvedMaterial);
|
||||
|
||||
resolvedMaterial = linkedParentMaterial;
|
||||
@@ -155,36 +142,36 @@ export async function extractModel(data: string, path: string) {
|
||||
const node = document.createNode(hierarchy.name);
|
||||
const components = times(
|
||||
hierarchy.components.count,
|
||||
(index) => hierarchy.components[index.toString().padStart(4, '0')],
|
||||
(index) => hierarchy.components[index.toString().padStart(4, "0")]
|
||||
);
|
||||
|
||||
components.forEach((component) => {
|
||||
switch (component['comp.typename']) {
|
||||
case 'LodComponent':
|
||||
switch (component["comp.typename"]) {
|
||||
case "LodComponent":
|
||||
// found and used later by transform component
|
||||
// TODO: lod component always shows up before transform component; cache it before parsing transform
|
||||
break;
|
||||
|
||||
case 'TransformComponent': {
|
||||
node.setTranslation(component['tc.localTranslation']);
|
||||
node.setRotation(component['tc.localRotation']);
|
||||
node.setScale(component['tc.localScale']);
|
||||
case "TransformComponent": {
|
||||
node.setTranslation(component["tc.localTranslation"]);
|
||||
node.setRotation(component["tc.localRotation"]);
|
||||
node.setScale(component["tc.localScale"]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'RenderComponent': {
|
||||
const renderObject = component['rc.renderObj'];
|
||||
case "RenderComponent": {
|
||||
const renderObject = component["rc.renderObj"];
|
||||
|
||||
times(renderObject['ro.batchCount'], (batchIndex): void => {
|
||||
times(renderObject["ro.batchCount"], (batchIndex): void => {
|
||||
const lodIndex = renderObject[`rb${batchIndex}.lodIndex`];
|
||||
|
||||
if (lodIndex !== 0) return;
|
||||
|
||||
const batchKey = batchIndex.toString().padStart(4, '0');
|
||||
const batch = renderObject['ro.batches'][batchKey];
|
||||
const material = materials.get(batch['rb.nmatname']);
|
||||
const polygonGroup = scg.get(batch['rb.datasource']);
|
||||
const batchKey = batchIndex.toString().padStart(4, "0");
|
||||
const batch = renderObject["ro.batches"][batchKey];
|
||||
const material = materials.get(batch["rb.nmatname"]);
|
||||
const polygonGroup = scg.get(batch["rb.datasource"]);
|
||||
|
||||
if (!(material instanceof Material)) {
|
||||
// probably shadow material
|
||||
@@ -192,14 +179,14 @@ export async function extractModel(data: string, path: string) {
|
||||
}
|
||||
if (polygonGroup === undefined) {
|
||||
throw new Error(
|
||||
`Missing polygon group ${batch['rb.datasource']}`,
|
||||
`Missing polygon group ${batch["rb.datasource"]}`
|
||||
);
|
||||
}
|
||||
|
||||
const lodNode = document.createNode(batchKey);
|
||||
const indicesAccessor = document
|
||||
.createAccessor()
|
||||
.setType('SCALAR')
|
||||
.setType("SCALAR")
|
||||
.setArray(new Uint16Array(polygonGroup.indices))
|
||||
.setBuffer(buffer);
|
||||
const primitive = document
|
||||
@@ -233,7 +220,7 @@ export async function extractModel(data: string, path: string) {
|
||||
const vertexSize = vertexAttributeGltfVectorSizes[attribute];
|
||||
const attributeAccessor = document
|
||||
.createAccessor(name)
|
||||
.setType(vertexSize === 1 ? 'SCALAR' : `VEC${vertexSize}`)
|
||||
.setType(vertexSize === 1 ? "SCALAR" : `VEC${vertexSize}`)
|
||||
.setArray(new Float32Array(value.flat()))
|
||||
.setBuffer(buffer);
|
||||
|
||||
@@ -241,7 +228,7 @@ export async function extractModel(data: string, path: string) {
|
||||
});
|
||||
|
||||
const mesh = document
|
||||
.createMesh(batch['##name'])
|
||||
.createMesh(batch["##name"])
|
||||
.addPrimitive(primitive);
|
||||
lodNode.setMesh(mesh);
|
||||
node.addChild(lodNode);
|
||||
@@ -253,24 +240,24 @@ export async function extractModel(data: string, path: string) {
|
||||
default: {
|
||||
if (ERROR_ON_UNKNOWN_COMPONENT) {
|
||||
throw new Error(
|
||||
`Unhandled component type: ${component['comp.typename']}`,
|
||||
`Unhandled component type: ${component["comp.typename"]}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (hierarchy['#hierarchy']) {
|
||||
parseHierarchies(hierarchy['#hierarchy'], node);
|
||||
if (hierarchy["#hierarchy"]) {
|
||||
parseHierarchies(hierarchy["#hierarchy"], node);
|
||||
}
|
||||
|
||||
parent.addChild(node);
|
||||
});
|
||||
}
|
||||
|
||||
parseHierarchies(sc2['#hierarchy'], scene);
|
||||
parseHierarchies(sc2["#hierarchy"], scene);
|
||||
|
||||
scene.addChild(document.createNode('test'));
|
||||
scene.addChild(document.createNode("test"));
|
||||
|
||||
await document.transform(prune({ keepAttributes: true }), dedup());
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import sharp from "sharp";
|
||||
import { readTexture } from "./readTexture";
|
||||
|
||||
export async function readBaseColor(path: string, occlusionPath?: string) {
|
||||
const baseRaw = await readTexture(path);
|
||||
const occlusionRaw = occlusionPath
|
||||
? await readTexture(occlusionPath)
|
||||
: undefined;
|
||||
const base = await sharp(baseRaw.data, { raw: baseRaw })
|
||||
.removeAlpha()
|
||||
.raw()
|
||||
.toBuffer();
|
||||
const occlusion = occlusionRaw
|
||||
? await sharp(occlusionRaw.data, { raw: occlusionRaw })
|
||||
.extractChannel(3)
|
||||
.raw()
|
||||
.toBuffer()
|
||||
: undefined;
|
||||
|
||||
const combined = Buffer.alloc(baseRaw.width * baseRaw.height * 3);
|
||||
|
||||
for (let i = 0; i < baseRaw.width * baseRaw.height; i++) {
|
||||
let c = 1;
|
||||
|
||||
if (occlusionRaw) {
|
||||
const x = i % baseRaw.width;
|
||||
const y = Math.floor(i / baseRaw.width);
|
||||
const u = Math.floor(x * (occlusionRaw.width / baseRaw.width));
|
||||
const v = Math.floor(y * (occlusionRaw.height / baseRaw.height));
|
||||
|
||||
const occlusionI = u + v * occlusionRaw.width;
|
||||
|
||||
c = occlusion![occlusionI] / 255;
|
||||
}
|
||||
|
||||
combined[i * 3 + 0] = Math.round(base[i * 3 + 0] * c);
|
||||
combined[i * 3 + 1] = Math.round(base[i * 3 + 1] * c);
|
||||
combined[i * 3 + 2] = Math.round(base[i * 3 + 2] * c);
|
||||
}
|
||||
|
||||
const image = await sharp(combined, {
|
||||
raw: { width: baseRaw.width, height: baseRaw.height, channels: 3 },
|
||||
})
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
|
||||
return image;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import sharp from "sharp";
|
||||
import { readTexture } from "./readTexture";
|
||||
|
||||
export async function readNormal(path: string, isBase: boolean) {
|
||||
const raw = await readTexture(path);
|
||||
|
||||
if (!isBase) {
|
||||
return await sharp(raw.data, { raw }).removeAlpha().jpeg().toBuffer();
|
||||
}
|
||||
|
||||
const bytes = 4 * raw.width * raw.height;
|
||||
|
||||
for (let index = 0; index < bytes; index += 4) {
|
||||
/**
|
||||
* Red is always 255 and blue is always 0. Only alpha and green contain any
|
||||
* sort of information.
|
||||
*/
|
||||
let x = raw.data[index + 3] * (2 / 255) - 1;
|
||||
let y = raw.data[index + 1] * (2 / 255) - 1;
|
||||
let z = Math.sqrt(Math.max(0, 1 - x ** 2 - y ** 2));
|
||||
|
||||
raw.data[index] = Math.round((x + 1) * (255 / 2));
|
||||
raw.data[index + 1] = Math.round((y + 1) * (255 / 2));
|
||||
raw.data[index + 2] = Math.round((z + 1) * (255 / 2));
|
||||
raw.data[index + 3] = 255;
|
||||
}
|
||||
|
||||
return await sharp(raw.data, { raw }).jpeg().toBuffer();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import sharp from "sharp";
|
||||
import { readTexture } from "./readTexture";
|
||||
|
||||
export async function readRoughnessMetallic(path: string) {
|
||||
const raw = await readTexture(path);
|
||||
const blitz = sharp(raw.data, { raw: raw });
|
||||
const metallicness = await blitz?.extractChannel(1).raw().toBuffer();
|
||||
const roughness = await blitz?.extractChannel(3).raw().toBuffer();
|
||||
|
||||
const combined = Buffer.alloc(raw.width * raw.height * 3);
|
||||
|
||||
for (let i = 0; i < raw.width * raw.height; i++) {
|
||||
combined[i * 3 + 1] = roughness[i];
|
||||
combined[i * 3 + 2] = metallicness[i];
|
||||
}
|
||||
|
||||
const image = await sharp(combined, {
|
||||
raw: { width: raw.width, height: raw.height, channels: 3 },
|
||||
})
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
|
||||
return image;
|
||||
}
|
||||
@@ -1,81 +1,83 @@
|
||||
import { DdsReadStream, PvrReadStream } from '@blitzkit/core';
|
||||
import { existsSync } from 'fs';
|
||||
import sharp from 'sharp';
|
||||
import { DATA_COMPRESSED } from '../../../buildAssets/constants';
|
||||
import { readDVPLFile } from '../readDVPLFile';
|
||||
import { TextureMutation } from './constants';
|
||||
import { DdsReadStream, PvrReadStream } from "@blitzkit/core";
|
||||
import { existsSync } from "fs";
|
||||
import sharp from "sharp";
|
||||
import { DATA_COMPRESSED } from "../../../buildAssets/constants";
|
||||
import { readDVPLFile } from "../readDVPLFile";
|
||||
import { TextureMutation } from "./constants";
|
||||
|
||||
export async function readTexture(path: string, mutation?: TextureMutation) {
|
||||
const ddsTexturePath = path.replace('.tex', '.dx11.dds');
|
||||
export async function readTexture(path: string) {
|
||||
const ddsTexturePath = path.replace(".tex", ".dx11.dds");
|
||||
const isDds = existsSync(
|
||||
`${ddsTexturePath}${DATA_COMPRESSED ? '.dvpl' : ''}`,
|
||||
`${ddsTexturePath}${DATA_COMPRESSED ? ".dvpl" : ""}`
|
||||
);
|
||||
const resolvedTexturePath = isDds
|
||||
? ddsTexturePath
|
||||
: ddsTexturePath.replace('.dds', '.pvr');
|
||||
: ddsTexturePath.replace(".dds", ".pvr");
|
||||
const decompressedDvpl = await readDVPLFile(`${resolvedTexturePath}.dvpl`);
|
||||
|
||||
const raw = isDds
|
||||
? await new DdsReadStream(decompressedDvpl.buffer as ArrayBuffer).dds()
|
||||
: new PvrReadStream(decompressedDvpl.buffer as ArrayBuffer).pvr();
|
||||
|
||||
switch (mutation) {
|
||||
case TextureMutation.Normal: {
|
||||
const bytes = 4 * raw.width * raw.height;
|
||||
for (let index = 0; index < bytes; index += 4) {
|
||||
/**
|
||||
* Red is always 255 and blue is always 0. Only alpha and green contain any
|
||||
* sort of information.
|
||||
*/
|
||||
let x = raw.data[index + 3] * (2 / 255) - 1;
|
||||
let y = raw.data[index + 1] * (2 / 255) - 1;
|
||||
const underSqrt = 1 - x ** 2 - y ** 2;
|
||||
let z = underSqrt > 0 ? Math.sqrt(underSqrt) : -Math.sqrt(-underSqrt);
|
||||
return raw;
|
||||
|
||||
raw.data[index] = Math.round((x + 1) * (255 / 2));
|
||||
raw.data[index + 1] = Math.round((y + 1) * (255 / 2));
|
||||
raw.data[index + 2] = Math.round((z + 1) * (255 / 2));
|
||||
raw.data[index + 3] = 255;
|
||||
}
|
||||
// switch (mutation) {
|
||||
// case TextureMutation.Normal: {
|
||||
// const bytes = 4 * raw.width * raw.height;
|
||||
// for (let index = 0; index < bytes; index += 4) {
|
||||
// /**
|
||||
// * Red is always 255 and blue is always 0. Only alpha and green contain any
|
||||
// * sort of information.
|
||||
// */
|
||||
// let x = raw.data[index + 3] * (2 / 255) - 1;
|
||||
// let y = raw.data[index + 1] * (2 / 255) - 1;
|
||||
// const underSqrt = 1 - x ** 2 - y ** 2;
|
||||
// let z = underSqrt > 0 ? Math.sqrt(underSqrt) : -Math.sqrt(-underSqrt);
|
||||
|
||||
return await sharp(raw.data, { raw }).jpeg().toBuffer();
|
||||
}
|
||||
// raw.data[index] = Math.round((x + 1) * (255 / 2));
|
||||
// raw.data[index + 1] = Math.round((y + 1) * (255 / 2));
|
||||
// raw.data[index + 2] = Math.round((z + 1) * (255 / 2));
|
||||
// raw.data[index + 3] = 255;
|
||||
// }
|
||||
|
||||
case TextureMutation.RoughnessMetallicness: {
|
||||
const newImage = sharp(Buffer.alloc(raw.width * raw.height), {
|
||||
raw: { ...raw, channels: 1 },
|
||||
});
|
||||
const metallicness = sharp(raw.data, { raw })
|
||||
.extractChannel('green')
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
const roughness = sharp(raw.data, { raw })
|
||||
.extractChannel('alpha')
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
// return await sharp(raw.data, { raw }).jpeg().toBuffer();
|
||||
// }
|
||||
|
||||
return await newImage
|
||||
.joinChannel(await Promise.all([roughness, metallicness]))
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
}
|
||||
// case TextureMutation.RoughnessMetallicness: {
|
||||
// const newImage = sharp(Buffer.alloc(raw.width * raw.height), {
|
||||
// raw: { ...raw, channels: 1 },
|
||||
// });
|
||||
// const metallicness = sharp(raw.data, { raw })
|
||||
// .extractChannel("green")
|
||||
// .jpeg()
|
||||
// .toBuffer();
|
||||
// const roughness = sharp(raw.data, { raw })
|
||||
// .extractChannel("alpha")
|
||||
// .jpeg()
|
||||
// .toBuffer();
|
||||
|
||||
case TextureMutation.Miscellaneous: {
|
||||
/**
|
||||
* Alpha is ambient occlusion and green is emissive. But very few tanks
|
||||
* use emissive so I am ignoring it for now.
|
||||
*/
|
||||
return await sharp(raw.data, { raw })
|
||||
.extractChannel('alpha')
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
}
|
||||
// return await newImage
|
||||
// .joinChannel(await Promise.all([roughness, metallicness]))
|
||||
// .jpeg()
|
||||
// .toBuffer();
|
||||
// }
|
||||
|
||||
case TextureMutation.Albedo: {
|
||||
return await sharp(raw.data, { raw }).removeAlpha().jpeg().toBuffer();
|
||||
}
|
||||
// case TextureMutation.Miscellaneous: {
|
||||
// /**
|
||||
// * Alpha is ambient occlusion and green is emissive. But very few tanks
|
||||
// * use emissive so I am ignoring it for now.
|
||||
// */
|
||||
// return await sharp(raw.data, { raw })
|
||||
// .extractChannel("alpha")
|
||||
// .jpeg()
|
||||
// .toBuffer();
|
||||
// }
|
||||
|
||||
default:
|
||||
return await sharp(raw.data, { raw }).jpeg().toBuffer();
|
||||
}
|
||||
// case TextureMutation.Albedo: {
|
||||
// return await sharp(raw.data, { raw }).removeAlpha().jpeg().toBuffer();
|
||||
// }
|
||||
|
||||
// default:
|
||||
// return await sharp(raw.data, { raw }).jpeg().toBuffer();
|
||||
// }
|
||||
}
|
||||
@@ -1,57 +1,61 @@
|
||||
import { exec as execSync } from 'child_process';
|
||||
import { existsSync } from 'fs';
|
||||
import { readdir, rm, writeFile } from 'fs/promises';
|
||||
import { chunk } from 'lodash-es';
|
||||
import { promisify } from 'util';
|
||||
import { exec as execSync } from "child_process";
|
||||
import { existsSync } from "fs";
|
||||
import { readdir, rm } from "fs/promises";
|
||||
import { promisify } from "util";
|
||||
|
||||
const MAX_COMMAND_LENGTH = 2 ** 11;
|
||||
|
||||
const roots = [
|
||||
// "packages/core/src/protos",
|
||||
"submodules/blitzkit-closed/protos",
|
||||
];
|
||||
|
||||
const exec = promisify(execSync);
|
||||
|
||||
let executableFileExtension: string;
|
||||
|
||||
if (existsSync('node_modules/.bin/protoc-gen-ts_proto.exe')) {
|
||||
executableFileExtension = '.exe';
|
||||
if (existsSync("node_modules/.bin/protoc-gen-ts_proto.exe")) {
|
||||
executableFileExtension = ".exe";
|
||||
} else {
|
||||
executableFileExtension = '';
|
||||
executableFileExtension = "";
|
||||
}
|
||||
|
||||
// await compile('packages/core/src/protos');
|
||||
await compile('submodules/blitzkit-closed/src/unreal/protos');
|
||||
|
||||
async function compile(root: string) {
|
||||
const filesRaw = await readdir(root);
|
||||
for (const root of roots) {
|
||||
const filesRaw = await readdir(`${root}`);
|
||||
const files: string[] = [];
|
||||
|
||||
for (const file of filesRaw) {
|
||||
if (!file.endsWith('.ts')) continue;
|
||||
await rm(`${root}/${file}`);
|
||||
if (file.endsWith(".proto")) files.push(file);
|
||||
if (file.endsWith(".ts")) await rm(`${root}/${file}`);
|
||||
}
|
||||
|
||||
const allFiles = filesRaw.filter(
|
||||
(file) => file.endsWith('.proto') && !file.startsWith('_'),
|
||||
);
|
||||
|
||||
for (const files of chunk(allFiles, 32)) {
|
||||
const args = [
|
||||
`-I=${root}`,
|
||||
while (files.length > 0) {
|
||||
let command = [
|
||||
"protoc",
|
||||
`--plugin=./node_modules/.bin/protoc-gen-ts_proto${executableFileExtension}`,
|
||||
'--ts_proto_opt=esModuleInterop=true',
|
||||
'--ts_proto_opt=oneof=unions-value',
|
||||
// '--ts_proto_opt=removeEnumPrefix=true',
|
||||
'--ts_proto_opt=unrecognizedEnum=false',
|
||||
'--ts_proto_opt=snakeToCamel=false',
|
||||
"--ts_proto_opt=esModuleInterop=true",
|
||||
"--ts_proto_opt=oneof=unions-value",
|
||||
// "--ts_proto_opt=removeEnumPrefix=true",
|
||||
"--ts_proto_opt=unrecognizedEnum=false",
|
||||
"--ts_proto_opt=snakeToCamel=false",
|
||||
`--ts_proto_out=${root}`,
|
||||
...files.map((file) => `${root}/${file}`),
|
||||
];
|
||||
`-I=${root}`,
|
||||
].join(" ");
|
||||
|
||||
await exec(`protoc ${args.join(' ')}`);
|
||||
while (true) {
|
||||
if (files.length === 0) break;
|
||||
|
||||
const file = files.at(-1);
|
||||
const newLine = ` ${root}/${file}`;
|
||||
|
||||
if (command.length + newLine.length < MAX_COMMAND_LENGTH) {
|
||||
command += newLine;
|
||||
files.pop();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await exec(command);
|
||||
}
|
||||
|
||||
await writeFile(
|
||||
`${root}/index.ts`,
|
||||
'// @ts-nocheck\n' +
|
||||
allFiles
|
||||
.sort()
|
||||
.map((file) => file.replace('.proto', ''))
|
||||
.map((file) => `export * from './${file}.ts';`)
|
||||
.join('\n'),
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src/**/*"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../../"
|
||||
}
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "varuna",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"dependencies": {
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import { produce } from "immer";
|
||||
import { merge } from "lodash-es";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
export class Varuna<Type, Arguments = void> {
|
||||
private listeners = new Set<(state: Type) => void>();
|
||||
|
||||
private initialized = false;
|
||||
private _initial?: Type;
|
||||
private _state?: Type;
|
||||
|
||||
constructor(
|
||||
private creator: Type | ((...args: Arguments[]) => Type),
|
||||
private persistence?: string
|
||||
) {
|
||||
if (typeof creator !== "function") this.initialize(creator);
|
||||
}
|
||||
|
||||
private initialize(initial: Type) {
|
||||
let data = initial;
|
||||
|
||||
if (this.persistence && typeof localStorage !== "undefined") {
|
||||
const instance = this;
|
||||
const dehydrated = localStorage.getItem(this.persistence);
|
||||
|
||||
if (dehydrated) {
|
||||
const rehydrated = JSON.parse(dehydrated) as unknown;
|
||||
data = merge(data, rehydrated);
|
||||
}
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
localStorage.setItem(this.persistence!, JSON.stringify(instance.state));
|
||||
});
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
this._initial = data;
|
||||
this._state = data;
|
||||
}
|
||||
|
||||
useInitialization(...args: Arguments[]) {
|
||||
if (this.initialized) return;
|
||||
|
||||
const initializedThisMount = useRef(false);
|
||||
|
||||
if (initializedThisMount.current) return;
|
||||
|
||||
if (typeof this.creator !== "function") {
|
||||
throw new Error("Provider must be used with a function creator");
|
||||
}
|
||||
|
||||
this.initialize((this.creator as (...args: Arguments[]) => Type)(...args));
|
||||
initializedThisMount.current = true;
|
||||
}
|
||||
|
||||
private dispatch() {
|
||||
const state = this.state;
|
||||
console.log("dispatched");
|
||||
this.listeners.forEach((callback) => callback(state));
|
||||
}
|
||||
|
||||
on<Slice>(slicer: (state: Type) => Slice, callback: (state: Slice) => void) {
|
||||
let last = slicer(this.state);
|
||||
|
||||
function listener(state: Type) {
|
||||
const next = slicer(state);
|
||||
|
||||
if (next !== last) {
|
||||
callback(next);
|
||||
last = next;
|
||||
}
|
||||
}
|
||||
|
||||
this.listeners.add(listener);
|
||||
|
||||
return () => {
|
||||
this.listeners.delete(listener);
|
||||
};
|
||||
}
|
||||
|
||||
mutate(mutator: (state: Type) => void) {
|
||||
this.set(produce(this.state, mutator));
|
||||
}
|
||||
|
||||
private assertInitialized() {
|
||||
if (!this.initialized) {
|
||||
throw new Error("Varuna store used before initialization");
|
||||
}
|
||||
}
|
||||
|
||||
get state() {
|
||||
this.assertInitialized();
|
||||
return this._state as Type;
|
||||
}
|
||||
|
||||
set(state: Type) {
|
||||
this.assertInitialized();
|
||||
this._state = state;
|
||||
this.dispatch();
|
||||
}
|
||||
|
||||
get initial() {
|
||||
this.assertInitialized();
|
||||
return this._initial as Type;
|
||||
}
|
||||
|
||||
use<Slice = Type>(
|
||||
slicer: (state: Type) => Slice = (state) => state as unknown as Slice
|
||||
) {
|
||||
return this.useDeferred(slicer, slicer(this.state));
|
||||
}
|
||||
|
||||
useDeferred<Slice = Type>(slicer: (state: Type) => Slice, initial: Slice) {
|
||||
const instance = this;
|
||||
const [slice, setSlice] = useState(initial);
|
||||
|
||||
useEffect(() => instance.on(slicer, setSlice), []);
|
||||
|
||||
return slice;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json"
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
@@ -1,47 +0,0 @@
|
||||
# Astro Starter Kit: Minimal
|
||||
|
||||
```sh
|
||||
bun create astro@latest -- --template minimal
|
||||
```
|
||||
|
||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
|
||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal)
|
||||
[](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/minimal/devcontainer.json)
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```text
|
||||
/
|
||||
├── public/
|
||||
├── src/
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `bun install` | Installs dependencies |
|
||||
| `bun dev` | Starts local dev server at `localhost:4321` |
|
||||
| `bun build` | Build your production site to `./dist/` |
|
||||
| `bun preview` | Preview your build locally, before deploying |
|
||||
| `bun astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `bun astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
@@ -1,14 +0,0 @@
|
||||
// @ts-check
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
import react from '@astrojs/react';
|
||||
|
||||
export default defineConfig({
|
||||
integrations: [react(), sitemap()],
|
||||
devToolbar: { enabled: false },
|
||||
outDir: '../../dist/website',
|
||||
|
||||
prefetch: true,
|
||||
site: 'https://blitzkit.app',
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"name": "@blitzkit/website-ue",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"lint": "astro check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/partytown": "^2.1.4",
|
||||
"@astrojs/react": "^4.3.0",
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"@astrolib/seo": "^1.0.0-beta.8",
|
||||
"@blitzkit/closed": "workspace:*",
|
||||
"@blitzkit/core": "workspace:*",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@radix-ui/themes": "^3.1.4",
|
||||
"@react-three/drei": "^9.115.0",
|
||||
"@react-three/fiber": "^8.17.10",
|
||||
"@types/ffi-napi": "^4.0.10",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/pako": "^2.0.3",
|
||||
"@types/react": "^19.1.5",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"@types/ref-napi": "^3.0.12",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/three": "^0.160.0",
|
||||
"astro": "^5.8.0",
|
||||
"diff": "^8.0.2",
|
||||
"ffi-napi": "^4.0.3",
|
||||
"fuzzysort": "^3.1.0",
|
||||
"immer": "^10.1.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"minimatch": "^10.0.1",
|
||||
"minizlib": "^3.0.2",
|
||||
"pako": "^2.1.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"ref-napi": "^3.0.3",
|
||||
"semver": "^7.7.2",
|
||||
"sharp": "^0.34.2",
|
||||
"three": "^0.169.0",
|
||||
"zustand": "^4.5.5"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_741_237)">
|
||||
<rect width="60" height="60" fill="#FDD1EA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M54.6274 0L55.4558 0.828427L54.0416 2.24264L51.799 0L54.6274 0ZM5.37258 0L4.54416 0.828427L5.95837 2.24264L8.20101 0L5.37258 0ZM48.9706 0L52.6274 3.65685L51.2132 5.07107L46.1421 0L48.9706 0ZM11.0294 0L7.37258 3.65685L8.7868 5.07107L13.8579 0L11.0294 0ZM43.3137 0L49.799 6.48528L48.3848 7.8995L40.4853 0H43.3137ZM16.6863 0L10.201 6.48528L11.6152 7.8995L19.5147 0L16.6863 0ZM37.6569 0L46.9706 9.31371L45.5564 10.7279L34.8284 0L37.6569 0ZM22.3431 0L13.0294 9.31371L14.4437 10.7279L25.1716 0L22.3431 0ZM32 0L44.1421 12.1421L42.7279 13.5563L30 0.828427L17.2721 13.5563L15.8579 12.1421L28 0H32ZM0.284271 0L28.2843 28L26.8701 29.4142L0 2.54416V0L0.284271 0ZM0 5.37258L25.4558 30.8284L24.0416 32.2426L0 8.20101L0 5.37258ZM0 11.0294L22.6274 33.6569L21.2132 35.0711L0 13.8579L0 11.0294ZM0 16.6863L19.799 36.4853L18.3848 37.8995L0 19.5147L0 16.6863ZM0 22.3431L16.9706 39.3137L15.5563 40.7279L0 25.1716L0 22.3431ZM0 28L14.1421 42.1421L12.7279 43.5564L0 30.8284V28ZM0 33.6569L11.3137 44.9706L9.8995 46.3848L0 36.4853L0 33.6569ZM0 39.3137L8.48528 47.799L7.07107 49.2132L0 42.1421L0 39.3137ZM0 44.9706L5.65685 50.6274L4.24264 52.0416L0 47.799L0 44.9706ZM0 50.6274L2.82843 53.4558L1.41421 54.8701L0 53.4558L0 50.6274ZM54.6274 60L30 35.3726L5.37258 60H8.20101L30 38.201L51.799 60H54.6274ZM48.9706 60L30 41.0294L11.0294 60H13.8579L30 43.8579L46.1421 60H48.9706ZM43.3137 60L30 46.6863L16.6863 60H19.5147L30 49.5147L40.4853 60H43.3137ZM37.6569 60L30 52.3431L22.3431 60H25.1716L30 55.1716L34.8284 60H37.6569ZM32 60L30 58L28 60H32ZM59.7157 0L31.7157 28L33.1299 29.4142L60 2.54416V0L59.7157 0ZM60 5.37258L34.5442 30.8284L35.9584 32.2426L60 8.20101V5.37258ZM60 11.0294L37.3726 33.6569L38.7868 35.0711L60 13.8579V11.0294ZM60 16.6863L40.201 36.4853L41.6152 37.8995L60 19.5147V16.6863ZM60 22.3431L43.0294 39.3137L44.4436 40.7279L60 25.1716V22.3431ZM60 28L45.8579 42.1421L47.2721 43.5564L60 30.8284V28ZM60 33.6569L48.6863 44.9706L50.1005 46.3848L60 36.4853V33.6569ZM60 39.3137L51.5147 47.799L52.9289 49.2132L60 42.1421V39.3137ZM60 44.9706L54.3431 50.6274L55.7574 52.0416L60 47.799V44.9706ZM60 50.6274L57.1716 53.4558L58.5858 54.8701L60 53.4558V50.6274ZM39.8995 16.3848L41.3137 14.9706L30 3.65685L18.6863 14.9706L20.1005 16.3848L30 6.48528L39.8995 16.3848ZM37.0711 19.2132L38.4853 17.799L30 9.31371L21.5147 17.799L22.9289 19.2132L30 12.1421L37.0711 19.2132ZM34.2426 22.0416L35.6569 20.6274L30 14.9706L24.3431 20.6274L25.7574 22.0416L30 17.799L34.2426 22.0416ZM31.4142 24.8701L32.8284 23.4558L30 20.6274L27.1716 23.4558L28.5858 24.8701L30 23.4558L31.4142 24.8701ZM56.8701 59.4142L58.2843 58L30 29.7157L1.71573 58L3.12994 59.4142L30 32.5442L56.8701 59.4142Z" fill="#FFBECE"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_741_237">
|
||||
<rect width="60" height="60" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 676 KiB |
|
Before Width: | Height: | Size: 443 KiB |
|
Before Width: | Height: | Size: 330 KiB |
|
Before Width: | Height: | Size: 715 KiB |
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 346 KiB |
|
Before Width: | Height: | Size: 406 KiB |
|
Before Width: | Height: | Size: 360 KiB |
|
Before Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 692 B |
|
Before Width: | Height: | Size: 692 B |
@@ -1,10 +0,0 @@
|
||||
import { Grade } from '@protos/blitz_static_standard_grades_enum';
|
||||
import type { BadgeProps } from '@radix-ui/themes';
|
||||
|
||||
export const GRADE_COLORS = {
|
||||
[Grade.GRADE_UNSPECIFIED]: 'gray',
|
||||
[Grade.GRADE_COMMON]: 'gray',
|
||||
[Grade.GRADE_RARE]: 'blue',
|
||||
[Grade.GRADE_EPIC]: 'purple',
|
||||
[Grade.GRADE_LEGENDARY]: 'amber',
|
||||
} satisfies Record<Grade, BadgeProps['color']>;
|
||||
@@ -1,57 +0,0 @@
|
||||
import { type FlexProps, Box, Flex, Skeleton, Text } from '@radix-ui/themes';
|
||||
import { useRef } from 'react';
|
||||
import { useIntersection } from '../../hooks/useIntersection';
|
||||
import { useLocale } from '../../hooks/useLocale';
|
||||
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
|
||||
import { InlineSkeleton } from '../InlineSkeleton';
|
||||
import type { AvatarGroupProps } from './Group';
|
||||
import { Stack } from './Stack';
|
||||
|
||||
const MAX_AVATARS_PREVIEW = 5;
|
||||
|
||||
export function Content({
|
||||
...props
|
||||
}: FlexProps & MaybeSkeletonComponentProps<AvatarGroupProps>) {
|
||||
const { strings } = useLocale();
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
|
||||
useIntersection(() => props.skeleton && props.onIntersection?.(), container, {
|
||||
disabled: !props.skeleton || props.onIntersection === undefined,
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex
|
||||
ref={container}
|
||||
direction="column"
|
||||
gap="3"
|
||||
style={{ cursor: props.skeleton ? undefined : 'pointer' }}
|
||||
{...props}
|
||||
>
|
||||
<Box width="100%" style={{ aspectRatio: '7 / 8' }} position="relative">
|
||||
{props.skeleton && <Skeleton height="100%" />}
|
||||
|
||||
{!props.skeleton && (
|
||||
<Stack avatars={[...props.avatars].slice(-MAX_AVATARS_PREVIEW)} />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Text
|
||||
size="2"
|
||||
wrap="nowrap"
|
||||
align="center"
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
fontStyle:
|
||||
!props.skeleton && props.name === undefined ? 'italic' : undefined,
|
||||
}}
|
||||
>
|
||||
{props.skeleton ? (
|
||||
<InlineSkeleton width="100%" />
|
||||
) : (
|
||||
(props.name ?? strings.website.tools.avatars.unnamed_avatars)
|
||||
)}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import type { BlitzKitAvatar } from '@protos/blitzkit';
|
||||
import { Dialog } from '@radix-ui/themes';
|
||||
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
|
||||
import { Content } from './Content';
|
||||
import { Popup } from './Popup';
|
||||
|
||||
export interface AvatarGroupProps {
|
||||
name?: string;
|
||||
avatars: { avatar: BlitzKitAvatar }[];
|
||||
}
|
||||
|
||||
export function AvatarGroup(
|
||||
props: MaybeSkeletonComponentProps<AvatarGroupProps>,
|
||||
) {
|
||||
if (props.skeleton) return <Content {...props} />;
|
||||
|
||||
return (
|
||||
<Dialog.Root>
|
||||
<Dialog.Trigger>
|
||||
<Content {...props} />
|
||||
</Dialog.Trigger>
|
||||
|
||||
<Dialog.Content>
|
||||
<Popup {...props} />
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
import { Grade } from '@protos/blitz_static_standard_grades_enum';
|
||||
import {
|
||||
CardStackIcon,
|
||||
DownloadIcon,
|
||||
ExternalLinkIcon,
|
||||
EyeNoneIcon,
|
||||
StarFilledIcon,
|
||||
} from '@radix-ui/react-icons';
|
||||
import { Badge, Box, Button, Dialog, Flex, Link, Text } from '@radix-ui/themes';
|
||||
import { Var } from '../../core/radix/var';
|
||||
import { useLocale } from '../../hooks/useLocale';
|
||||
import { RewardBadge } from '../RewardBadge';
|
||||
import { GRADE_COLORS } from './Colors';
|
||||
import type { AvatarGroupProps } from './Group';
|
||||
|
||||
export function Popup({ name, avatars }: AvatarGroupProps) {
|
||||
const { strings, gameStrings } = useLocale();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog.Title>{name}</Dialog.Title>
|
||||
|
||||
<Flex direction="column" gap="5" mt="5">
|
||||
{[...avatars].reverse().map(({ avatar }, index) => (
|
||||
<Flex key={index} gap="5">
|
||||
<Box
|
||||
m="1"
|
||||
mb="4"
|
||||
height="10rem"
|
||||
style={{
|
||||
aspectRatio: '7 / 8',
|
||||
borderRadius: Var('radius-4'),
|
||||
outline: `${Var('space-1')} solid ${Var(`${GRADE_COLORS[avatar.grade]}-9`)}`,
|
||||
boxShadow: `0 0 ${
|
||||
1.5 * (avatar.grade / Grade.GRADE_LEGENDARY)
|
||||
}rem ${Var(`${GRADE_COLORS[avatar.grade]}-9`)}`,
|
||||
backgroundImage: 'url(/assets/sad-ghost.png)',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
backgroundSize: '3rem',
|
||||
}}
|
||||
position="relative"
|
||||
>
|
||||
<Box
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{
|
||||
backgroundImage: `url(/api/avatars/${avatar.id}.webp)`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Badge
|
||||
mt="1"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '100%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
variant="solid"
|
||||
color={GRADE_COLORS[avatar.grade]}
|
||||
>
|
||||
<StarFilledIcon /> {strings.common.grades[avatar.grade]}
|
||||
</Badge>
|
||||
</Box>
|
||||
|
||||
<Flex direction="column" gap="3" justify="center">
|
||||
<Flex gap="1">
|
||||
<Badge color="gray" variant="surface">
|
||||
<CardStackIcon />
|
||||
{strings.website.tools.avatars.categories[avatar.category]}
|
||||
</Badge>
|
||||
|
||||
{avatar.hidden_if_not_obtained && (
|
||||
<Badge color="gray" variant="surface">
|
||||
<EyeNoneIcon /> {strings.website.tools.avatars.hidden}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{avatar.sale && (
|
||||
<RewardBadge
|
||||
reward={avatar.sale}
|
||||
color="gray"
|
||||
variant="surface"
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Text>
|
||||
{gameStrings[avatar.description] ??
|
||||
(gameStrings[avatar.obtaining]
|
||||
? undefined
|
||||
: strings.website.tools.avatars.no_description)}{' '}
|
||||
{gameStrings[avatar.obtaining]}
|
||||
</Text>
|
||||
|
||||
<Flex gap="2">
|
||||
<Link href={`/api/avatars/${avatar.id}.webp`} download>
|
||||
<Button variant="solid">
|
||||
<DownloadIcon /> {strings.website.tools.avatars.download}
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link href={`/api/avatars/${avatar.id}.webp`} target="_blank">
|
||||
<Button variant="outline">
|
||||
<ExternalLinkIcon /> {strings.website.tools.avatars.enlarge}
|
||||
</Button>
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { Badge, Box } from '@radix-ui/themes';
|
||||
import { literals } from 'packages/i18n/src';
|
||||
import { Var } from '../../core/radix/var';
|
||||
import { useLocale } from '../../hooks/useLocale';
|
||||
import type { AvatarGroupProps } from './Group';
|
||||
|
||||
const dx = '4px';
|
||||
|
||||
export function Stack({ avatars }: { avatars: AvatarGroupProps['avatars'] }) {
|
||||
const { strings } = useLocale();
|
||||
|
||||
return (
|
||||
<>
|
||||
{avatars.map(({ avatar }, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
position="absolute"
|
||||
top={`calc(${dx} * ${index})`}
|
||||
left={`calc(${dx} * ${index})`}
|
||||
width={`calc(100% - ${dx} * ${avatars.length - 1})`}
|
||||
height={`calc(100% - ${dx} * ${avatars.length - 1})`}
|
||||
style={{
|
||||
filter: `brightness(${(index + 1) / avatars.length})`,
|
||||
borderRadius: Var('radius-4'),
|
||||
boxShadow: Var('shadow-2'),
|
||||
overflow: 'hidden',
|
||||
backgroundImage: 'url(/assets/sad-ghost.png)',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
backgroundSize: '3rem',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{
|
||||
backgroundImage: `url(/api/avatars/${avatar.id}.webp)`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
/>
|
||||
|
||||
{avatars.length > 1 && index === avatars.length - 1 && (
|
||||
<Box
|
||||
width="100%"
|
||||
height="100%"
|
||||
style={{
|
||||
background: `linear-gradient(-45deg, ${Var('black-a9')} 25%, ${Var('black-a1')} 50%)`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{avatars.length > 1 && (
|
||||
<Box position="absolute" bottom="1" right="1">
|
||||
<Badge variant="solid" color="gray" highContrast size="2">
|
||||
{literals(strings.common.units.times, [`${avatars.length}`])}
|
||||
</Badge>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { Theme, type ThemeProps } from '@radix-ui/themes';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
interface BlitzKitThemeProps extends ThemeProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function BlitzKitTheme({
|
||||
children,
|
||||
className,
|
||||
style,
|
||||
...props
|
||||
}: BlitzKitThemeProps) {
|
||||
return (
|
||||
<Theme
|
||||
style={{ minHeight: 'unset', display: 'contents', ...style }}
|
||||
className={`blitzkit-theme ${className}`}
|
||||
grayColor="mauve"
|
||||
appearance="dark"
|
||||
panelBackground="solid"
|
||||
radius="full"
|
||||
accentColor="iris"
|
||||
suppressHydrationWarning
|
||||
suppressContentEditableWarning
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Theme>
|
||||
);
|
||||
}
|
||||