mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-11-11 03:36:11 +01:00
Compare commits
55
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8478930f2e | ||
|
|
6a74451740 | ||
|
|
09f5d4e3f7 | ||
|
|
0365656a0e | ||
|
|
90a46eeffa | ||
|
|
1249877a8c | ||
|
|
02df1e59d8 | ||
|
|
edce64c1bd | ||
|
|
74b821ac13 | ||
|
|
a5a72f9536 | ||
|
|
fe3d029e74 | ||
|
|
a69ea38947 | ||
|
|
bc9f7054e1 | ||
|
|
53c2f9605f | ||
|
|
95f67a04d7 | ||
|
|
1e6ee2b80d | ||
|
|
6a21c9aaac | ||
|
|
7e11c1e5ce | ||
|
|
2dc4f12246 | ||
|
|
354aca5057 | ||
|
|
c5f156eecc | ||
|
|
37b89d1f2d | ||
|
|
74c358dde5 | ||
|
|
4362746de3 | ||
|
|
d5d97e772a | ||
|
|
654c87dae7 | ||
|
|
e94f8a7cd2 | ||
|
|
d39ffecb3b | ||
|
|
6f8e12657c | ||
|
|
4a79f98f3f | ||
|
|
06de86c5a6 | ||
|
|
37ba6d37d4 | ||
|
|
636add3a18 | ||
|
|
2e13bcfee2 | ||
|
|
af53713c60 | ||
|
|
c37820d2cd | ||
|
|
d51b6bdd18 | ||
|
|
67ce4815df | ||
|
|
8c14c3d093 | ||
|
|
8373fb1b7a | ||
|
|
3aafe4d9f1 | ||
|
|
2c2c3687c7 | ||
|
|
9375f134db | ||
|
|
1eca71d28c | ||
|
|
9bf1554c30 | ||
|
|
f48c5d246d | ||
|
|
5e9aa99755 | ||
|
|
e7386ff766 | ||
|
|
4129a1964f | ||
|
|
64d12bba8b | ||
|
|
69b13fd33a | ||
|
|
bc6bb4f10e | ||
|
|
5adf90bab2 | ||
|
|
bc8cfc9897 | ||
|
|
9037e2caff |
No files matched your search
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"indentSize": 2
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
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
|
||||
@@ -1,49 +0,0 @@
|
||||
name: Averages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
|
||||
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
|
||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||
ALLOW_ROBOTS: 'true'
|
||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
|
||||
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
|
||||
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
|
||||
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
|
||||
|
||||
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
|
||||
@@ -1,32 +0,0 @@
|
||||
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,19 +22,6 @@ 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:
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
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
|
||||
@@ -1,41 +0,0 @@
|
||||
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
|
||||
@@ -1,41 +0,0 @@
|
||||
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,33 +17,34 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
env:
|
||||
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 }}
|
||||
AUTO_PROXY_ORIGIN: ${{ secrets.AUTO_PROXY_ORIGIN }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
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 }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: actions/configure-pages@v5
|
||||
- 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
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
@@ -57,13 +58,12 @@ jobs:
|
||||
path: dist/website
|
||||
|
||||
deploy-github-pages:
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
name: Deploy to GitHub Pages
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
environment:
|
||||
name: Production
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
if: >
|
||||
(github.repository == 'tresabhi/blitzkit' && github.ref == 'refs/heads/main') ||
|
||||
(github.repository == 'blitzkit/host-redux' && github.ref == 'refs/heads/ue4')
|
||||
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
|
||||
+224
-26
@@ -22,34 +22,232 @@ depots
|
||||
public/sitemap*
|
||||
/firebase.json
|
||||
target
|
||||
# packages/core/src/protos/*.ts
|
||||
/.vercel
|
||||
.astro
|
||||
/.astro
|
||||
.vercel
|
||||
/src
|
||||
/packages/bot/src/prisma
|
||||
dev-dist
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
*.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
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 80
|
||||
}
|
||||
Vendored
+12
-8
@@ -21,7 +21,6 @@
|
||||
"AYUV",
|
||||
"battleresults",
|
||||
"bbox",
|
||||
"beforeinstallprompt",
|
||||
"bepaint",
|
||||
"betakrieg",
|
||||
"BINORMAL",
|
||||
@@ -52,6 +51,7 @@
|
||||
"CUBEMAP",
|
||||
"CUBETEXCOORD",
|
||||
"customperiod",
|
||||
"Datacard",
|
||||
"datasource",
|
||||
"Dava",
|
||||
"dbid",
|
||||
@@ -69,6 +69,7 @@
|
||||
"DIDS",
|
||||
"donotremove",
|
||||
"drei",
|
||||
"dsapp",
|
||||
"dvpd",
|
||||
"dvpl",
|
||||
"DVPLCRC",
|
||||
@@ -77,9 +78,7 @@
|
||||
"echarts",
|
||||
"Everytime",
|
||||
"FASTNAME",
|
||||
"favicons",
|
||||
"flexdirection",
|
||||
"fontsource",
|
||||
"FOURCC",
|
||||
"frameloop",
|
||||
"fsformat",
|
||||
@@ -132,6 +131,7 @@
|
||||
"noout",
|
||||
"normalmap",
|
||||
"oneof",
|
||||
"oodle",
|
||||
"oopsies",
|
||||
"Oopsy",
|
||||
"opentest",
|
||||
@@ -157,7 +157,6 @@
|
||||
"Pyogenics",
|
||||
"ratingleaderboard",
|
||||
"raycaster",
|
||||
"Redirector",
|
||||
"researchable",
|
||||
"Resvg",
|
||||
"rizz",
|
||||
@@ -171,7 +170,6 @@
|
||||
"searchtanks",
|
||||
"SHAREDEXP",
|
||||
"SKLLD",
|
||||
"smoothstep",
|
||||
"Smri",
|
||||
"Specularity",
|
||||
"SSAO",
|
||||
@@ -191,7 +189,6 @@
|
||||
"tankopedia",
|
||||
"tankopedias",
|
||||
"tankstats",
|
||||
"tauri",
|
||||
"texconv",
|
||||
"TEXCOORD",
|
||||
"tresabhi",
|
||||
@@ -199,6 +196,8 @@
|
||||
"Turretless",
|
||||
"TYPELESS",
|
||||
"ubjson",
|
||||
"Uncompiled",
|
||||
"undiscriminated",
|
||||
"Unicum",
|
||||
"UNLOCALIZED",
|
||||
"UNORM",
|
||||
@@ -228,7 +227,12 @@
|
||||
"yolo",
|
||||
"youtuber",
|
||||
"youtubers",
|
||||
"zoomable"
|
||||
"zustand"
|
||||
],
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||
"deno.enable": false,
|
||||
"deno.unstable": false,
|
||||
"cSpell.enableFiletypes": ["glsl", "proto3"],
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||
"liveServer.settings.multiRootWorkspaceName": "blitzkit",
|
||||
"git.ignoreLimitWarning": true
|
||||
}
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
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
|
||||
@@ -1,49 +1,22 @@
|
||||
# BlitzKit
|
||||
## BlitzKit
|
||||
|
||||
Tools for everything and anything, World of Tanks Blitz.
|
||||
Your premier source for all things Blitz.
|
||||
|
||||
This GitHub repository contains the code for BlitzKit and all its services. If you are here, looking to use BlitzKit, go to https://blitzkit.app/.
|
||||
### https://blitzkit.app/
|
||||
|
||||
If you intend to contribute to the code, note that you may need to check out sensitive, closed-source submodules to locally develop certain aspects of BlitzKit. Communicate with `@tresabhi` on Discord to get started or discuss sensitive content, such as environment variables or submodules.
|
||||

|
||||
|
||||
## Packages
|
||||
## Invite the bot
|
||||
|
||||
| Package | Title | Source | Memo |
|
||||
| ------------------------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| [packages/app](packages/app) | BlitzKit | [Play Store](https://play.google.com/store/apps/details?id=com.tresabhi.blitzkit), [App Store](https://apps.apple.com/app/4321), [Microsoft Store](https://apps.microsoft.com/detail/zyxw) | The native app of BlitzKit for all platforms. Unavailable from stores at this time. |
|
||||
| [packages/bot](packages/bot) | BlitzKit Discord Bot | [Discord Application Directory](https://discord.com/discovery/applications/1097673957865443370) | The one-in-all Discord bot for statistics. |
|
||||
| [packages/core](packages/core) | | | Mutual internal code shared between all other packages. |
|
||||
| [packages/i18n](packages/i18n) | | [Crowdin](https://crowdin.com/project/blitzkrieg) | Translations served as JSON shared across all other packages, including some utility code. |
|
||||
| [packages/scripts](packages/scripts) | | | CLI utilities for the development and deployment of BlitzKit services. |
|
||||
| [packages/varuna](packages/varuna) | Varuna | [NPM](https://www.npmjs.com/package/varuna) | React and Astro-oriented state management library, awaiting separation from BlitzKit. Unavailable from NPM at this time. |
|
||||
| [packages/website](packages/website) | BlitzKit | https://blitzkit.app | The BlitzKit website provides a wide range of tools, APIs, and interconnects all other services. |
|
||||
Go to [application directory for BlitzKit](https://discord.com/application-directory/1097673957865443370) and click "Add to Server." Voila! You're ready to go.
|
||||
|
||||
## Submodules
|
||||

|
||||
|
||||
| Submodule | Title | Source | Memo |
|
||||
| ------------------------------------------------------------ | ----------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [submodules/blitzkit-closed](submodules/blitzkit-closed) | ClosedKit | [blitzkit/closed](https://github.com/blitzkit/closed) | Closed source, sensitive code hidden from the public to prevent bad actors from abusing the game. Discuss with `@tresabhi` on Discord for **special permission** to access. |
|
||||
| [submodules/blitzkit-internal](submodules/blitzkit-internal) | InternalKit | [blitzkit/internal](https://github.com/blitzkit/internal) | Closed source, sensitive internal documentation hidden from the public to prevent bad actors from abusing the game. Access to this submodule is **not negotiable**. |
|
||||
| [submodules/CUE4Parse](submodules/CUE4Parse) | CUE4Parse | [FabianFG/CUE4Parse](https://github.com/FabianFG/CUE4Parse) | An Unreal Engine archives and packages parsing library in C#. |
|
||||
## Naming Guide
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Name | Source | BlitzKit Default | Memo |
|
||||
| ------------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `PUBLIC_WARGAMING_APPLICATION_ID` | [Wargaming Developer's Room](https://developers.wargaming.net/) | | Application ID needed to communicate with Wargaming APIs. |
|
||||
| `PUBLIC_PROMOTE_OPENTEST` | | `false` | Optional boolean dictating whether the website advertises [the opentest subdomain of BlitzKit](https://opentest.blitzkit.app/). |
|
||||
| `ALLOW_ROBOTS` | | `true` | Toggles between `Allow: /` and `Disallow: /` in the website's `/robots.txt`. This is usually `false` for all BlitzKit's subdomains. |
|
||||
| `WOTB_GLOSSARY`, `WOTB_GLOSSARY_DOMAIN`, `WOTB_GLOSSARY_PATH` | | | |
|
||||
| `PUBLIC_GOOGLE_TAG_MANAGER_ID` | [Google Tag Manager](https://tagmanager.google.com/) | `G-GL2JVHCGPQ` | Google Tag Manager's unique identifier for analytical purposes. |
|
||||
| `PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID` | [Google Analytics](https://analytics.google.com/) | `430711767` | Google Analytics' unique property ID for [BlitzKit's apex domain](https://blitzkit.app/) and subdomains. |
|
||||
| `GOOGLE_APPLICATION_CREDENTIALS` | [Google Cloud](https://cloud.google.com/) | | Google Cloud's API key, primarily used for ranking popular tanks and finding relevant tank review videos in Tankopedia. |
|
||||
| `PUBLIC_DISCORD_SERVER_ID` | [BlitzKit Discord Server](https://discord.gg/nDt7AjGJQH) | `734786591205359697` | BlitzKit Discord Server's id. |
|
||||
| `PUBLIC_ASSET_BASE` | [blitzkit/assets](https://github.com/blitzkit/assets) | `https://blitzkit.github.io/assets` | The root domain and path that serves the external assets like tank models and icons. |
|
||||
| `PUBLIC_BRANCH` | | `main` | The branch that the website is operating on. This is completely nominal. |
|
||||
| `PUBLIC_GUIDE_WEBSITE` | [GuidesBlitz](https://guidesblitz.com/) | `https://guidesblitz.com` | The website that serves the encyclopedia-like content in BlitzKit. |
|
||||
| `SERVICING` | | `false` | Wether the website is down for servicing. |
|
||||
| `DISCORD_CLIENT_ID` | [Discord Developer Portal](https://discord.com/developers/applications) | `1097673957865443370` | The BlitzKit Discord Bot's user ID. |
|
||||
| `GH_TOKEN` | [GitHub Fine-Grained Tokens](https://github.com/settings/personal-access-tokens) | | The GitHub API key used for checking out private submodules and pushing content to the asset repository. |
|
||||
| `DISCORD_TOKEN` | [Discord Developer Portal](https://discord.com/developers/applications) | | The Discord API Key for the BlitzKit Discord Bot. |
|
||||
| `DATABASE_URL` | [CockroachDB](https://www.cockroachlabs.com/) | | The CockroachDB-SQL database URL used to link Wargaming and Discord users. |
|
||||
| `WOTB_DLC_CDN` | | | |
|
||||
| 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"` |
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
# 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
|
||||
@@ -1,18 +0,0 @@
|
||||
# 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!
|
||||
@@ -1,11 +0,0 @@
|
||||
# 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
|
||||
@@ -1,18 +0,0 @@
|
||||
# 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
|
||||
@@ -1,13 +0,0 @@
|
||||
# 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,16 +0,0 @@
|
||||
# BlitzKit 1.28.0
|
||||
|
||||
BlitzKit, as an app and the new Mixer tool!
|
||||
|
||||
## Features
|
||||
|
||||
- Created the BlitzKit app that can be downloaded on most devices
|
||||
- Re-did the BlitzKit logo to be a bit warmer
|
||||
- Added the Mixer tool which lets you combine tank models into Frankensteins
|
||||
- Updated all dependencies to the latest version for a decent performance boost
|
||||
|
||||
## Fixes
|
||||
|
||||
- BlitzKit no longer forgets persistent data if the app crashes without properly closing
|
||||
- The tools list in the navbar no longer overflows into the neighboring elements on certain devices
|
||||
- Added keying to a few places, speeding up re-rendering
|
||||
@@ -1,5 +0,0 @@
|
||||
# BlitzKit 1.28.1
|
||||
|
||||
## Fixes
|
||||
|
||||
- The lights no longer crash the Mixer tool
|
||||
@@ -1,5 +0,0 @@
|
||||
# BlitzKit 1.28.2
|
||||
|
||||
## Fixes
|
||||
|
||||
- BlitzKit no longer tries to redirect translated pages to the same page, over and over again
|
||||
+11
-25
@@ -1,29 +1,27 @@
|
||||
{
|
||||
"name": "blitzkit",
|
||||
"private": true,
|
||||
"version": "1.28.2",
|
||||
"version": "1.27.15",
|
||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||
"type": "module",
|
||||
"types": "./types.d.ts",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
"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:website": "cd packages/website-ue && 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",
|
||||
"build:app": "cd packages/app && bun run build",
|
||||
"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 && bun run dev",
|
||||
"dev:app": "cd packages/app && bun run dev",
|
||||
"dev": "bun run dev:website",
|
||||
"dev:api": "bun --filter=@blitzkit/api run dev",
|
||||
"setup:depotdownloader": "bun --filter=@blitzkit/scripts run setup:depotdownloader",
|
||||
@@ -33,33 +31,21 @@
|
||||
"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",
|
||||
"update": "yarn upgace --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"
|
||||
"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"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/xmpp__client": "^0.13.3",
|
||||
"@xmpp/client": "^0.13.6",
|
||||
"astro-favicons": "^3.1.5",
|
||||
"cheerio": "^1.1.2",
|
||||
"googleapis": "^160.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"protobufjs": "^7.5.4",
|
||||
"quicklime": "^1.2.0",
|
||||
"sharp": "^0.34.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.2.22",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^24.5.2",
|
||||
"prettier": "^3.6.2",
|
||||
"prisma": "^6.16.2",
|
||||
"typescript": "^5.9.2"
|
||||
"@types/bun": "^1.2.15",
|
||||
"prettier": "^3.5.3"
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "@blitzkit/app",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"vite": "^6.0.3",
|
||||
"typescript": "~5.6.2"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Generated by Tauri
|
||||
# will have schema files for capabilities auto-completion
|
||||
/gen/schemas
|
||||
Generated
-5286
File diff suppressed because it is too large.
Load diff
@@ -1,25 +0,0 @@
|
||||
[package]
|
||||
name = "blitzkit"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
# The `_lib` suffix may seem redundant but it is necessary
|
||||
# to make the lib name unique and wouldn't conflict with the bin name.
|
||||
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
||||
name = "blitzkit_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri-plugin-opener = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": ["main"],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default"
|
||||
]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
#[tauri::command]
|
||||
fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![greet])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use std::process::{Command, Stdio};
|
||||
use tauri::Manager;
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
let app_handle = app.handle();
|
||||
|
||||
let entry = app_handle
|
||||
.path_resolver()
|
||||
.resolve_resource("../../dist/website/server/entry.mjs")
|
||||
.expect("failed to resolve server.ts");
|
||||
|
||||
Command::new("bun")
|
||||
.arg("run")
|
||||
.arg(entry)
|
||||
.stderr(Stdio::null())
|
||||
.spawn()
|
||||
.expect("failed to start Bun server");
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running Tauri app");
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "blitzkit",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.tresabhi.blitzkit",
|
||||
"build": {
|
||||
"beforeDevCommand": "cd ../website && bun run dev",
|
||||
"_beforeBuildCommand": "cd ../website && bun run build",
|
||||
"frontendDist": "../../../dist/website/client",
|
||||
"devUrl": "http://localhost:4321"
|
||||
},
|
||||
"app": {
|
||||
"withGlobalTauri": true,
|
||||
"windows": [
|
||||
{
|
||||
"url": "http://localhost:4321",
|
||||
"title": "BlitzKit",
|
||||
"fullscreen": false,
|
||||
"resizable": true,
|
||||
"maximized": true
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/[email protected]",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"extends": ["../../tsconfig.json"],
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
}
|
||||
@@ -3,24 +3,8 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"types": "src/types.d.ts",
|
||||
"dependencies": {
|
||||
"@blitzkit/core": "workspace:*",
|
||||
"@blitzkit/i18n": "workspace:*",
|
||||
"satori": "^0.18.3",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"discord.js": "^14.22.1",
|
||||
"react": "^19.1.1",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"fuzzysort": "^3.1.0",
|
||||
"bepaint": "^0.1.0-beta.3",
|
||||
"dotenv": "^17.2.2",
|
||||
"markdown-escape": "^2.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "bun --watch src/main.ts",
|
||||
"start": "bun src/main.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-escape": "^1.1.3"
|
||||
}
|
||||
}
|
||||
@@ -6,23 +6,23 @@ import {
|
||||
getTankStats,
|
||||
getWN8Percentile,
|
||||
TankDefinition,
|
||||
} from "@blitzkit/core";
|
||||
import { literals } from "@blitzkit/i18n";
|
||||
import { Locale } from "discord.js";
|
||||
import { chunk } from "lodash-es";
|
||||
import * as Breakdown from "../../components/Breakdown";
|
||||
import { CommandWrapper } from "../../components/CommandWrapper";
|
||||
import { NoData } from "../../components/NoData";
|
||||
import { TitleBar } from "../../components/TitleBar";
|
||||
import { filtersToDescription } from "../../core/blitzkit/filtersToDescription";
|
||||
import { tankDefinitions } from "../../core/blitzkit/nonBlockingPromises";
|
||||
import { UserError } from "../../core/blitzkit/userError";
|
||||
import { filterStats, StatFilters } from "../../core/blitzstars/filterStats";
|
||||
import { getStatsInPeriod } from "../../core/blitzstars/getStatsInPeriod";
|
||||
import { blitzStarsTankAverages } from "../../core/blitzstars/tankAverages";
|
||||
import { ResolvedPeriod } from "../../core/discord/resolvePeriodFromCommand";
|
||||
import { ResolvedPlayer } from "../../core/discord/resolvePlayerFromCommand";
|
||||
import { translator } from "../../core/localization/translator";
|
||||
} from '@blitzkit/core';
|
||||
import { literals } from '@blitzkit/i18n';
|
||||
import { Locale } from 'discord.js';
|
||||
import { chunk } from 'lodash-es';
|
||||
import * as Breakdown from '../../components/Breakdown';
|
||||
import { CommandWrapper } from '../../components/CommandWrapper';
|
||||
import { NoData } from '../../components/NoData';
|
||||
import { TitleBar } from '../../components/TitleBar';
|
||||
import { filtersToDescription } from '../../core/blitzkit/filtersToDescription';
|
||||
import { tankDefinitions } from '../../core/blitzkit/nonBlockingPromises';
|
||||
import { UserError } from '../../core/blitzkit/userError';
|
||||
import { filterStats, StatFilters } from '../../core/blitzstars/filterStats';
|
||||
import { getStatsInPeriod } from '../../core/blitzstars/getStatsInPeriod';
|
||||
import { blitzStarsTankAverages } from '../../core/blitzstars/tankAverages';
|
||||
import { ResolvedPeriod } from '../../core/discord/resolvePeriodFromCommand';
|
||||
import { ResolvedPlayer } from '../../core/discord/resolvePlayerFromCommand';
|
||||
import { translator } from '../../core/localization/translator';
|
||||
|
||||
const ROWS_PER_PAGE = 8;
|
||||
const MAX_PAGES = 9;
|
||||
@@ -31,7 +31,7 @@ export async function renderBreakdown(
|
||||
{ region, id }: ResolvedPlayer,
|
||||
{ start, end, name }: ResolvedPeriod,
|
||||
filters: StatFilters,
|
||||
locale: Locale
|
||||
locale: Locale,
|
||||
) {
|
||||
const { strings, unwrap } = translator(locale);
|
||||
const awaitedTankDefinitions = await tankDefinitions;
|
||||
@@ -39,7 +39,7 @@ export async function renderBreakdown(
|
||||
const statsInPeriod = await getStatsInPeriod(region, id, start, end, locale);
|
||||
const { filteredOrder } = await filterStats(statsInPeriod, filters);
|
||||
const accountInfo = await getAccountInfo(region, id);
|
||||
const clanData = await getClanAccountInfo(region, id, ["clan"]);
|
||||
const clanData = await getClanAccountInfo(region, id, ['clan']);
|
||||
const tankStats = await getTankStats(region, id);
|
||||
const filterDescriptions = await filtersToDescription(filters, locale);
|
||||
const orderedCurrentStats: BlitzStats[] = [];
|
||||
@@ -74,63 +74,63 @@ export async function renderBreakdown(
|
||||
orderedCareerWN8Full.push(
|
||||
tankAveragesEntry
|
||||
? calculateWN8(tankAveragesEntry.all, tank.all)
|
||||
: undefined
|
||||
: undefined,
|
||||
);
|
||||
});
|
||||
|
||||
const currentBattles = orderedCurrentStats.reduce(
|
||||
(accumulator, current) => accumulator + current.battles,
|
||||
0
|
||||
0,
|
||||
);
|
||||
const careerBattles = tankStats.reduce(
|
||||
(accumulator, current) => accumulator + current.all.battles,
|
||||
0
|
||||
0,
|
||||
);
|
||||
const currentWinrate =
|
||||
orderedCurrentStats.reduce(
|
||||
(accumulator, current) => accumulator + current.wins,
|
||||
0
|
||||
0,
|
||||
) / currentBattles;
|
||||
const careerWinrate =
|
||||
tankStats.reduce(
|
||||
(accumulator, current) => accumulator + current.all.wins,
|
||||
0
|
||||
0,
|
||||
) / careerBattles;
|
||||
const currentWN8 =
|
||||
orderedCurrentWN8.reduce<number>(
|
||||
(accumulator, current, index) =>
|
||||
accumulator + (current ?? 0) * orderedCurrentStats[index].battles,
|
||||
0
|
||||
0,
|
||||
) /
|
||||
orderedCurrentStats.reduce(
|
||||
(accumulator, current, index) =>
|
||||
accumulator +
|
||||
(orderedCurrentWN8[index] === undefined ? 0 : current.battles),
|
||||
0
|
||||
0,
|
||||
);
|
||||
const careerWN8 =
|
||||
orderedCareerWN8Full.reduce<number>(
|
||||
(accumulator, current) => accumulator + (current ?? 0),
|
||||
0
|
||||
0,
|
||||
) /
|
||||
tankStats.reduce(
|
||||
(accumulator, current, index) =>
|
||||
accumulator +
|
||||
(orderedCareerWN8Full[index] === undefined ? 0 : current.all.battles),
|
||||
0
|
||||
0,
|
||||
);
|
||||
const currentDamage =
|
||||
orderedCurrentStats.reduce(
|
||||
(accumulator, current) => accumulator + current.damage_dealt,
|
||||
0
|
||||
0,
|
||||
) / currentBattles;
|
||||
const careerDamage =
|
||||
tankStats.reduce(
|
||||
(accumulator, current) => accumulator + current.all.damage_dealt,
|
||||
0
|
||||
0,
|
||||
) / careerBattles;
|
||||
|
||||
const children: React.JSX.Element[] = [];
|
||||
const children: JSX.Element[] = [];
|
||||
|
||||
if (filteredOrder.length > 0) {
|
||||
children.push(
|
||||
@@ -172,7 +172,7 @@ export async function renderBreakdown(
|
||||
delta: currentDamage - careerDamage,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
|
||||
filteredOrder.forEach((id, index) => {
|
||||
@@ -206,7 +206,7 @@ export async function renderBreakdown(
|
||||
{
|
||||
title: strings.bot.commands.breakdown.body.winrate,
|
||||
current: `${(100 * (current.wins / current.battles)).toFixed(
|
||||
2
|
||||
2,
|
||||
)}%`,
|
||||
career: `${(100 * (career.wins / career.battles)).toFixed(2)}%`,
|
||||
delta:
|
||||
@@ -230,24 +230,24 @@ export async function renderBreakdown(
|
||||
{
|
||||
title: strings.bot.commands.breakdown.body.damage,
|
||||
current: Math.round(
|
||||
current.damage_dealt / current.battles
|
||||
current.damage_dealt / current.battles,
|
||||
).toLocaleString(),
|
||||
career: Math.round(
|
||||
career.damage_dealt / career.battles
|
||||
career.damage_dealt / career.battles,
|
||||
).toLocaleString(),
|
||||
delta:
|
||||
current.damage_dealt / current.battles -
|
||||
career.damage_dealt / career.battles,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
/>,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const pages = chunk(
|
||||
children,
|
||||
Math.max(Math.ceil(children.length / MAX_PAGES), ROWS_PER_PAGE)
|
||||
Math.max(Math.ceil(children.length / MAX_PAGES), ROWS_PER_PAGE),
|
||||
);
|
||||
|
||||
if (filteredOrder.length > 0) {
|
||||
|
||||
@@ -197,10 +197,15 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
|
||||
turret.guns.forEach((gun) => {
|
||||
if (
|
||||
gun.research_cost !== undefined &&
|
||||
typeof gun.research_cost.research_cost_type!.value === 'number'
|
||||
gun.gun_type!.value.base.research_cost !== undefined &&
|
||||
typeof gun.gun_type!.value.base.research_cost
|
||||
.research_cost_type!.value === 'number'
|
||||
) {
|
||||
gunXps.set(gun.id, gun.research_cost.research_cost_type!.value);
|
||||
gunXps.set(
|
||||
gun.gun_type!.value.base.id,
|
||||
gun.gun_type!.value.base.research_cost.research_cost_type!
|
||||
.value,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { jsxToSvg } from "./jsxToSvg";
|
||||
import { svgToPng } from "./svgToPng";
|
||||
import { jsxToSvg } from './jsxToSvg';
|
||||
import { svgToPng } from './svgToPng';
|
||||
|
||||
export async function jsxToPng(jsx: React.JSX.Element) {
|
||||
export async function jsxToPng(jsx: JSX.Element) {
|
||||
const svg = await jsxToSvg(jsx);
|
||||
const png = svgToPng(svg);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { jsxToSvg } from "./jsxToSvg";
|
||||
import { RenderConfiguration } from "./renderConfiguration";
|
||||
import { svgToPngThreaded } from "./svgToPngThreaded";
|
||||
import { jsxToSvg } from './jsxToSvg';
|
||||
import { RenderConfiguration } from './renderConfiguration';
|
||||
import { svgToPngThreaded } from './svgToPngThreaded';
|
||||
|
||||
export async function jsxToPngThreaded(
|
||||
jsx: React.JSX.Element,
|
||||
renderConfiguration: RenderConfiguration
|
||||
jsx: JSX.Element,
|
||||
renderConfiguration: RenderConfiguration,
|
||||
) {
|
||||
const svg = await jsxToSvg(jsx, renderConfiguration);
|
||||
const png = await svgToPngThreaded(svg);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { readFile } from "fs/promises";
|
||||
import satori from "satori";
|
||||
import { RenderConfiguration } from "../renderConfiguration";
|
||||
import robotoBlack from "./Roboto-Black.ttf";
|
||||
import robotoBold from "./Roboto-Bold.ttf";
|
||||
import roboto from "./Roboto.ttf";
|
||||
import { readFile } from 'fs/promises';
|
||||
import satori from 'satori';
|
||||
import { RenderConfiguration } from '../renderConfiguration';
|
||||
import robotoBlack from './Roboto-Black.ttf';
|
||||
import robotoBold from './Roboto-Bold.ttf';
|
||||
import roboto from './Roboto.ttf';
|
||||
|
||||
const fontsPromise = [
|
||||
{ file: roboto, weight: 400 as const },
|
||||
@@ -11,12 +11,12 @@ const fontsPromise = [
|
||||
{ file: robotoBlack, weight: 900 as const },
|
||||
].map(async ({ file, weight }) => {
|
||||
const data = await readFile(`${__dirname}/${file}`);
|
||||
return { name: "Roboto", weight, data };
|
||||
return { name: 'Roboto', weight, data };
|
||||
});
|
||||
|
||||
export async function jsxToSvg(
|
||||
jsx: React.JSX.Element,
|
||||
renderConfiguration = new RenderConfiguration()
|
||||
jsx: JSX.Element,
|
||||
renderConfiguration = new RenderConfiguration(),
|
||||
) {
|
||||
const fonts = await Promise.all(fontsPromise);
|
||||
return await satori(jsx, { width: renderConfiguration.width, fonts });
|
||||
|
||||
@@ -6,14 +6,14 @@ import {
|
||||
InteractionEditReplyOptions,
|
||||
InteractionReplyOptions,
|
||||
MessageEditOptions,
|
||||
} from "discord.js";
|
||||
import { InteractionReturnable } from "../../events/interactionCreate";
|
||||
import { Writeable } from "../../types/writable";
|
||||
import { jsxToPngThreaded } from "../blitzkit/jsxToPngThreaded";
|
||||
import { RenderConfiguration } from "../blitzkit/renderConfiguration";
|
||||
} from 'discord.js';
|
||||
import { InteractionReturnable } from '../../events/interactionCreate';
|
||||
import { Writeable } from '../../types/writable';
|
||||
import { jsxToPngThreaded } from '../blitzkit/jsxToPngThreaded';
|
||||
import { RenderConfiguration } from '../blitzkit/renderConfiguration';
|
||||
|
||||
export async function normalizeInteractionReturnable(
|
||||
returnable: InteractionReturnable
|
||||
returnable: InteractionReturnable,
|
||||
) {
|
||||
const images: [number, Buffer][] = [];
|
||||
const reply: InteractionEditReplyOptions &
|
||||
@@ -27,7 +27,7 @@ export async function normalizeInteractionReturnable(
|
||||
|
||||
await Promise.all(
|
||||
normalizedReturnable.map(async (item, index) => {
|
||||
if (typeof item === "string") {
|
||||
if (typeof item === 'string') {
|
||||
reply.content = item;
|
||||
} else if (item instanceof EmbedBuilder) {
|
||||
if (!reply.embeds) reply.embeds = [];
|
||||
@@ -36,7 +36,7 @@ export async function normalizeInteractionReturnable(
|
||||
if (!reply.components)
|
||||
reply.components = [new ActionRowBuilder<ButtonBuilder>()];
|
||||
(reply.components[0] as ActionRowBuilder<ButtonBuilder>).addComponents(
|
||||
item
|
||||
item,
|
||||
);
|
||||
} else if (item instanceof AttachmentBuilder) {
|
||||
if (!reply.files) reply.files = [];
|
||||
@@ -49,14 +49,14 @@ export async function normalizeInteractionReturnable(
|
||||
const image = await jsxToPngThreaded(item, renderConfiguration);
|
||||
images.push([index, image]);
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
if (images.length > 0) {
|
||||
images.sort(([a], [b]) => b - a);
|
||||
if (!reply.files) reply.files = [];
|
||||
images.forEach(([, image]) =>
|
||||
(reply.files as Writeable<typeof reply.files>)?.push(image)
|
||||
(reply.files as Writeable<typeof reply.files>)?.push(image),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import locales from "@blitzkit/i18n/locales.json";
|
||||
import { Locale } from "discord.js";
|
||||
import { DEFAULT_LOCALE, SupportedLocale } from '@blitzkit/i18n';
|
||||
import { Locale } from 'discord.js';
|
||||
|
||||
const SUPPORTED_LOCALES_DISCORD_MAP: Record<string, Locale> = {
|
||||
const SUPPORTED_LOCALES_DISCORD_MAP: Record<SupportedLocale, Locale> = {
|
||||
en: Locale.EnglishUS,
|
||||
es: Locale.SpanishES,
|
||||
pt: Locale.PortugueseBR,
|
||||
@@ -14,12 +14,12 @@ const SUPPORTED_LOCALES_DISCORD_MAP: Record<string, Locale> = {
|
||||
};
|
||||
|
||||
export const SUPPORTED_LOCALES_DISCORD_MAP_INVERSE = Object.fromEntries(
|
||||
Object.entries(SUPPORTED_LOCALES_DISCORD_MAP).map(([k, v]) => [v, k])
|
||||
) as Record<Locale, string>;
|
||||
Object.entries(SUPPORTED_LOCALES_DISCORD_MAP).map(([k, v]) => [v, k]),
|
||||
) as Record<Locale, SupportedLocale>;
|
||||
|
||||
export const SUPPORTED_LOCALES_DISCORD = Object.values(
|
||||
SUPPORTED_LOCALES_DISCORD_MAP
|
||||
SUPPORTED_LOCALES_DISCORD_MAP,
|
||||
);
|
||||
|
||||
export const DEFAULT_LOCALE_DISCORD =
|
||||
SUPPORTED_LOCALES_DISCORD_MAP[locales.default];
|
||||
SUPPORTED_LOCALES_DISCORD_MAP[DEFAULT_LOCALE];
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ReadStream } from "@blitzkit/core";
|
||||
import AdmZip from "adm-zip";
|
||||
import { readFile } from "fs/promises";
|
||||
import { Parser } from "pickleparser";
|
||||
import protobuf from "protobufjs";
|
||||
import { ReadStream } from '@blitzkit/core';
|
||||
import AdmZip from 'adm-zip';
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
import { Parser } from 'pickleparser';
|
||||
import protobuf from 'protobufjs';
|
||||
|
||||
interface WotbReplayMetaJson {
|
||||
version: string;
|
||||
@@ -24,24 +24,26 @@ 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(
|
||||
@@ -51,7 +53,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,4 +1,4 @@
|
||||
import { assertSecret } from "@blitzkit/core";
|
||||
import { assertSecret } from '@blitzkit/core';
|
||||
import {
|
||||
AttachmentBuilder,
|
||||
AutocompleteInteraction,
|
||||
@@ -13,30 +13,30 @@ import {
|
||||
SlashCommandBuilder,
|
||||
SlashCommandOptionsOnlyBuilder,
|
||||
SlashCommandSubcommandsOnlyBuilder,
|
||||
} from "discord.js";
|
||||
import { aboutCommand } from "../../commands/about";
|
||||
import { breakdownCommand } from "../../commands/breakdown";
|
||||
import { debugCommand } from "../../commands/debug";
|
||||
import { evolutionCommand } from "../../commands/evolution";
|
||||
import { fullStatsCommand } from "../../commands/fullStats";
|
||||
import { inactiveCommand } from "../../commands/inactive";
|
||||
import { verifyCommand } from "../../commands/link";
|
||||
import { ownedTanksCommand } from "../../commands/ownedTanks";
|
||||
import { permissionsCommand } from "../../commands/permissions";
|
||||
import { pingCommand } from "../../commands/ping";
|
||||
import { playerInfoCommand } from "../../commands/playerInfo";
|
||||
import { ratingCommand } from "../../commands/rating";
|
||||
import { ratingLeaderboardCommand } from "../../commands/ratingLeaderboard";
|
||||
import { replayCommand } from "../../commands/replay";
|
||||
import { researchCommand } from "../../commands/research";
|
||||
import { searchClansCommand } from "../../commands/searchClans";
|
||||
import { searchPlayersCommand } from "../../commands/searchPlayers";
|
||||
import { statsCommand } from "../../commands/stats";
|
||||
import { todayCommand } from "../../commands/today";
|
||||
import { RenderConfiguration } from "../../core/blitzkit/renderConfiguration";
|
||||
import { handleAutocomplete } from "./handlers/autocomplete";
|
||||
import { handleButton } from "./handlers/button";
|
||||
import { handleChatInputCommand } from "./handlers/chatInputCommand";
|
||||
} from 'discord.js';
|
||||
import { aboutCommand } from '../../commands/about';
|
||||
import { breakdownCommand } from '../../commands/breakdown';
|
||||
import { debugCommand } from '../../commands/debug';
|
||||
import { evolutionCommand } from '../../commands/evolution';
|
||||
import { fullStatsCommand } from '../../commands/fullStats';
|
||||
import { inactiveCommand } from '../../commands/inactive';
|
||||
import { verifyCommand } from '../../commands/link';
|
||||
import { ownedTanksCommand } from '../../commands/ownedTanks';
|
||||
import { permissionsCommand } from '../../commands/permissions';
|
||||
import { pingCommand } from '../../commands/ping';
|
||||
import { playerInfoCommand } from '../../commands/playerInfo';
|
||||
import { ratingCommand } from '../../commands/rating';
|
||||
import { ratingLeaderboardCommand } from '../../commands/ratingLeaderboard';
|
||||
import { replayCommand } from '../../commands/replay';
|
||||
import { researchCommand } from '../../commands/research';
|
||||
import { searchClansCommand } from '../../commands/searchClans';
|
||||
import { searchPlayersCommand } from '../../commands/searchPlayers';
|
||||
import { statsCommand } from '../../commands/stats';
|
||||
import { todayCommand } from '../../commands/today';
|
||||
import { RenderConfiguration } from '../../core/blitzkit/renderConfiguration';
|
||||
import { handleAutocomplete } from './handlers/autocomplete';
|
||||
import { handleButton } from './handlers/button';
|
||||
import { handleChatInputCommand } from './handlers/chatInputCommand';
|
||||
|
||||
export type InteractionRawReturnable =
|
||||
| string
|
||||
@@ -44,7 +44,7 @@ export type InteractionRawReturnable =
|
||||
| ButtonBuilder
|
||||
| AttachmentBuilder
|
||||
| RenderConfiguration
|
||||
| React.JSX.Element
|
||||
| JSX.Element
|
||||
| null;
|
||||
export type InteractionIterableReturnable =
|
||||
| InteractionRawReturnable
|
||||
@@ -58,7 +58,7 @@ interface CommandRegistryBase {
|
||||
| SlashCommandBuilder
|
||||
| SlashCommandSubcommandsOnlyBuilder
|
||||
| SlashCommandOptionsOnlyBuilder
|
||||
| Omit<SlashCommandBuilder, "addSubcommand" | "addSubcommandGroup">;
|
||||
| Omit<SlashCommandBuilder, 'addSubcommand' | 'addSubcommandGroup'>;
|
||||
|
||||
autocomplete?: (interaction: AutocompleteInteraction<CacheType>) => void;
|
||||
button?: (interaction: ButtonInteraction<CacheType>) => InteractionReturnable;
|
||||
@@ -72,7 +72,7 @@ interface CommandRegistryImplicit extends CommandRegistryBase {
|
||||
interface CommandRegistryExplicit extends CommandRegistryBase {
|
||||
handlesInteraction?: false;
|
||||
handler: (
|
||||
interaction: ChatInputCommandInteraction<CacheType>
|
||||
interaction: ChatInputCommandInteraction<CacheType>,
|
||||
) => InteractionReturnable;
|
||||
}
|
||||
|
||||
@@ -104,16 +104,16 @@ export const COMMANDS_RAW: Promise<CommandRegistry>[] = [
|
||||
export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => {
|
||||
return rawCommands.reduce<Record<string, CommandRegistry>>(
|
||||
(commands, registry, index) => {
|
||||
if (registry.status === "rejected") {
|
||||
if (registry.status === 'rejected') {
|
||||
console.warn(
|
||||
`Command ${index} failed to load; skipping...`,
|
||||
registry.reason
|
||||
registry.reason,
|
||||
);
|
||||
return commands;
|
||||
}
|
||||
return { ...commands, [registry.value.command.name]: registry.value };
|
||||
},
|
||||
{}
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -121,12 +121,12 @@ const rest = new REST().setToken(assertSecret(import.meta.env.DISCORD_TOKEN));
|
||||
|
||||
commands.then((awaitedCommands) => {
|
||||
const body = Object.values(awaitedCommands).map((registry) =>
|
||||
registry.command.toJSON()
|
||||
registry.command.toJSON(),
|
||||
);
|
||||
|
||||
rest.put(
|
||||
Routes.applicationCommands(assertSecret(import.meta.env.DISCORD_CLIENT_ID)),
|
||||
{ body }
|
||||
{ body },
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src/**/*", "../core/src/blitzkit/supportedLocales.ts", "../i18n/src/unwrapper.ts"]
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"../core/src/blitzkit/supportedLocales.ts",
|
||||
"../i18n/src/unwrapper.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../../"
|
||||
}
|
||||
}
|
||||
@@ -4,20 +4,9 @@
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"dependencies": {
|
||||
"@octokit/plugin-throttling": "^11.0.1",
|
||||
"@octokit/rest": "^22.0.0",
|
||||
"@prisma/client": "^6.16.2",
|
||||
"adm-zip": "^0.5.16",
|
||||
"crc": "^4.3.2",
|
||||
"dotenv": "^17.2.2",
|
||||
"lz4js": "^0.2.0",
|
||||
"pickleparser": "^0.2.1",
|
||||
"protobufjs": "^7.5.4",
|
||||
"ts-proto": "^2.7.7",
|
||||
"yaml": "^2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/adm-zip": "^0.5.7",
|
||||
"@types/lz4js": "^0.2.1"
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"js-yaml": "^4.1.0",
|
||||
"ts-proto": "^2.2.2",
|
||||
"@blitzkit/closed": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
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,6 +18,7 @@ export * from './getTankStats';
|
||||
export * from './idToRegion';
|
||||
export * from './isOnGoingRatingSeason';
|
||||
export * from './isValidBlitzId';
|
||||
export * from './metadata';
|
||||
export * from './nations';
|
||||
export * from './normalizeExtra';
|
||||
export * from './normalizeIds';
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,131 @@
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { BlitzKitAvatars } from '@protos/blitzkit';
|
||||
import { MetadataAccessor } from '../../blitz/metadata';
|
||||
|
||||
export abstract class BlitzkitAPI {
|
||||
abstract metadata: MetadataAccessor;
|
||||
|
||||
abstract avatars(): Promise<BlitzKitAvatars>;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './abstract';
|
||||
export * from './client';
|
||||
export * from './server';
|
||||
@@ -0,0 +1,37 @@
|
||||
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,6 +41,7 @@ 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,
|
||||
GunDefinitionAutoReloader,
|
||||
GunDefinitionAutoReloaderProperties,
|
||||
ShellDefinition,
|
||||
} from '../protos';
|
||||
|
||||
@@ -15,46 +15,34 @@ export function resolveDpm(
|
||||
let dps: number;
|
||||
|
||||
if (gun.gun_type!.$case === 'regular') {
|
||||
dps = alpha / (reloadCoefficient * gun.gun_type!.value.reload);
|
||||
dps = alpha / (reloadCoefficient * gun.gun_type!.value.extension.reload);
|
||||
} else if (gun.gun_type!.$case === 'auto_loader') {
|
||||
const damage = alpha * gun.gun_type!.value.shell_count;
|
||||
let time = gun.gun_type!.value.clip_reload * reloadCoefficient;
|
||||
|
||||
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;
|
||||
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.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);
|
||||
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);
|
||||
|
||||
if (current === null || reload < current.reload) {
|
||||
return { index, reload };
|
||||
}
|
||||
return current;
|
||||
}, null)!;
|
||||
if (current === null || reload < current.reload) {
|
||||
return { index, reload };
|
||||
}
|
||||
return current;
|
||||
}, null)!;
|
||||
|
||||
dps = alpha / mostOptimalShell?.reload;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import locales from "@blitzkit/i18n/locales.json";
|
||||
import { deburr } from "lodash-es";
|
||||
import { I18nString } from "../protos";
|
||||
import { fetchCamouflageDefinitions } from "./camouflageDefinitions";
|
||||
import { fetchTankDefinitions } from "./tankDefinitions";
|
||||
import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
|
||||
import { deburr } from 'lodash-es';
|
||||
import { I18nString } from '../protos';
|
||||
import { fetchCamouflageDefinitions } from './camouflageDefinitions';
|
||||
import { fetchTankDefinitions } from './tankDefinitions';
|
||||
|
||||
export async function fetchTankNames() {
|
||||
const [tankDefinitions, camouflageDefinitions] = await Promise.all([
|
||||
@@ -28,16 +28,16 @@ export async function fetchTankNames() {
|
||||
?.map((id) =>
|
||||
locales.supported.map(
|
||||
({ locale }) =>
|
||||
camouflageDefinitions.camouflages[id]?.name.locales[locale]
|
||||
)
|
||||
camouflageDefinitions.camouflages[id]?.name.locales[locale],
|
||||
),
|
||||
)
|
||||
.flat()
|
||||
.filter(Boolean)
|
||||
.map(deburr)
|
||||
.join(" "),
|
||||
.join(' '),
|
||||
treeType: tank.type,
|
||||
};
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,5 +48,5 @@ export const SEARCH_KEYS = [
|
||||
`searchableNameDeburr.locales.${locale}`,
|
||||
])
|
||||
.flat(),
|
||||
"camouflages",
|
||||
'camouflages',
|
||||
];
|
||||
@@ -7,8 +7,8 @@ export function uniqueGuns(turrets: TurretDefinition[]) {
|
||||
|
||||
forEachRight(turrets, (turret) => {
|
||||
forEachRight(turret.guns, (gun) => {
|
||||
if (!gunIds.has(gun.id)) {
|
||||
gunIds.add(gun.id);
|
||||
if (!gunIds.has(gun.gun_type!.value.base.id)) {
|
||||
gunIds.add(gun.gun_type!.value.base.id);
|
||||
guns.push({ turret, gun });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -108,47 +108,58 @@ 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 Burst {
|
||||
required uint32 count = 1;
|
||||
required float interval = 2;
|
||||
message GunDefinitionAutoReloader {
|
||||
required GunDefinitionBase base = 1;
|
||||
required GunDefinitionAutoReloaderProperties extension = 2;
|
||||
}
|
||||
|
||||
message GunDefinitionAutoReloader {
|
||||
message GunDefinitionAutoReloaderProperties {
|
||||
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;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src/**/*", "../i18n/src/strings.ts"]
|
||||
"include": ["src/**/*"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../../"
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,5 @@
|
||||
"name": "@blitzkit/i18n",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"dependencies": {
|
||||
"ts-deepmerge": "^7.0.3"
|
||||
},
|
||||
"devDependencies": {}
|
||||
"main": "src/index.ts"
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from "./literals";
|
||||
export * from "./strings";
|
||||
export * from "./unwrapper";
|
||||
export * from "./wrapper";
|
||||
export * from './literals';
|
||||
export * from './strings';
|
||||
export * from './unwrapper';
|
||||
@@ -1,68 +1,27 @@
|
||||
import { merge } from "ts-deepmerge";
|
||||
import locales from "../locales.json";
|
||||
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";
|
||||
import type en from '../strings/en.json';
|
||||
|
||||
export type BlitzKitStrings = typeof en;
|
||||
|
||||
const stringsRaw: Record<string, TranslationTree> = {
|
||||
en,
|
||||
es,
|
||||
fr,
|
||||
ja,
|
||||
pl,
|
||||
pt,
|
||||
ru,
|
||||
uk,
|
||||
zh,
|
||||
};
|
||||
|
||||
export const STRINGS: Record<string, BlitzKitStrings> = {};
|
||||
|
||||
for (const locale in stringsRaw) {
|
||||
if (locale === locales.default) {
|
||||
STRINGS[locales.default] = stringsRaw[locales.default] as BlitzKitStrings;
|
||||
continue;
|
||||
}
|
||||
|
||||
const localeStrings = stringsRaw[locale];
|
||||
STRINGS[locale] = merge(
|
||||
stringsRaw[locales.default],
|
||||
localeStrings
|
||||
) as BlitzKitStrings;
|
||||
}
|
||||
|
||||
export const SUPPORTED_LOCALE_BLITZ_MAP: Record<string, 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<string, string> = {
|
||||
en: "🇺🇸",
|
||||
es: "🇪🇸",
|
||||
fr: "🇫🇷",
|
||||
ja: "🇯🇵",
|
||||
pt: "🇵🇹",
|
||||
ru: "🇷🇺",
|
||||
uk: "🇺🇦",
|
||||
zh: "🇨🇳",
|
||||
pl: "🇵🇱",
|
||||
en: '🇺🇸',
|
||||
es: '🇪🇸',
|
||||
fr: '🇫🇷',
|
||||
ja: '🇯🇵',
|
||||
pt: '🇵🇹',
|
||||
ru: '🇷🇺',
|
||||
uk: '🇺🇦',
|
||||
zh: '🇨🇳',
|
||||
pl: '🇵🇱',
|
||||
};
|
||||
|
||||
export type TranslationTree = { [key: string]: TranslationNode };
|
||||
|
||||
export type TranslationNode = string | TranslationTree;
|
||||
export const LOCALE_NAMES: Record<string, string> = {
|
||||
en: 'English',
|
||||
es: 'Español',
|
||||
fr: 'Français',
|
||||
ja: '日本語',
|
||||
pt: 'Português',
|
||||
ru: 'Русский',
|
||||
uk: 'Українська',
|
||||
zh: '中文',
|
||||
pl: 'Polski',
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { I18nString } from "@blitzkit/core";
|
||||
import locales from "@blitzkit/i18n/locales.json";
|
||||
import { I18nString } from '@blitzkit/core';
|
||||
import locales from '../locales.json' with { type: 'json' };
|
||||
|
||||
export function unwrapper(locale = locales.default) {
|
||||
export function unwrapper(locale: string) {
|
||||
return function (i18nString: I18nString) {
|
||||
if (locale in i18nString.locales) {
|
||||
return i18nString.locales[locale];
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import locales from "../locales.json";
|
||||
import { BlitzKitStrings, STRINGS } from "./strings";
|
||||
|
||||
export function wrapper(slicer: (strings: BlitzKitStrings) => string) {
|
||||
const defaultString = slicer(STRINGS[locales.default] as BlitzKitStrings);
|
||||
|
||||
const object = { [locales.default]: defaultString };
|
||||
|
||||
for (const { locale } of locales.supported) {
|
||||
const string = slicer(STRINGS[locale] as BlitzKitStrings);
|
||||
if (string !== defaultString) object[locale] = string;
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
@@ -6,7 +6,13 @@
|
||||
},
|
||||
|
||||
"common": {
|
||||
"name": "BlitzKit",
|
||||
"grades": {
|
||||
"0": "Unspecified grade",
|
||||
"1": "Common",
|
||||
"2": "Rare",
|
||||
"3": "Epic",
|
||||
"4": "Legendary"
|
||||
},
|
||||
|
||||
"units": {
|
||||
"m": "%s1m",
|
||||
@@ -15,7 +21,8 @@
|
||||
"hp": "%s1hp",
|
||||
"x": "x%s1",
|
||||
"percentage": "%s1%",
|
||||
"deg_s": "%s1°/s"
|
||||
"deg_s": "%s1°/s",
|
||||
"times": "x%s1"
|
||||
},
|
||||
|
||||
"gun_types": {
|
||||
@@ -765,8 +772,6 @@
|
||||
},
|
||||
|
||||
"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",
|
||||
@@ -837,15 +842,6 @@
|
||||
},
|
||||
|
||||
"home": {
|
||||
"pwa": {
|
||||
"title": "Install the BlitzKit App",
|
||||
"new": "NEW",
|
||||
"checking": "Checking compatibility...",
|
||||
"instructions": "Instructions for Apple devices.",
|
||||
"install": "Install",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
|
||||
"discord": {
|
||||
"join_short": "Join cool people!",
|
||||
"join_long": "Join super duper cool people!",
|
||||
@@ -874,13 +870,11 @@
|
||||
},
|
||||
|
||||
"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"
|
||||
"internal": {
|
||||
"name": "Internal",
|
||||
"button": "Internal",
|
||||
"description": "BlitzKit internal tools",
|
||||
"seo_description": "BlitzKit internal tools"
|
||||
},
|
||||
|
||||
"guess": {
|
||||
@@ -909,10 +903,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -939,13 +929,6 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -961,7 +944,9 @@
|
||||
"green_penetration": "Green penetration",
|
||||
"hide_model_under_armor": "Hide model under armor",
|
||||
"opaque": "Opaque",
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Environment",
|
||||
"show_grid": "Show grid"
|
||||
},
|
||||
|
||||
"screenshot": {
|
||||
@@ -1057,6 +1042,7 @@
|
||||
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
"disclaimer": "BlitzKit does not endorse any review videos.",
|
||||
"join": "Apply to %s1 YouTuber program"
|
||||
},
|
||||
|
||||
@@ -1120,8 +1106,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Shells",
|
||||
"mostOptimalShellIndex": "Most optimal shell",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Reload",
|
||||
"shellReloads": "Shell reloads",
|
||||
"shellReload": "Shell reload",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Caliber",
|
||||
"penetration": "Penetration",
|
||||
@@ -1179,9 +1165,7 @@
|
||||
"shellRange": "Shell range",
|
||||
"shellCapacity": "Shell capacity",
|
||||
"gunType": "Gun type",
|
||||
"penetrationAt250m": "Penetration at 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
"penetrationAt250m": "Penetration at 250m"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1289,17 +1273,33 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"gallery": {
|
||||
"name": "Gallery",
|
||||
|
||||
"avatars": {
|
||||
"name": "Avatars",
|
||||
"button": "Browse all",
|
||||
"description": "Blitz player avatars",
|
||||
"seo_description": "All World of Tanks Blitz player avatars",
|
||||
"last_updated": "Last updated on %s1",
|
||||
"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",
|
||||
"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,11 +1461,17 @@
|
||||
"description": "Get started with BlitzKit",
|
||||
"seo_description": "Get started with BlitzKit"
|
||||
},
|
||||
"api": {
|
||||
"name": "API",
|
||||
"button": "To the docs",
|
||||
"description": "BlitzKit hosted API",
|
||||
"seo_description": "BlitzKit hosted static API for World of Tanks Blitz"
|
||||
"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"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"fox": "El rápido zorro marrón salta sobre el perro perezoso."
|
||||
},
|
||||
"common": {
|
||||
"name": "BlitzKit",
|
||||
"units": {
|
||||
"m": "%s1m",
|
||||
"mm": "%s1mm",
|
||||
@@ -672,8 +671,6 @@
|
||||
"copied": "Copiado!"
|
||||
},
|
||||
"tank_search": {
|
||||
"select_dialog_title": "Search for tanks",
|
||||
"swap_dialog_title": "Swap tank",
|
||||
"clear_filters": "Clear filters",
|
||||
"search_bar_hint": "Buscar tanques...",
|
||||
"recent": "Visto recientemente",
|
||||
@@ -742,14 +739,6 @@
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"pwa": {
|
||||
"title": "Install the BlitzKit App",
|
||||
"new": "NEW",
|
||||
"checking": "Checking compatibility...",
|
||||
"instructions": "Instructions for Apple devices.",
|
||||
"install": "Install",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"discord": {
|
||||
"join_short": "Join cool people!",
|
||||
"join_long": "Join super duper cool people!",
|
||||
@@ -775,14 +764,6 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"name": "Guess the tank",
|
||||
"button": "Guess away",
|
||||
@@ -806,9 +787,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -832,12 +810,6 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -851,7 +823,9 @@
|
||||
"green_penetration": "Penetración verde",
|
||||
"hide_model_under_armor": "Ocultar modelo bajo la armadura",
|
||||
"opaque": "Opaco",
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Entorno",
|
||||
"show_grid": "Mostrar cuadrícula"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "Descargar",
|
||||
@@ -938,6 +912,7 @@
|
||||
},
|
||||
"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": {
|
||||
@@ -998,8 +973,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Proyectiles",
|
||||
"mostOptimalShellIndex": "Proyectil más óptimo",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Recarga",
|
||||
"shellReloads": "Tiempos de recarga",
|
||||
"shellReload": "Tiempo de recarga",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Calibre",
|
||||
"penetration": "Penetración",
|
||||
@@ -1057,9 +1032,7 @@
|
||||
"shellRange": "Rango del proyectil",
|
||||
"shellCapacity": "Capacidad de proyectiles",
|
||||
"gunType": "Tipo de cañón",
|
||||
"penetrationAt250m": "Penetración a 250 m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
"penetrationAt250m": "Penetración a 250 m"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
@@ -1327,11 +1300,17 @@
|
||||
"description": "Get started with BlitzKit",
|
||||
"seo_description": "Get started with BlitzKit"
|
||||
},
|
||||
"api": {
|
||||
"name": "API",
|
||||
"button": "To the docs",
|
||||
"description": "BlitzKit hosted API",
|
||||
"seo_description": "BlitzKit hosted static API for World of Tanks Blitz"
|
||||
"blitz_analysis": {
|
||||
"name": "BlitzAnalysis[]",
|
||||
"button": "Analizar ahora",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"fox": "Le renard brun et rapide saute par-dessus le chien paresseux."
|
||||
},
|
||||
"common": {
|
||||
"name": "BlitzKit",
|
||||
"units": {
|
||||
"m": "%s1m",
|
||||
"mm": "%s1mm",
|
||||
@@ -365,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 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."
|
||||
"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."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
@@ -381,7 +380,7 @@
|
||||
},
|
||||
"embeds": {
|
||||
"name": "intègre",
|
||||
"description": "Comment fonctionnent les intégrations"
|
||||
"description": "How embeds work"
|
||||
},
|
||||
"introduction": {
|
||||
"name": "introduction",
|
||||
@@ -403,7 +402,7 @@
|
||||
},
|
||||
"breakdown": {
|
||||
"name": "décomposition",
|
||||
"description": "Répartition d'une période par chars joués",
|
||||
"description": "A period's breakdown by tanks played",
|
||||
"body": {
|
||||
"total": "Total",
|
||||
"battles": "Batailles",
|
||||
@@ -472,7 +471,7 @@
|
||||
"accuracy": "Précision"
|
||||
},
|
||||
"lethality": {
|
||||
"title": "Létalité",
|
||||
"title": "Lethality",
|
||||
"average_damage": "Dégâts moyens",
|
||||
"damage_per_tier": "Dégâts par tier",
|
||||
"damage_ratio": "Rapport de dégâts",
|
||||
@@ -500,20 +499,20 @@
|
||||
},
|
||||
"embed": {
|
||||
"title": "Compte invalide ?",
|
||||
"description": "Utilisez à nouveau la commande et sélectionnez un nom d'utilisateur à partir des résultats de recherche."
|
||||
"description": "Use the command again and select a username from the search results."
|
||||
}
|
||||
},
|
||||
"search_clans": {
|
||||
"name": "clans de recherche",
|
||||
"description": "Rechercher des clans",
|
||||
"name": "search-clans",
|
||||
"description": "Search clans",
|
||||
"options": {
|
||||
"limit": {
|
||||
"name": "limite",
|
||||
"description": "Nombre maximum de résultats du clan (par défaut: %s1)"
|
||||
"description": "The max number of clan results (default: %s1)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"title": "Recherche de clan %s1 dans la région %s2",
|
||||
"title": "Clan search for %s1 in region %s2",
|
||||
"no_results": "Pas de clan trouvé"
|
||||
}
|
||||
},
|
||||
@@ -536,69 +535,69 @@
|
||||
"description": "Statistiques des batailles de cotés",
|
||||
"subcommands": {
|
||||
"neighbors": {
|
||||
"name": "voisins",
|
||||
"description": "Joueurs voisins dans le classement"
|
||||
"name": "neighbors",
|
||||
"description": "Neighboring players on the leaderboard"
|
||||
},
|
||||
"league": {
|
||||
"name": "ligue",
|
||||
"description": "Meilleurs joueurs d'une ligue"
|
||||
"description": "Top players of a league"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"limit": {
|
||||
"name": "limite",
|
||||
"description": "Combien de voisins afficher (par défaut : %s1)"
|
||||
"description": "How many neighbors to display (default: %s1)"
|
||||
},
|
||||
"season": {
|
||||
"name": "saison",
|
||||
"description": "Numéro de saison (défaut : courant)",
|
||||
"name": "season",
|
||||
"description": "Season number (default: current)",
|
||||
"choices": {
|
||||
"current": "%s1 (actuel)"
|
||||
"current": "%s1 (current)"
|
||||
}
|
||||
},
|
||||
"league": {
|
||||
"name": "ligue",
|
||||
"description": "La ligue à afficher"
|
||||
"description": "The league to display"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"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"
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"today": {
|
||||
"name": "aujourd'hui",
|
||||
"description": "Les chars et les statistiques joués aujourd'hui"
|
||||
"description": "Today's played tanks and statistics"
|
||||
},
|
||||
"stats": {
|
||||
"name": "statistiques",
|
||||
"description": "Statistiques rapides pour une période"
|
||||
"description": "Quick statistics for a period"
|
||||
},
|
||||
"owned_tanks": {
|
||||
"name": "chars possédés",
|
||||
"description": "Affiche tous les chars d'un niveau",
|
||||
"name": "owned-tanks",
|
||||
"description": "Shows all own tanks in a tier",
|
||||
"body": {
|
||||
"title": "# %s1 possédait des chars de rang %s2"
|
||||
"title": "# %s1's owned tier %s2 tanks\n\n"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"name": "permissions",
|
||||
"description": "Vérifie si le bot a les permissions nécessaires pour fonctionner",
|
||||
"description": "Checks wether the bot has the required permissions to function",
|
||||
"body": {
|
||||
"title": "# Permissions\n\n%s1",
|
||||
"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"
|
||||
"view_channels": "View channels: needed to find the message the refresh button was used on",
|
||||
"read_message_history": "Read message history: needed to find the message to refresh",
|
||||
"attach_files": "Attach files: needed to add images to image-based commands",
|
||||
"external_emojis": "External emojis: needed to add national flags and tank class emojis"
|
||||
}
|
||||
},
|
||||
"ping": {
|
||||
"name": "ping",
|
||||
"description": "Vérifiez si le bot est en vie.",
|
||||
"description": "Check wether the bot is alive.",
|
||||
"body": {
|
||||
"pong": "Pong 🏓",
|
||||
"pong_with_time": "Pong 🏓 - %s1ms"
|
||||
@@ -606,7 +605,7 @@
|
||||
"subcommands": {
|
||||
"blitzkit": {
|
||||
"name": "blitzkit",
|
||||
"description": "Ping blitzkit"
|
||||
"description": "Ping BlitzKit"
|
||||
},
|
||||
"wotb": {
|
||||
"name": "wotb",
|
||||
@@ -615,10 +614,10 @@
|
||||
}
|
||||
},
|
||||
"player_info": {
|
||||
"name": "info-joueur",
|
||||
"description": "Informations de base sur un joueur",
|
||||
"name": "player-info",
|
||||
"description": "Basic information about a player",
|
||||
"body": {
|
||||
"title": "Informations de %s1",
|
||||
"title": "%s1's information",
|
||||
"nickname": "Nickname",
|
||||
"battles": "Batailles",
|
||||
"winrate": "Taux de victoires",
|
||||
@@ -636,7 +635,7 @@
|
||||
"description": "Today's rating statistics"
|
||||
},
|
||||
"season": {
|
||||
"name": "saison",
|
||||
"name": "season",
|
||||
"description": "This season's rating statistics"
|
||||
}
|
||||
},
|
||||
@@ -672,8 +671,6 @@
|
||||
"copied": "Copied!"
|
||||
},
|
||||
"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",
|
||||
@@ -742,14 +739,6 @@
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"pwa": {
|
||||
"title": "Install the BlitzKit App",
|
||||
"new": "NEW",
|
||||
"checking": "Checking compatibility...",
|
||||
"instructions": "Instructions for Apple devices.",
|
||||
"install": "Install",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"discord": {
|
||||
"join_short": "Join cool people!",
|
||||
"join_long": "Join super duper cool people!",
|
||||
@@ -775,14 +764,6 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"name": "Guess the tank",
|
||||
"button": "Guess away",
|
||||
@@ -806,9 +787,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -832,12 +810,6 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -851,7 +823,9 @@
|
||||
"green_penetration": "Green penetration",
|
||||
"hide_model_under_armor": "Hide model under armor",
|
||||
"opaque": "Opaque",
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Environment",
|
||||
"show_grid": "Show grid"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "Download",
|
||||
@@ -938,6 +912,7 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
"disclaimer": "BlitzKit does not endorse any review videos.",
|
||||
"join": "Apply to %s1 YouTuber program"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -998,8 +973,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Shells",
|
||||
"mostOptimalShellIndex": "Most optimal shell",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Reload",
|
||||
"shellReloads": "Shell reloads",
|
||||
"shellReload": "Shell reload",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Caliber",
|
||||
"penetration": "Penetration",
|
||||
@@ -1057,9 +1032,7 @@
|
||||
"shellRange": "Shell range",
|
||||
"shellCapacity": "Shell capacity",
|
||||
"gunType": "Gun type",
|
||||
"penetrationAt250m": "Penetration at 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
"penetrationAt250m": "Penetration at 250m"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
@@ -1327,11 +1300,17 @@
|
||||
"description": "Get started with BlitzKit",
|
||||
"seo_description": "Get started with BlitzKit"
|
||||
},
|
||||
"api": {
|
||||
"name": "API",
|
||||
"button": "To the docs",
|
||||
"description": "BlitzKit hosted API",
|
||||
"seo_description": "BlitzKit hosted static API for World of Tanks Blitz"
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"fox": "すばしっこい茶色のキツネが怠け者の犬を飛び越した。"
|
||||
},
|
||||
"common": {
|
||||
"name": "BlitzKit",
|
||||
"units": {
|
||||
"m": "%s1m",
|
||||
"mm": "%s1mm",
|
||||
@@ -672,8 +671,6 @@
|
||||
"copied": "コピーしました!"
|
||||
},
|
||||
"tank_search": {
|
||||
"select_dialog_title": "Search for tanks",
|
||||
"swap_dialog_title": "Swap tank",
|
||||
"clear_filters": "Clear filters",
|
||||
"search_bar_hint": "車輌名を入力…",
|
||||
"recent": "最近の閲覧",
|
||||
@@ -742,14 +739,6 @@
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"pwa": {
|
||||
"title": "Install the BlitzKit App",
|
||||
"new": "NEW",
|
||||
"checking": "Checking compatibility...",
|
||||
"instructions": "Instructions for Apple devices.",
|
||||
"install": "Install",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"discord": {
|
||||
"join_short": "Join cool people!",
|
||||
"join_long": "Join super duper cool people!",
|
||||
@@ -775,14 +764,6 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"name": "Guess the tank",
|
||||
"button": "Guess away",
|
||||
@@ -806,9 +787,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -832,12 +810,6 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -851,7 +823,9 @@
|
||||
"green_penetration": "貫通可能域を緑色表示",
|
||||
"hide_model_under_armor": "車輌モデルを非表示",
|
||||
"opaque": "貫通アシストを不透明にする",
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "環境",
|
||||
"show_grid": "グリッドを表示"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "保存する",
|
||||
@@ -938,6 +912,7 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
"disclaimer": "BlitzKitはいかなるレビュー動画も後援していません。",
|
||||
"join": "%s1 YouTuberプログラムに応募する"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -998,7 +973,7 @@
|
||||
"dpmEffective": "60秒での有効値",
|
||||
"shells": "砲弾",
|
||||
"mostOptimalShellIndex": "最適な弾数",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReloads": "自動装填時間",
|
||||
"shellReload": "装填時間",
|
||||
"intraClip": "砲弾再装填時間",
|
||||
"caliber": "口径",
|
||||
@@ -1057,9 +1032,7 @@
|
||||
"shellRange": "射程",
|
||||
"shellCapacity": "総弾数",
|
||||
"gunType": "主砲タイプ",
|
||||
"penetrationAt250m": "250m での貫通力",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
"penetrationAt250m": "250m での貫通力"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
@@ -1327,11 +1300,17 @@
|
||||
"description": "さあ、BlitzKit を始めよう!",
|
||||
"seo_description": "さあ、BlitzKit を始めよう!"
|
||||
},
|
||||
"api": {
|
||||
"name": "API",
|
||||
"button": "To the docs",
|
||||
"description": "BlitzKit hosted API",
|
||||
"seo_description": "BlitzKit hosted static API for World of Tanks Blitz"
|
||||
"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": "ゲーム内のセッション統計",
|
||||
"seo_description": "In-game session stats in World of Tanks Blitz"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"fox": "Szybki brązowy lis przeskoczył nad leniwym psem."
|
||||
},
|
||||
"common": {
|
||||
"name": "BlitzKit",
|
||||
"units": {
|
||||
"m": "%s1m",
|
||||
"mm": "%s1mm",
|
||||
@@ -672,8 +671,6 @@
|
||||
"copied": "Copied!"
|
||||
},
|
||||
"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",
|
||||
@@ -742,14 +739,6 @@
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"pwa": {
|
||||
"title": "Install the BlitzKit App",
|
||||
"new": "NEW",
|
||||
"checking": "Checking compatibility...",
|
||||
"instructions": "Instructions for Apple devices.",
|
||||
"install": "Install",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"discord": {
|
||||
"join_short": "Join cool people!",
|
||||
"join_long": "Join super duper cool people!",
|
||||
@@ -775,14 +764,6 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"name": "Guess the tank",
|
||||
"button": "Guess away",
|
||||
@@ -806,9 +787,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -832,12 +810,6 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -851,7 +823,9 @@
|
||||
"green_penetration": "Green penetration",
|
||||
"hide_model_under_armor": "Hide model under armor",
|
||||
"opaque": "Opaque",
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Environment",
|
||||
"show_grid": "Show grid"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "Download",
|
||||
@@ -938,6 +912,7 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
"disclaimer": "BlitzKit does not endorse any review videos.",
|
||||
"join": "Apply to %s1 YouTuber program"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -998,8 +973,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Shells",
|
||||
"mostOptimalShellIndex": "Most optimal shell",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Reload",
|
||||
"shellReloads": "Shell reloads",
|
||||
"shellReload": "Shell reload",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Caliber",
|
||||
"penetration": "Penetration",
|
||||
@@ -1057,9 +1032,7 @@
|
||||
"shellRange": "Shell range",
|
||||
"shellCapacity": "Shell capacity",
|
||||
"gunType": "Gun type",
|
||||
"penetrationAt250m": "Penetration at 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
"penetrationAt250m": "Penetration at 250m"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
@@ -1327,11 +1300,17 @@
|
||||
"description": "Get started with BlitzKit",
|
||||
"seo_description": "Get started with BlitzKit"
|
||||
},
|
||||
"api": {
|
||||
"name": "API",
|
||||
"button": "To the docs",
|
||||
"description": "BlitzKit hosted API",
|
||||
"seo_description": "BlitzKit hosted static API for World of Tanks Blitz"
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"fox": "A ágil raposa castanha salta por cima do cão preguiçoso."
|
||||
},
|
||||
"common": {
|
||||
"name": "BlitzKit",
|
||||
"units": {
|
||||
"m": "%s1m",
|
||||
"mm": "%s1mm",
|
||||
@@ -672,8 +671,6 @@
|
||||
"copied": "Copiado!"
|
||||
},
|
||||
"tank_search": {
|
||||
"select_dialog_title": "Search for tanks",
|
||||
"swap_dialog_title": "Swap tank",
|
||||
"clear_filters": "Clear filters",
|
||||
"search_bar_hint": "Procurar tanques...",
|
||||
"recent": "Visto recentemente",
|
||||
@@ -742,14 +739,6 @@
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"pwa": {
|
||||
"title": "Install the BlitzKit App",
|
||||
"new": "NEW",
|
||||
"checking": "Checking compatibility...",
|
||||
"instructions": "Instructions for Apple devices.",
|
||||
"install": "Install",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"discord": {
|
||||
"join_short": "Join cool people!",
|
||||
"join_long": "Join super duper cool people!",
|
||||
@@ -775,14 +764,6 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"name": "Guess the tank",
|
||||
"button": "Guess away",
|
||||
@@ -806,9 +787,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -832,12 +810,6 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -851,7 +823,9 @@
|
||||
"green_penetration": "Penetração verde",
|
||||
"hide_model_under_armor": "Ocultar modelo sob blindagem",
|
||||
"opaque": "Opaco",
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Ambiente",
|
||||
"show_grid": "Exibir grade"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "Download",
|
||||
@@ -938,6 +912,7 @@
|
||||
},
|
||||
"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": {
|
||||
@@ -998,8 +973,8 @@
|
||||
"dpmEffective": "Efetivo aos 60s",
|
||||
"shells": "Projéteis",
|
||||
"mostOptimalShellIndex": "Projétil mais ideal",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Recarga",
|
||||
"shellReloads": "Recarga de projétil",
|
||||
"shellReload": "Recarregamento de projétil",
|
||||
"intraClip": "Intra-clipe",
|
||||
"caliber": "Calibre",
|
||||
"penetration": "Penetração",
|
||||
@@ -1057,9 +1032,7 @@
|
||||
"shellRange": "Alcance de projétil",
|
||||
"shellCapacity": "Capacidade de projéteis",
|
||||
"gunType": "Tipo de arma",
|
||||
"penetrationAt250m": "Penetração à 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
"penetrationAt250m": "Penetração à 250m"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
@@ -1327,11 +1300,17 @@
|
||||
"description": "Comece com BlitzKit",
|
||||
"seo_description": "Comece com BlitzKit"
|
||||
},
|
||||
"api": {
|
||||
"name": "API",
|
||||
"button": "To the docs",
|
||||
"description": "BlitzKit hosted API",
|
||||
"seo_description": "BlitzKit hosted static API for World of Tanks Blitz"
|
||||
"blitz_analysis": {
|
||||
"name": "AnaliseBlitz[]",
|
||||
"button": "Analise agora",
|
||||
"description": "Estatísticas médias históricas ricas",
|
||||
"seo_description": "Estatísticas médias históricas ricas no World of Tanks Blitz"
|
||||
},
|
||||
"blitz_insider": {
|
||||
"name": "Assistente Blitz",
|
||||
"button": "Descubra hoje",
|
||||
"description": "Estatísticas da temporada dentro do jogo",
|
||||
"seo_description": "Estatísticas da sessão no jogo World of Tanks Blitz"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
{
|
||||
"test": {
|
||||
"default": "тест",
|
||||
"long": "Это длинная тестовая строка. Вы НИКОГДА не должны видеть её в пользовательском интерфейсе. Мой любимый фрукт — %s1, потому что это цвет %s2. Он мне так нравится, что я произнесу его имя 3 раза! %s1! %s1! %s1! И ещё раз, это %s2, а не %s3.",
|
||||
"fox": "Съешь же ещё этих мягких французских булок, да выпей чаю."
|
||||
"long": "Это длинная тестовая строка. Вы НИКОГДА не должны видеть её в пользовательском интерфейсе. Мой любимый фрукт - %s1, потому что его цвет %s2. Он мне так нравится, что я произнесу его имя 3 раза! %s1! %s1! %s1! И ещё раз, это %s2, а не %s3.",
|
||||
"fox": "Это длинная тестовая строка. Вы НИКОГДА не должны видеть её в пользовательском интерфейсе. Мой любимый фрукт - %s1, потому что его цвет %s2. Он мне так нравится, что я произнесу его имя 3 раза! %s1! %s1! %s1! И ещё раз, это %s2, а не %s3."
|
||||
},
|
||||
"common": {
|
||||
"name": "BlitzKit",
|
||||
"units": {
|
||||
"m": "%s1м",
|
||||
"mm": "%s1мм",
|
||||
@@ -284,15 +283,15 @@
|
||||
"name": "уровень",
|
||||
"description": "Уровень",
|
||||
"choices": {
|
||||
"tier": "Уровень %s1 - %s2"
|
||||
"tier": "Уровень %s - %s"
|
||||
}
|
||||
}
|
||||
},
|
||||
"periods": {
|
||||
"career": "Карьера",
|
||||
"today": "Cегодня",
|
||||
"days": "%s1 дней",
|
||||
"custom": "%s1 - %s2 дней"
|
||||
"days": "%s дней",
|
||||
"custom": "%s - %s дней"
|
||||
},
|
||||
"filters": {
|
||||
"none": "Без фильтров",
|
||||
@@ -324,9 +323,9 @@
|
||||
"description": "Танки доступные на аукционе сейчас",
|
||||
"body": {
|
||||
"title": "Blitz аукцион",
|
||||
"subtitle": "%s1 доступные • следующие цены %s2\nПокупка сейчас: %s3",
|
||||
"next": "Следующая цена: %s1",
|
||||
"available": "Доступно: %s1 / %s2"
|
||||
"subtitle": "%s доступные • следующие цены %s\nПокупка сейчас: %s",
|
||||
"next": "Следующая цена: %s",
|
||||
"available": "Доступно: %s / %s"
|
||||
},
|
||||
"errors": {
|
||||
"no_auction": "# Аукцион сейчас не проходит\n\nНа данный момент нет танков для покупки на аукционе. Если считаете, что это ошибка, пожалуйста о несоответствии на [официальном сервере Discord](https://discord.gg/nDt7AjGJQH)."
|
||||
@@ -355,19 +354,19 @@
|
||||
"body": {
|
||||
"subtitle": "Исследование",
|
||||
"total": "Всего",
|
||||
"research": "Исследование: %s1",
|
||||
"upgrades": "Улучшения: %s1",
|
||||
"purchase": "Покупка: %s1",
|
||||
"equipment": "Снаряжение: %s1",
|
||||
"research": "Исследование: %s",
|
||||
"upgrades": "Улучшения: %s",
|
||||
"purchase": "Покупка: %s",
|
||||
"equipment": "Снаряжение: %s",
|
||||
"estimation": "# Это приблизительная оценка!\n\nВ этот список могут входить уже исследованные танки из-за неточной информации, предоставляемой Wargaming. Попробуйте указать «стартовый-танк» в строке при использовании команды, если сможете."
|
||||
},
|
||||
"errors": {
|
||||
"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», так как это танк более низкого уровня."
|
||||
"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», так как это танк более низкого уровня."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
@@ -410,7 +409,7 @@
|
||||
"wn8": "WN8",
|
||||
"winrate": "Коэффициент побед",
|
||||
"damage": "Урон",
|
||||
"unknown_tank": "Неизвестный танк %s1"
|
||||
"unknown_tank": "Неизвестный танк %s"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
@@ -446,12 +445,12 @@
|
||||
"options": {
|
||||
"threshold": {
|
||||
"name": "порог",
|
||||
"description": "Количество дней не активности (по умолчанию: %s1 дней)"
|
||||
"description": "Количество дней не активности (по умолчанию: %s дней)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"subtitle": "Неактивен %s1+ дней",
|
||||
"listing": "%s1 дней"
|
||||
"subtitle": "Неактивен %s+ дней",
|
||||
"listing": "%s дней"
|
||||
}
|
||||
},
|
||||
"full_stats": {
|
||||
@@ -509,11 +508,11 @@
|
||||
"options": {
|
||||
"limit": {
|
||||
"name": "лимит",
|
||||
"description": "Максимальное количество результатов клана (по умолчанию: %s1)"
|
||||
"description": "Максимальное количество результатов клана (по умолчанию: %s)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"title": "Поиск клана %s1 в регионе %s2",
|
||||
"title": "Поиск клана %s в регионе %s",
|
||||
"no_results": "Кланов не найдено"
|
||||
}
|
||||
},
|
||||
@@ -523,11 +522,11 @@
|
||||
"options": {
|
||||
"limit": {
|
||||
"name": "лимит",
|
||||
"description": "Максимальное число результатов игрока (по умолчанию: %s1)"
|
||||
"description": "Максимальное число результатов игрока (по умолчанию: %s)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"title": "Поиск игрока %s1 в регионе %s2",
|
||||
"title": "Поиск игрока %s в регионе %s",
|
||||
"no_results": "Игроков не найдено"
|
||||
}
|
||||
},
|
||||
@@ -547,13 +546,13 @@
|
||||
"options": {
|
||||
"limit": {
|
||||
"name": "лимит",
|
||||
"description": "Количество отображаемых ближайших игроков (по умолчанию: %s1)"
|
||||
"description": "Количество отображаемых ближайших игроков (по умолчанию: %s)"
|
||||
},
|
||||
"season": {
|
||||
"name": "сезон",
|
||||
"description": "Номер сезона (по умолчанию: текущий)",
|
||||
"choices": {
|
||||
"current": "%s1 (текущая)"
|
||||
"current": "%s (текущая)"
|
||||
}
|
||||
},
|
||||
"league": {
|
||||
@@ -562,12 +561,12 @@
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"top_players": "Топ %s1 игроков",
|
||||
"no_participation": "# %s1 не играл в рейтинг в сезоне %s2\n\nЭтот игрок не участвовал в этом сезоне или не получил прошлую калибровку.",
|
||||
"top_players": "Топ %s игроков",
|
||||
"no_participation": "# %s не играл в рейтинг в сезоне %s\n\nЭтот игрок не участвовал в этом сезоне или не получил прошлую калибровку.",
|
||||
"no_ongoing_season": "# Нет текущего сезона\n\nВ данный момент нет рейтингового сезона.",
|
||||
"deleted_player": "Удалено %s1",
|
||||
"deleted_player": "Удалено %s",
|
||||
"neighbors": "Рейтинг ближайших игроков",
|
||||
"more": "+ ещё %s1"
|
||||
"more": "+ ещё %s"
|
||||
}
|
||||
},
|
||||
"today": {
|
||||
@@ -582,14 +581,14 @@
|
||||
"name": "cобственные-танки",
|
||||
"description": "Показывает все принадлежащие танки на уровне",
|
||||
"body": {
|
||||
"title": "# %s1 принадлежал уровень %s2 танков\n\n"
|
||||
"title": "# %s принадлежал уровень %s танков\n\n"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"name": "разрешения",
|
||||
"description": "Проверяет, есть ли у бота необходимые разрешения для работы",
|
||||
"body": {
|
||||
"title": "# Разрешения\n\n%s1",
|
||||
"title": "# Разрешения\n\n%s",
|
||||
"view_channels": "Просмотр каналов: необходимо найти сообщение, на котором была использована кнопка обновления",
|
||||
"read_message_history": "Просмотр истории сообщений: необходимо найти сообщение для обновления",
|
||||
"attach_files": "Прикрепить файлы: необходимо для добавления изображений в команды на основе изображения",
|
||||
@@ -601,7 +600,7 @@
|
||||
"description": "Проверьте, что бот жив.",
|
||||
"body": {
|
||||
"pong": "Понг 🏓",
|
||||
"pong_with_time": "Понг 🏓 - %s1мс"
|
||||
"pong_with_time": "Понг 🏓 - %sмс"
|
||||
},
|
||||
"subcommands": {
|
||||
"blitzkit": {
|
||||
@@ -618,7 +617,7 @@
|
||||
"name": "информация-об-игроке",
|
||||
"description": "Основная информация о игроке",
|
||||
"body": {
|
||||
"title": "Информация о %s1",
|
||||
"title": "Информация о %s",
|
||||
"nickname": "Никнейм",
|
||||
"battles": "Боёв",
|
||||
"winrate": "Коэффициент побед",
|
||||
@@ -647,7 +646,7 @@
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"no_participation": "# %s1 не получил рейтинга\n\nЭтот игрок в этом сезоне не участвовал в рейтинговом режиме.",
|
||||
"no_participation": "# %s не получил рейтинга\n\nЭтот игрок в этом сезоне не участвовал в рейтинговом режиме.",
|
||||
"unrecorded_stats": "# Статистика недоступна в настоящее время\n\nВ настоящее время Wargaming, возможно, сталкивается с простоями сервера. Если сезон продолжается, статистика должна быть доступна в течение 24 часов."
|
||||
}
|
||||
}
|
||||
@@ -672,14 +671,12 @@
|
||||
"copied": "Скопировано!"
|
||||
},
|
||||
"tank_search": {
|
||||
"select_dialog_title": "Search for tanks",
|
||||
"swap_dialog_title": "Swap tank",
|
||||
"clear_filters": "Сбросить фильтры",
|
||||
"search_bar_hint": "Поиск танков...",
|
||||
"recent": "Недавние",
|
||||
"select_all": "Выбрать всё",
|
||||
"count_singular": "1 танк",
|
||||
"count_plural": "Танков: %s1",
|
||||
"count_plural": "%s1 танк",
|
||||
"sorting_by": "Сортировать по %s1",
|
||||
"no_tanks_found": {
|
||||
"body": "Танков не найдено.",
|
||||
@@ -696,7 +693,7 @@
|
||||
"survivability.volume": "объём",
|
||||
"survivability.length": "длина",
|
||||
"fire.dpm": "базовый ДПМ",
|
||||
"fire.dpmPremium": "премиумный ДПМ",
|
||||
"fire.dpmPremium": "улучшенный ДПМ",
|
||||
"fire.reload": "перезарядка",
|
||||
"fire.caliber": "калибр",
|
||||
"fire.standardPenetration": "базовое пробитие",
|
||||
@@ -742,14 +739,6 @@
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"pwa": {
|
||||
"title": "Install the BlitzKit App",
|
||||
"new": "NEW",
|
||||
"checking": "Checking compatibility...",
|
||||
"instructions": "Instructions for Apple devices.",
|
||||
"install": "Install",
|
||||
"dismiss": "Отклонить"
|
||||
},
|
||||
"discord": {
|
||||
"join_short": "Присоединяйтесь к крутым ребятам!",
|
||||
"join_long": "Присоединяйся к супер-дупер крутым ребятам!",
|
||||
@@ -775,14 +764,6 @@
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"mixer": {
|
||||
"name": "Смешиватель",
|
||||
"button": "Смешать и собрать",
|
||||
"description": "Модули смешанного танка",
|
||||
"seo_description": "Смешивание и сбор корпусов, башен и пушек танков из World of Tanks Blitz",
|
||||
"screenshot": "Screenshot",
|
||||
"randomize": "Случайный выбор"
|
||||
},
|
||||
"guess": {
|
||||
"name": "Угадайте танк",
|
||||
"button": "Угадать",
|
||||
@@ -803,14 +784,11 @@
|
||||
"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": "Штурмовые снаряды"
|
||||
"title": "Assault shells"
|
||||
},
|
||||
"rotator": {
|
||||
"hull": "Корпус",
|
||||
@@ -828,16 +806,10 @@
|
||||
"no_ricochet": "Разрывные снаряды не рикошетят"
|
||||
},
|
||||
"view_range": {
|
||||
"camo": "Камуфляж",
|
||||
"camo": "Камо",
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Промзона",
|
||||
"dead_rail": "Эшелон"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Поворот",
|
||||
"pitch": "Наклон",
|
||||
"min": "Вниз",
|
||||
"max": "Вверх"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -851,7 +823,9 @@
|
||||
"green_penetration": "Зелёное пробитие",
|
||||
"hide_model_under_armor": "Скрыть модель под броней",
|
||||
"opaque": "Прозрачность",
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Окружение",
|
||||
"show_grid": "Показать сетку"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "Загрузить",
|
||||
@@ -922,7 +896,7 @@
|
||||
"tier": "Уровень",
|
||||
"type": "Тип",
|
||||
"dev_id": "DEV: ID",
|
||||
"purchase": "Цена покупки",
|
||||
"purchase": "Стоимость покупки",
|
||||
"restoration": "Цена восстановления",
|
||||
"sale": "Цена продажи",
|
||||
"research": "Необходимо опыта"
|
||||
@@ -938,6 +912,7 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Видеообзоры",
|
||||
"disclaimer": "BlitzKit не несёт ответственности за видеообзоры.",
|
||||
"join": "Предложить ютубера для программы %s1"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -998,13 +973,13 @@
|
||||
"dpmEffective": "Эффективность за 60с",
|
||||
"shells": "Снарядов в барабане",
|
||||
"mostOptimalShellIndex": "Самый эффективный снаряд",
|
||||
"shellReloads": "Перезарядка",
|
||||
"shellReloads": "Перезарядка снарядов",
|
||||
"shellReload": "Перезарядка",
|
||||
"intraClip": "Между снарядами",
|
||||
"caliber": "Калибр",
|
||||
"penetration": "Пробитие",
|
||||
"damage": "Урон",
|
||||
"damageWithoutAssault": "Урон без штурмовых снарядов",
|
||||
"damageWithoutAssault": "Damage without assault shells",
|
||||
"clipDamage": "Урон с барабана",
|
||||
"moduleDamage": "Урон по модулям",
|
||||
"explosionRadius": "Разлет осколков",
|
||||
@@ -1057,9 +1032,7 @@
|
||||
"shellRange": "Дальность снаряда",
|
||||
"shellCapacity": "Боезапас",
|
||||
"gunType": "Тип орудия",
|
||||
"penetrationAt250m": "Пробитие на 250м",
|
||||
"burstShells": "Кол-во разрывных снарядов",
|
||||
"burstInterShell": "Взрыв между снарядами"
|
||||
"penetrationAt250m": "Пробитие на 250м"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
@@ -1156,7 +1129,7 @@
|
||||
"hits": "Уничтожен",
|
||||
"damage_ratio": "Коэф. урона",
|
||||
"damage_taken": "Получено урона",
|
||||
"capture_points": "Точки ограничения"
|
||||
"capture_points": "Ограничительные точки"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1168,7 +1141,7 @@
|
||||
"last_updated": "Последнее обновление %s1",
|
||||
"search": {
|
||||
"hint": "Поиск аватаров...",
|
||||
"results": "Аватаров: %s1"
|
||||
"results": "%s1 аватар"
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
@@ -1177,7 +1150,7 @@
|
||||
"description": "Статистика сеанса игры",
|
||||
"seo_description": "Отслеживайте свою статистику в реальном времени во время игры World of Tanks Blitz",
|
||||
"search": {
|
||||
"lookup": "Введите ник игрока",
|
||||
"lookup": "Искать игрока, чтобы начать",
|
||||
"hint": "Поиск игроков...",
|
||||
"searching": "Поиск...",
|
||||
"no_results": "Игроков не найдено"
|
||||
@@ -1218,7 +1191,7 @@
|
||||
"button": "Создать",
|
||||
"description": "Встраиваемые элементы для стримов",
|
||||
"seo_description": "Создать вставки для стримов по World of Tanks Blitz с невероятной кастомизации",
|
||||
"coming_soon": "В скором времени появятся новые встраиваемые элементы.",
|
||||
"coming_soon": "Скоро появятся новые встраиваемые элементы.",
|
||||
"suggest": "Предложить идеи.",
|
||||
"preview": {
|
||||
"title": "Предпросмотр",
|
||||
@@ -1327,11 +1300,17 @@
|
||||
"description": "Начните с BlitzKit",
|
||||
"seo_description": "Начните с BlitzKit"
|
||||
},
|
||||
"api": {
|
||||
"name": "API",
|
||||
"button": "To the docs",
|
||||
"description": "BlitzKit hosted API",
|
||||
"seo_description": "BlitzKit hosted static API for World of Tanks Blitz"
|
||||
"blitz_analysis": {
|
||||
"name": "BlitzAnalysis[]",
|
||||
"button": "Анализируй",
|
||||
"description": "Богатая статистика",
|
||||
"seo_description": "Богатые статистика по World of Tanks Blitz"
|
||||
},
|
||||
"blitz_insider": {
|
||||
"name": "Blitz Insider",
|
||||
"button": "Открой для себя",
|
||||
"description": "Статистика сеанса игры",
|
||||
"seo_description": "Статистика сеанса игры в World of Tanks Blitz"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"fox": "Швидка бура лисиця перестрибує через лінивого собаку."
|
||||
},
|
||||
"common": {
|
||||
"name": "BlitzKit",
|
||||
"units": {
|
||||
"m": "%s1м",
|
||||
"mm": "%s1мм",
|
||||
@@ -672,8 +671,6 @@
|
||||
"copied": "Скопійовано!"
|
||||
},
|
||||
"tank_search": {
|
||||
"select_dialog_title": "Search for tanks",
|
||||
"swap_dialog_title": "Swap tank",
|
||||
"clear_filters": "Clear filters",
|
||||
"search_bar_hint": "Шукайте техніку...",
|
||||
"recent": "Недавно переглянуті",
|
||||
@@ -742,14 +739,6 @@
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"pwa": {
|
||||
"title": "Install the BlitzKit App",
|
||||
"new": "NEW",
|
||||
"checking": "Checking compatibility...",
|
||||
"instructions": "Instructions for Apple devices.",
|
||||
"install": "Install",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"discord": {
|
||||
"join_short": "Join cool people!",
|
||||
"join_long": "Join super duper cool people!",
|
||||
@@ -775,14 +764,6 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"name": "Guess the tank",
|
||||
"button": "Guess away",
|
||||
@@ -806,9 +787,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -832,12 +810,6 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "Ghost Factory",
|
||||
"dead_rail": "Dead Rail"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -851,7 +823,9 @@
|
||||
"green_penetration": "Зелене пробиття",
|
||||
"hide_model_under_armor": "Hide model under armor",
|
||||
"opaque": "Opaque",
|
||||
"dev_wireframe": "DEV: wireframe"
|
||||
"dev_wireframe": "DEV: wireframe",
|
||||
"environment": "Environment",
|
||||
"show_grid": "Show grid"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "Завантажити",
|
||||
@@ -938,6 +912,7 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "Review videos",
|
||||
"disclaimer": "BlitzKit does not endorse any review videos.",
|
||||
"join": "Apply to %s1 YouTuber program"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -998,8 +973,8 @@
|
||||
"dpmEffective": "Effective at 60s",
|
||||
"shells": "Снаряди",
|
||||
"mostOptimalShellIndex": "Most optimal shell",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "Час перезаряджання",
|
||||
"shellReloads": "Shell reloads",
|
||||
"shellReload": "Shell reload",
|
||||
"intraClip": "Intra-clip",
|
||||
"caliber": "Калібр",
|
||||
"penetration": "Бронепробиття",
|
||||
@@ -1057,9 +1032,7 @@
|
||||
"shellRange": "Shell range",
|
||||
"shellCapacity": "Shell capacity",
|
||||
"gunType": "Тип гармати",
|
||||
"penetrationAt250m": "Penetration at 250m",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
"penetrationAt250m": "Penetration at 250m"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
@@ -1327,11 +1300,17 @@
|
||||
"description": "Get started with BlitzKit",
|
||||
"seo_description": "Get started with BlitzKit"
|
||||
},
|
||||
"api": {
|
||||
"name": "API",
|
||||
"button": "To the docs",
|
||||
"description": "BlitzKit hosted API",
|
||||
"seo_description": "BlitzKit hosted static API for World of Tanks Blitz"
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"fox": "褐色的狐狸迅速地跳过那只懒狗"
|
||||
},
|
||||
"common": {
|
||||
"name": "BlitzKit",
|
||||
"units": {
|
||||
"m": "%s1公尺",
|
||||
"mm": "%s1毫米",
|
||||
@@ -672,8 +671,6 @@
|
||||
"copied": "已拷贝!"
|
||||
},
|
||||
"tank_search": {
|
||||
"select_dialog_title": "Search for tanks",
|
||||
"swap_dialog_title": "Swap tank",
|
||||
"clear_filters": "清除筛选",
|
||||
"search_bar_hint": "搜寻战车……",
|
||||
"recent": "最近查看的",
|
||||
@@ -742,14 +739,6 @@
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"pwa": {
|
||||
"title": "Install the BlitzKit App",
|
||||
"new": "NEW",
|
||||
"checking": "Checking compatibility...",
|
||||
"instructions": "Instructions for Apple devices.",
|
||||
"install": "Install",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"discord": {
|
||||
"join_short": "加入超級酷的人群!",
|
||||
"join_long": "加入我們的Discord社群,和超酷的人一起聊天!",
|
||||
@@ -775,14 +764,6 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"name": "猜坦克",
|
||||
"button": "猜猜看",
|
||||
@@ -806,9 +787,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -832,12 +810,6 @@
|
||||
"percentage": "%s1%",
|
||||
"ghost_factory": "幽灵工厂",
|
||||
"dead_rail": "死亡铁道"
|
||||
},
|
||||
"flexibility": {
|
||||
"yaw": "Yaw",
|
||||
"pitch": "Pitch",
|
||||
"min": "Min",
|
||||
"max": "Max"
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
@@ -851,7 +823,9 @@
|
||||
"green_penetration": "可击穿区域",
|
||||
"hide_model_under_armor": "隐藏装甲下的模型",
|
||||
"opaque": "不透明",
|
||||
"dev_wireframe": "开发选项:网格"
|
||||
"dev_wireframe": "开发选项:网格",
|
||||
"environment": "环境",
|
||||
"show_grid": "显示网格"
|
||||
},
|
||||
"screenshot": {
|
||||
"download": "下载",
|
||||
@@ -938,6 +912,7 @@
|
||||
},
|
||||
"review": {
|
||||
"title": "回顾影片",
|
||||
"disclaimer": "BlitzKit 不支援任何影片回顾。",
|
||||
"join": "应用到 %s1 YouTuber程序"
|
||||
},
|
||||
"configuration": {
|
||||
@@ -998,8 +973,8 @@
|
||||
"dpmEffective": "60秒内生效",
|
||||
"shells": "炮弹 ",
|
||||
"mostOptimalShellIndex": "最优炮弹",
|
||||
"shellReloads": "Reloads",
|
||||
"shellReload": "装填时间",
|
||||
"shellReloads": "弹匣装填",
|
||||
"shellReload": "炮弹装填",
|
||||
"intraClip": "炮弹装填",
|
||||
"caliber": "口径",
|
||||
"penetration": "穿深",
|
||||
@@ -1057,9 +1032,7 @@
|
||||
"shellRange": "射程",
|
||||
"shellCapacity": "携弹量",
|
||||
"gunType": "炮管类型",
|
||||
"penetrationAt250m": "两百五十米处的穿透力",
|
||||
"burstShells": "Burst shell count",
|
||||
"burstInterShell": "Burst inter-shell"
|
||||
"penetrationAt250m": "两百五十米处的穿透力"
|
||||
}
|
||||
},
|
||||
"firepower": {
|
||||
@@ -1327,11 +1300,17 @@
|
||||
"description": "开始使用 BlitzKit",
|
||||
"seo_description": "开始使用 BlitzKit"
|
||||
},
|
||||
"api": {
|
||||
"name": "API",
|
||||
"button": "To the docs",
|
||||
"description": "BlitzKit hosted API",
|
||||
"seo_description": "BlitzKit hosted static API for World of Tanks Blitz"
|
||||
"blitz_analysis": {
|
||||
"name": "闪击战分析",
|
||||
"button": "立即分析",
|
||||
"description": "丰富的历史平均统计",
|
||||
"seo_description": "战车世界闪击战中丰富的历史平均统计"
|
||||
},
|
||||
"blitz_insider": {
|
||||
"name": "闪击战内嵌插件",
|
||||
"button": "开始探索",
|
||||
"description": "游戏内场次统计",
|
||||
"seo_description": "战车世界闪击战内的游戏内场次统计"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
|
||||
@@ -11,24 +11,6 @@
|
||||
"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.2.1",
|
||||
"@gltf-transform/extensions": "^4.2.1",
|
||||
"@gltf-transform/functions": "^4.2.1",
|
||||
"csv-parse": "^6.1.0",
|
||||
"decompress": "^4.2.1",
|
||||
"esbuild": "^0.25.10",
|
||||
"fast-xml-parser": "^5.2.5",
|
||||
"progress": "^2.0.3",
|
||||
"puppeteer": "^24.22.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/decompress": "^4.2.7",
|
||||
"@types/progress": "^2.0.7"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { BLITZKIT_TANK_ICON_SIZE, fetchTankDefinitions } from "@blitzkit/core";
|
||||
import { launch } from "puppeteer";
|
||||
import sharp from "sharp";
|
||||
import { commitAssets } from "../core/github/commitAssets";
|
||||
import { FileChange } from "../core/github/commitMultipleFiles";
|
||||
import { BLITZKIT_TANK_ICON_SIZE, fetchTankDefinitions } from '@blitzkit/core';
|
||||
import { launch } from 'puppeteer';
|
||||
import sharp from 'sharp';
|
||||
import { commitAssets } from '../core/github/commitAssets';
|
||||
import { FileChange } from '../core/github/commitMultipleFiles';
|
||||
|
||||
export async function blitzkitTankIcons() {
|
||||
console.log("Building blitzkit tank icons...");
|
||||
console.log('Building blitzkit tank icons...');
|
||||
|
||||
const browser = await launch();
|
||||
const page = await browser.newPage();
|
||||
@@ -21,7 +21,7 @@ export async function blitzkitTankIcons() {
|
||||
|
||||
try {
|
||||
await page.goto(`http://localhost:4321/api/tankopedia/tank-icon/${id}/`, {
|
||||
waitUntil: "networkidle0",
|
||||
waitUntil: 'networkidle0',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`${id} ${name} time out`, error);
|
||||
@@ -29,21 +29,21 @@ export async function blitzkitTankIcons() {
|
||||
}
|
||||
|
||||
const screenshot = await page.screenshot({
|
||||
type: "webp",
|
||||
type: 'webp',
|
||||
omitBackground: true,
|
||||
});
|
||||
const content = await sharp(screenshot).trim({ threshold: 100 }).toBuffer();
|
||||
|
||||
index++;
|
||||
console.log(
|
||||
`Rendered ${id} (${name}) ${((index / tanks.length) * 100).toFixed(2)}%`
|
||||
`Rendered ${id} (${name}) ${((index / tanks.length) * 100).toFixed(2)}%`,
|
||||
);
|
||||
|
||||
files.push({ path: `icons/tanks/big/${id}.webp`, content });
|
||||
files.push({ path: `icons/tanks/blitzkit/${id}.webp`, content });
|
||||
}
|
||||
|
||||
await page.close();
|
||||
await browser.close();
|
||||
|
||||
await commitAssets("tank icons", files);
|
||||
await commitAssets('tank icons', files);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -62,7 +62,6 @@ 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,52 +1,49 @@
|
||||
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";
|
||||
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';
|
||||
|
||||
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}`);
|
||||
|
||||
let index = 0;
|
||||
for (const [tankKey, tank] of entries) {
|
||||
console.log(` ${++index} / ${entries.length} : ${tankKey}`);
|
||||
|
||||
if (tankKey.includes("tutorial_bot")) continue;
|
||||
for (const [tankKey, tank] of Object.entries(tanks.root)) {
|
||||
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 });
|
||||
changes.push({
|
||||
path: `3d/tanks/models/${id}.glb`,
|
||||
content: Buffer.from(await nodeIO.writeBinary(model)),
|
||||
});
|
||||
}
|
||||
|
||||
await commitAssets(`tank models ${nation}`, changes);
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
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_1",
|
||||
[VertexAttribute.TEXCOORD2]: "TEXCOORD_2",
|
||||
[VertexAttribute.TEXCOORD3]: "TEXCOORD_3",
|
||||
[VertexAttribute.VERTEX]: 'POSITION',
|
||||
[VertexAttribute.NORMAL]: 'NORMAL',
|
||||
[VertexAttribute.TEXCOORD0]: 'TEXCOORD_0',
|
||||
[VertexAttribute.TEXCOORD1]: 'TEXCOORD_0',
|
||||
[VertexAttribute.TEXCOORD2]: 'TEXCOORD_0',
|
||||
[VertexAttribute.TEXCOORD3]: 'TEXCOORD_0',
|
||||
};
|
||||
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 { readBaseColor } from "../readBaseColor";
|
||||
import { readDVPLFile } from "../readDVPLFile";
|
||||
import { readNormal } from "../readNormal";
|
||||
import { readRoughnessMetallic } from "../readRoughnessMetallic";
|
||||
} 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';
|
||||
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,27 +66,26 @@ export async function extractModel(data: string, path: string) {
|
||||
material.setBaseColorTexture(
|
||||
document
|
||||
.createTexture(node.materialName)
|
||||
.setMimeType("image/jpeg")
|
||||
.setMimeType('image/jpeg')
|
||||
.setImage(
|
||||
await readBaseColor(
|
||||
`${data}/3d/${dirname(path)}/${textures.baseColorMap ?? textures.albedo}`,
|
||||
textures.miscMap
|
||||
? `${data}/3d/${dirname(path)}/${textures.miscMap}`
|
||||
: undefined
|
||||
)
|
||||
)
|
||||
await readTexture(
|
||||
`${data}/3d/${dirname(path)}/${textures.albedo}`,
|
||||
TextureMutation.Albedo,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (textures.baseRMMap) {
|
||||
material.setMetallicRoughnessTexture(
|
||||
document
|
||||
.createTexture(node.materialName)
|
||||
.setMimeType("image/jpeg")
|
||||
.setMimeType('image/jpeg')
|
||||
.setImage(
|
||||
await readRoughnessMetallic(
|
||||
`${data}/3d/${dirname(path)}/${textures.baseRMMap}`
|
||||
)
|
||||
)
|
||||
await readTexture(
|
||||
`${data}/3d/${dirname(path)}/${textures.baseRMMap}`,
|
||||
TextureMutation.RoughnessMetallicness,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -96,33 +95,47 @@ export async function extractModel(data: string, path: string) {
|
||||
material.setNormalTexture(
|
||||
document
|
||||
.createTexture(node.materialName)
|
||||
.setMimeType("image/jpeg")
|
||||
.setMimeType('image/jpeg')
|
||||
.setImage(
|
||||
await readNormal(
|
||||
await readTexture(
|
||||
`${data}/3d/${dirname(path)}/${
|
||||
textures.baseNormalMap ?? textures.normalmap
|
||||
}`,
|
||||
isBase
|
||||
)
|
||||
)
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -142,36 +155,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
|
||||
@@ -179,14 +192,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
|
||||
@@ -220,7 +233,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);
|
||||
|
||||
@@ -228,7 +241,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);
|
||||
@@ -240,24 +253,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());
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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();
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
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,83 +1,81 @@
|
||||
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) {
|
||||
const ddsTexturePath = path.replace(".tex", ".dx11.dds");
|
||||
export async function readTexture(path: string, mutation?: TextureMutation) {
|
||||
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();
|
||||
|
||||
return raw;
|
||||
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);
|
||||
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// return await sharp(raw.data, { raw }).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.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 newImage
|
||||
.joinChannel(await Promise.all([roughness, metallicness]))
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
// return await newImage
|
||||
// .joinChannel(await Promise.all([roughness, metallicness]))
|
||||
// .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();
|
||||
}
|
||||
|
||||
// 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();
|
||||
// }
|
||||
case TextureMutation.Albedo: {
|
||||
return await sharp(raw.data, { raw }).removeAlpha().jpeg().toBuffer();
|
||||
}
|
||||
|
||||
// case TextureMutation.Albedo: {
|
||||
// return await sharp(raw.data, { raw }).removeAlpha().jpeg().toBuffer();
|
||||
// }
|
||||
|
||||
// default:
|
||||
// return await sharp(raw.data, { raw }).jpeg().toBuffer();
|
||||
// }
|
||||
default:
|
||||
return await sharp(raw.data, { raw }).jpeg().toBuffer();
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,57 @@
|
||||
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",
|
||||
];
|
||||
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';
|
||||
|
||||
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 = '';
|
||||
}
|
||||
|
||||
for (const root of roots) {
|
||||
const filesRaw = await readdir(`${root}`);
|
||||
const files: string[] = [];
|
||||
// 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 file of filesRaw) {
|
||||
if (file.endsWith(".proto")) files.push(file);
|
||||
if (file.endsWith(".ts")) await rm(`${root}/${file}`);
|
||||
if (!file.endsWith('.ts')) continue;
|
||||
await rm(`${root}/${file}`);
|
||||
}
|
||||
|
||||
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_out=${root}`,
|
||||
const allFiles = filesRaw.filter(
|
||||
(file) => file.endsWith('.proto') && !file.startsWith('_'),
|
||||
);
|
||||
|
||||
for (const files of chunk(allFiles, 32)) {
|
||||
const args = [
|
||||
`-I=${root}`,
|
||||
].join(" ");
|
||||
`--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_out=${root}`,
|
||||
...files.map((file) => `${root}/${file}`),
|
||||
];
|
||||
|
||||
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 exec(`protoc ${args.join(' ')}`);
|
||||
}
|
||||
|
||||
await writeFile(
|
||||
`${root}/index.ts`,
|
||||
'// @ts-nocheck\n' +
|
||||
allFiles
|
||||
.sort()
|
||||
.map((file) => file.replace('.proto', ''))
|
||||
.map((file) => `export * from './${file}.ts';`)
|
||||
.join('\n'),
|
||||
);
|
||||
}
|
||||
@@ -1,26 +1,29 @@
|
||||
import { type TranslationTree } from "@blitzkit/i18n";
|
||||
import locales from "@blitzkit/i18n/locales.json";
|
||||
import { writeFile } from "fs/promises";
|
||||
import {
|
||||
DEFAULT_LOCALE,
|
||||
SUPPORTED_LOCALES,
|
||||
TranslationTree,
|
||||
} from '@blitzkit/i18n';
|
||||
import { writeFile } from 'fs/promises';
|
||||
|
||||
const ROOT = "@blitzkit/i18n/strings";
|
||||
const WORKING_ROOT = "../../packages/i18n/strings";
|
||||
const ROOT = '@blitzkit/i18n/strings';
|
||||
const WORKING_ROOT = '../../packages/i18n/strings';
|
||||
|
||||
const defaultStrings = require(`${ROOT}/${locales.default}.json`).default;
|
||||
const nonDefaultLocales = locales.supported.filter(
|
||||
({ locale }) => locale !== locales.default
|
||||
const defaultStrings = require(`${ROOT}/${DEFAULT_LOCALE}.json`).default;
|
||||
const nonDefaultLocales = SUPPORTED_LOCALES.filter(
|
||||
(locale) => locale !== DEFAULT_LOCALE,
|
||||
);
|
||||
|
||||
function prune(defaultStrings: TranslationTree, strings: TranslationTree) {
|
||||
for (const key in strings) {
|
||||
if (key in defaultStrings) {
|
||||
if (typeof defaultStrings[key] === typeof strings[key]) {
|
||||
if (typeof strings[key] === "string") {
|
||||
if (typeof strings[key] === 'string') {
|
||||
if (
|
||||
// are equivalent regardless of %s flavor
|
||||
(defaultStrings[key] as string).replaceAll(/%s\d/g, "%s") ===
|
||||
(strings[key] as string).replaceAll(/%s\d/g, "%s") ||
|
||||
(defaultStrings[key] as string).replaceAll(/%s\d/g, '%s') ===
|
||||
(strings[key] as string).replaceAll(/%s\d/g, '%s') ||
|
||||
// string is empty
|
||||
strings[key] === "" ||
|
||||
strings[key] === '' ||
|
||||
// string has old flavor of %s
|
||||
strings[key].match(/%s([^\d]|$)/g) !== null
|
||||
) {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src/**/*"]
|
||||
"include": ["src/**/*"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "../../"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "varuna",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"dependencies": {
|
||||
"react": "^19.1.1"
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
import { produce } from "immer";
|
||||
import { debounce, 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 dehydrated = localStorage.getItem(this.persistence);
|
||||
|
||||
if (dehydrated) {
|
||||
const rehydrated = JSON.parse(dehydrated) as unknown;
|
||||
data = merge(data, rehydrated);
|
||||
}
|
||||
|
||||
window.addEventListener("beforeunload", this.store.bind(this));
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
this._initial = data;
|
||||
this._state = data;
|
||||
}
|
||||
|
||||
useInitialization(...args: Arguments[]) {
|
||||
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;
|
||||
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();
|
||||
this.storeDeferred();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
store() {
|
||||
localStorage.setItem(this.persistence!, JSON.stringify(this.state));
|
||||
}
|
||||
|
||||
storeDeferred = debounce(this.store.bind(this), 500);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# 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/
|
||||
@@ -0,0 +1,47 @@
|
||||
# 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).
|
||||
@@ -0,0 +1,14 @@
|
||||
// @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',
|
||||
});
|
||||
Loaded 100 of 436 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user