Compare commits

..
58 Commits
Author SHA1 Message Date
TrèsAbhi 1558b69931 Merge branch 'main' of https://github.com/blitzkit/blitzkit 2025-08-27 17:19:45 -05:00
TrèsAbhi a560a3d3ae feat: logs 2025-08-27 17:19:40 -05:00
TrèsAbhi b620110d0f New Crowdin updates (#167)
* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Japanese)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)
2025-08-27 11:36:57 -05:00
TrèsAbhi 23ea72bf40 Merge branch 'dev' 2025-08-26 11:29:09 -05:00
TrèsAbhi aaee0b7965 feat: servicing 2025-08-26 11:28:57 -05:00
TrèsAbhi 347523f9e7 feat: burst dpm 2025-08-22 13:38:02 -05:00
TrèsAbhi 3205630a0f feat: recoil 2025-08-22 12:44:34 -05:00
TrèsAbhi 8446e3fbbd feat: flashes 2025-08-22 12:40:26 -05:00
TrèsAbhi 6d25a97fc8 fix: dynamic sizing 2025-08-22 12:23:57 -05:00
TrèsAbhi bdbfce0e18 feat: auto reloader compatibility 2025-08-22 12:21:04 -05:00
TrèsAbhi 8033e8f7cc feat: burst compatiblity 2025-08-21 20:22:12 -05:00
TrèsAbhi f98c801d6c New translations en.json (Russian) (#163) 2025-08-21 13:45:37 -05:00
TrèsAbhi 339663adaf Merge branch 'main' into dev 2025-08-20 11:07:50 -05:00
TrèsAbhi 2b15357dee New Crowdin updates (#162)
* New translations en.json (French)

* New translations en.json (Spanish)

* New translations en.json (Japanese)

* New translations en.json (Polish)

* New translations en.json (Portuguese)

* New translations en.json (Russian)

* New translations en.json (Ukrainian)

* New translations en.json (Chinese Simplified)

* New translations en.json (Russian)
2025-08-19 19:02:36 -05:00
TrèsAbhi 1e05717daf fix: remove logs 2025-08-18 12:07:02 -05:00
TrèsAbhi c20f8c5040 feat: burst data 2025-08-18 12:06:14 -05:00
TrèsAbhi f22f4eeccd Update website.yml 2025-08-18 11:56:41 -05:00
TrèsAbhi 79c450798c feat: logs 2025-08-18 11:28:16 -05:00
TrèsAbhi d247e67690 fix: link the whole frame 2025-08-18 11:21:16 -05:00
TrèsAbhi 69b2035cfc fix: module damage no longer scales with assault shells 2025-08-18 11:14:46 -05:00
TrèsAbhi 62fed63310 fix: max width docs images 2025-08-18 11:11:35 -05:00
TrèsAbhi 7c29046390 fix: doubled range 2025-08-18 11:05:10 -05:00
TrèsAbhi b65c3c1d93 feat: some recoloring 2025-08-18 10:54:15 -05:00
TrèsAbhi 87f5d8fe8f Merge branch 'main' into dev 2025-08-18 10:51:38 -05:00
TrèsAbhi 525b9377cc feat: optimize info lines 2025-08-12 13:48:10 -05:00
TrèsAbhi 94cba0c9ba feat: remove reactive pitch and yaw 2025-08-12 13:43:11 -05:00
TrèsAbhi 4ddf77fc7f fix: invalidate 2025-08-12 13:20:29 -05:00
TrèsAbhi ae6dc259dc feat: rename things 2025-08-12 13:17:07 -05:00
TrèsAbhi c2e95f6ef6 feat: hide ReloadVisualizer 2025-08-12 13:11:59 -05:00
TrèsAbhi 868812e578 feat adjust coloring 2025-08-12 13:11:34 -05:00
TrèsAbhi b5064f9e8b fix: touch action 2025-08-12 12:59:46 -05:00
TrèsAbhi a0eb0d54f0 feat: syncronize tank model 2025-08-12 12:52:57 -05:00
TrèsAbhi e47b1315a9 feat: hull rotate 2025-08-12 12:45:23 -05:00
TrèsAbhi cde6904513 feat: yaw support 2025-08-11 11:34:51 -05:00
TrèsAbhi 997d1df8f9 fix: auto rotate disabling 2025-08-11 11:26:53 -05:00
TrèsAbhi 9f15ded0c9 feat: optimize rendering 2025-08-11 11:25:25 -05:00
TrèsAbhi b2ee97b3d4 feat: active camera positioner 2025-08-11 11:17:26 -05:00
TrèsAbhi 03d819a6c1 feat: dog 2025-08-11 11:08:47 -05:00
TrèsAbhi 8ff26d57f6 feat: some model 2025-08-10 20:36:48 -05:00
TrèsAbhi c243ebd19a feat: embedded canvas 2025-08-10 14:03:13 -05:00
TrèsAbhi 40f5042896 feat: cursor 2025-08-09 13:30:53 -05:00
TrèsAbhi 21130b29f8 fix: 90 / 2 2025-08-09 12:39:20 -05:00
TrèsAbhi d50b054243 fix: unintended space before cross 2025-08-09 12:32:37 -05:00
TrèsAbhi 6b7f8049fe feat: elevation 2025-08-09 12:31:36 -05:00
TrèsAbhi e6029157aa feat: allow scrolling on hero section 2025-08-09 12:07:26 -05:00
TrèsAbhi ba656eb603 fix: remove dead code 2025-08-09 11:59:47 -05:00
TrèsAbhi e8dd26b2ba feat: moved shadow down 2025-08-09 11:52:10 -05:00
TrèsAbhi 897da9479d feat: transition and continous 2025-08-08 12:29:21 -05:00
TrèsAbhi 291395718f feat: yaw 2025-08-08 12:09:16 -05:00
TrèsAbhi 42e860b171 feat: start of gun depression visualzier 2025-08-08 12:00:33 -05:00
TrèsAbhi 3b6d339310 feat: compact info lines 2025-08-08 11:03:20 -05:00
TrèsAbhi 728fd5b824 feat: remove dpm counter 2025-08-08 11:01:26 -05:00
TrèsAbhi 34217b2754 feat: more progress 2025-08-08 10:57:39 -05:00
TrèsAbhi 0b5c88531f feat: guidesblitz 2025-08-07 20:24:19 -05:00
TrèsAbhi e145d7c6e8 Merge branch 'main' into dev 2025-08-07 14:11:24 -05:00
TrèsAbhi 86f4712b87 feat: subtler advanced highlighting 2025-08-06 12:33:50 -05:00
TrèsAbhi ea90ca21b1 feat: remove shooting range 2025-08-06 12:23:57 -05:00
TrèsAbhi b3046d8655 feat: enable reload visualizer 2025-08-06 12:16:14 -05:00
218 changed files with 3141 additions and 7189 deletions

No files matched your search

-3
View File
@@ -1,3 +0,0 @@
{
"indentSize": 2
}
+153
View File
@@ -0,0 +1,153 @@
name: Build Assets
on:
push:
branches: main
workflow_dispatch:
inputs:
cdn_branch:
type: string
required: false
default: main
description: What CDN branch to write to
zip_url:
type: string
required: false
description: ZIP URL to download instead of Steam client
zip_root:
type: string
required: false
description: Root dir of ZIP which houses the executable and Data dir
schedule:
- cron: '0 2 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.cdn_branch }}
cancel-in-progress: true
env:
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
WOTB_DLC_CDN: ${{ secrets.WOTB_DLC_CDN }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_REPO: blitzkit/assets
PUBLIC_ASSET_BRANCH: main
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: true
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
PUBLIC_PATREON_CLIENT_ID: ${{ secrets.PUBLIC_PATREON_CLIENT_ID }}
PUBLIC_PATREON_REDIRECT_URI: ${{ secrets.PUBLIC_PATREON_REDIRECT_URI }}
PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }}
PUBLIC_NITROPAY_ID: ${{ secrets.PUBLIC_NITROPAY_ID }}
PUBLIC_DEBUG_MISSING_I18N: false
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_FAST_USER: blitzkit
PUBLIC_ASSET_FAST_REPO: assets
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
jobs:
download_client:
name: Download client
runs-on: ubuntu-latest
steps:
- name: (ZIP) Maximize build space
if: github.event.inputs.zip_url != ''
uses: easimon/maximize-build-space@master
with:
root-reserve-mb: 512
swap-size-mb: 1024
remove-dotnet: 'true'
- uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.GH_TOKEN }}
- name: (Steam) Setup bun
uses: oven-sh/setup-bun@v1
if: github.event.inputs.zip_url == ''
- name: (Steam) Install dependencies
if: github.event.inputs.zip_url == ''
run: bun install
- name: (Steam) Setup DepotDownloader
if: github.event.inputs.zip_url == ''
run: bun run setup:depotdownloader
- name: (Steam) Download client
if: github.event.inputs.zip_url == ''
run: |
chmod +x ./temp/depotDownloader/DepotDownloader
./temp/depotDownloader/DepotDownloader -q -os windows -app 444200 -username ${{ secrets.DEPOT_DOWNLOADER_USERNAME }} -password "${{ secrets.DEPOT_DOWNLOADER_PASSWORD }}"
- name: (Steam) Apply patches
if: github.event.inputs.zip_url == ''
run: bun setup:patches --depot
- name: (ZIP) Download client
if: github.event.inputs.zip_url != ''
env:
ZIP_URL: ${{ github.event.inputs.zip_url }}
ZIP_ROOT: ${{ github.event.inputs.zip_root }}
run: |
wget -q -O client.zip $ZIP_URL
unzip -q client.zip -d client
rm client.zip
mkdir -p depots/444202/0
mv client/$ZIP_ROOT/* depots/444202/0
- name: Archive depots
uses: actions/upload-artifact@v4
with:
name: depots
path: depots
build_assets:
name: Build assets
runs-on: ubuntu-latest
needs: download_client
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.GH_TOKEN }}
- name: Setup bun
uses: oven-sh/setup-bun@v1
- name: Install dependencies
run: bun install
- name: Lint
run: bun run lint:website
- name: Download depots
uses: actions/download-artifact@v4
with:
name: depots
path: depots
- name: AppArmor hack
run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
- name: Start server and build assets
run: |
bun dev:website --verbosepa &
SERVER_PID=$!
bun run build:assets --all-targets --depot --production
kill $SERVER_PID || true
+49
View File
@@ -0,0 +1,49 @@
name: Averages
on:
workflow_dispatch:
schedule:
- cron: '0 8 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: 'true'
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- name: Install dependencies
run: bun install
- name: Build averages
env:
PUBLIC_ASSET_BRANCH: main
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
run: bun run build:averages --production
+32
View File
@@ -0,0 +1,32 @@
name: Discover IDs
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 1'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- name: Install dependencies
run: bun install
- name: Discover IDs
run: bun build:ids --production
+41
View File
@@ -0,0 +1,41 @@
name: Rating Asia
on:
workflow_dispatch:
schedule:
- cron: '0 17 * * *'
jobs:
host:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- name: Install dependencies
run: bun install
- name: Generate leaderboard
env:
PUBLIC_ASSET_BRANCH: main
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: 'true'
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
run: bun build:ratingleaderboard --region=asia
+41
View File
@@ -0,0 +1,41 @@
name: Rating Europe
on:
workflow_dispatch:
schedule:
- cron: '0 23 * * *'
jobs:
host:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- name: Install dependencies
run: bun install
- name: Generate leaderboard
env:
PUBLIC_ASSET_BRANCH: main
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: 'true'
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
run: bun build:ratingleaderboard --region=eu
+41
View File
@@ -0,0 +1,41 @@
name: Rating North America
on:
workflow_dispatch:
schedule:
- cron: '0 5 * * *'
jobs:
host:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- name: Install dependencies
run: bun install
- name: Generate leaderboard
env:
PUBLIC_ASSET_BRANCH: main
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: 'true'
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
run: bun build:ratingleaderboard --region=com
+20 -20
View File
@@ -17,34 +17,33 @@ concurrency:
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
env:
AUTO_PROXY_ORIGIN: ${{ secrets.AUTO_PROXY_ORIGIN }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: ${{ secrets.ALLOW_ROBOTS }}
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
PUBLIC_GUIDE_WEBSITE: ${{ secrets.PUBLIC_GUIDE_WEBSITE }}
SERVICING: ${{ secrets.SERVICING }}
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.GH_TOKEN }}
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: latest
- name: Configure pages
uses: actions/configure-pages@v5
- uses: oven-sh/setup-bun@v1
- uses: actions/configure-pages@v5
- name: Install dependencies
run: bun install
@@ -58,12 +57,13 @@ jobs:
path: dist/website
deploy-github-pages:
if: ${{ github.ref == 'refs/heads/main' }}
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
needs: build
if: >
(github.repository == 'tresabhi/blitzkit' && github.ref == 'refs/heads/main') ||
(github.repository == 'blitzkit/host-redux' && github.ref == 'refs/heads/ue4')
environment:
name: Production
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
+3 -226
View File
@@ -22,232 +22,9 @@ depots
public/sitemap*
/firebase.json
target
# packages/core/src/protos/*.ts
/.vercel
/.astro
.vercel
/packages/bot/src/prisma
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
*.userprefs
mono_crash.*
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
[Aa][Rr][Mm]64[Ee][Cc]/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
.vs/
Generated\ Files/
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*.VisualState.xml
TestResult.xml
nunit-*.xml
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
BenchmarkDotNet.Artifacts/
project.lock.json
project.fragment.lock.json
artifacts/
ScaffoldingReadMe.txt
StyleCopReport.xml
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.idb
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
!Directory.Build.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
_Chutzpah*
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
*.psess
*.vsp
*.vspx
*.sap
*.e2e
$tf/
*.gpState
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
_TeamCity*
*.dotCover
.axoCover/*
!.axoCover/settings.json
coverage*.json
coverage*.xml
coverage*.info
*.coverage
*.coveragexml
_NCrunch_*
.NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
*.mm.*
AutoTest.Net/
.sass-cache/
[Ee]xpress/
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
publish/
*.[Pp]ublish.xml
*.azurePubxml
*.pubxml
*.publishproj
PublishScripts/
*.nupkg
*.snupkg
*.nuget.props
*.nuget.targets
csx/
*.build.csdef
ecf/
rcf/
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
*.[Cc]ache
!?*.[Cc]ache/
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
Generated_Code/
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
*.mdf
*.ldf
*.ndf
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
FakesAssemblies/
*.GhostDoc.xml
.ntvs_analysis.dat
node_modules/
*.plg
*.opt
*.vbw
*.vbp
*.dsw
*.dsp
*.ncb
*.aps
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
.paket/paket.exe
paket-files/
.fake/
.cr/personal
__pycache__/
*.pyc
*.tss
*.jmconfig
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
OpenCover/
ASALocalRun/
*.binlog
.aws-sam
*.nvuser
.mfractor/
.localhistory/
.vshistory/
healthchecksdb
MigrationBackup/
.ionide/
FodyWeavers.xsd
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
.history/
*.cab
*.msi
*.msix
*.msm
*.msp
*.sln.iml
/src
/packages/bot/src/prisma
+2 -7
View File
@@ -51,7 +51,6 @@
"CUBEMAP",
"CUBETEXCOORD",
"customperiod",
"Datacard",
"datasource",
"Dava",
"dbid",
@@ -69,7 +68,6 @@
"DIDS",
"donotremove",
"drei",
"dsapp",
"dvpd",
"dvpl",
"DVPLCRC",
@@ -131,7 +129,6 @@
"noout",
"normalmap",
"oneof",
"oodle",
"oopsies",
"Oopsy",
"opentest",
@@ -196,8 +193,6 @@
"Turretless",
"TYPELESS",
"ubjson",
"Uncompiled",
"undiscriminated",
"Unicum",
"UNLOCALIZED",
"UNORM",
@@ -227,12 +222,12 @@
"yolo",
"youtuber",
"youtubers",
"zoomable",
"zustand"
],
"deno.enable": false,
"deno.unstable": false,
"cSpell.enableFiletypes": ["glsl", "proto3"],
"typescript.tsdk": "node_modules\\typescript\\lib",
"liveServer.settings.multiRootWorkspaceName": "blitzkit",
"git.ignoreLimitWarning": true
"liveServer.settings.multiRootWorkspaceName": "blitzkit"
}
-41
View File
@@ -1,41 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/BlitzKit.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/BlitzKit.sln",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"--project",
"${workspaceFolder}/BlitzKit.sln"
],
"problemMatcher": "$msCompile"
}
]
}
-27
View File
@@ -1,27 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F23F76D4-C350-4BBA-9DF1-E67C5CD391B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlitzKit.FFI", "src\BlitzKit.FFI\BlitzKit.FFI.csproj", "{8E798C66-C863-4A67-87EA-853109EEE037}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8E798C66-C863-4A67-87EA-853109EEE037}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8E798C66-C863-4A67-87EA-853109EEE037}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E798C66-C863-4A67-87EA-853109EEE037}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E798C66-C863-4A67-87EA-853109EEE037}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{8E798C66-C863-4A67-87EA-853109EEE037} = {F23F76D4-C350-4BBA-9DF1-E67C5CD391B1}
EndGlobalSection
EndGlobal
-9
View File
@@ -11,12 +11,3 @@ Your premier source for all things Blitz.
Go to [application directory for BlitzKit](https://discord.com/application-directory/1097673957865443370) and click "Add to Server." Voila! You're ready to go.
![BlitzKit application directory](https://i.imgur.com/YWCtOuJ.png)
## Naming Guide
| Type | Style | Example |
| --------- | -------------- | ---------------------------------------------------------------- |
| In-text | CamelCase | BlitzKit |
| Code | lowerCamelCase | `let blitzkit = { ... }`, `function fetchBlitzkitData() { ... }` |
| URLs | kebab-case | `https://example.com/blitzkit/` |
| Constants | SNAKE_CASE | `const BLITZKIT_VERSION = "1.0.0"` |
-96
View File
@@ -1,96 +0,0 @@
{
"name": "blitzkit",
"private": true,
"version": "1.27.11",
"description": "🎉 Tools for everything World of Tanks Blitz",
"type": "module",
"types": "./types.d.ts",
"workspaces": ["packages/*", "submodules/*"],
"scripts": {
"build:assets": "cd packages/scripts && bun run build:assets",
"build:averages": "bun --filter=@blitzkit/scripts run build:averages",
"build:bot": "cd packages/scripts && bun run build:bot",
"build:website": "bun build:prisma --no-hints && cd packages/website && bun run build",
"build:prisma": "prisma generate",
"build:sitemap": "bun scripts/sitemap.ts",
"build:docs": "vitepress build docs",
"build:ratingleaderboard": "cd packages/scripts && bun run build:ratingleaderboard",
"dev:bot": "bun run build:bot --dev && bun start:bot",
"start:bot": "bun dist/bot/main.js",
"dev:docs": "vitepress dev docs",
"dev:website": "cd packages/website-ue && bun run dev",
"dev:api": "bun --filter=@blitzkit/api run dev",
"setup:depotdownloader": "bun --filter=@blitzkit/scripts run setup:depotdownloader",
"setup:patches": "bun --filter=@blitzkit/scripts run setup:patches",
"start:website": "bun --filter=@blitzkit/website run start",
"host:docs": "vitepress preview docs",
"lint": "tsc",
"lint:website": "cd packages/website && bun run lint",
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"",
"update": "yarn upgrade --latest",
"tunnel": "cloudflared tunnel --url http://localhost:3000",
"discoverids": "scripts/discoverIds.ts",
"build:protos": "bun packages/scripts/src/protos.ts",
"preview:website": "cd packages/website && bun run preview",
"build:reviews": "cd packages/scripts && bun build:reviews",
"build:ids": "cd packages/scripts && bun build:ids",
"prunestrings": "cd packages/scripts && bun prunestrings"
},
"license": "MIT",
"dependencies": {
"satori": "^0.10.14",
"@resvg/resvg-js": "^2.6.2",
"discord.js": "^14.16.3",
"bepaint": "^0.1.0-beta.3",
"markdown-escape": "^2.0.0",
"@types/markdown-escape": "^1.1.3",
"@types/adm-zip": "^0.5.5",
"@types/lz4js": "^0.2.1",
"@octokit/plugin-throttling": "^8.2.0",
"@octokit/rest": "^20.1.1",
"@prisma/client": "^6.6.0",
"adm-zip": "^0.5.16",
"crc": "^4.3.2",
"dotenv": "^16.4.5",
"lz4js": "^0.2.0",
"yaml": "^2.5.0",
"ts-deepmerge": "^7.0.2",
"@types/decompress": "^4.2.7",
"@types/progress": "^2.0.7",
"@gltf-transform/core": "^4.0.10",
"@gltf-transform/extensions": "^4.0.10",
"@gltf-transform/functions": "^4.0.10",
"csv-parse": "^5.5.6",
"decompress": "^4.2.1",
"esbuild": "^0.19.12",
"fast-xml-parser": "^4.4.1",
"progress": "^2.0.3",
"puppeteer": "^23.10.4",
"@astrojs/check": "^0.9.4",
"@playform/compress": "^0.1.9",
"@playform/inline": "^0.1.2",
"@types/js-cookie": "^3.0.6",
"astro-compressor": "^1.0.0",
"@fontsource/inter": "^5.2.5",
"@nanostores/persistent": "^0.10.2",
"@nanostores/react": "^0.7.3",
"@nivo/bar": "^0.87.0",
"@nivo/line": "^0.88.0",
"compare-versions": "^6.1.1",
"echarts": "^5.5.1",
"js-cookie": "^3.0.5",
"markdown-to-jsx": "^7.5.0",
"nanostores": "^0.11.3",
"react-device-detect": "^2.2.3",
"react-promise-suspense": "^0.3.4",
"urlon": "^3.1.0",
"@types/bun": "latest",
"@types/node": "^22.14.1",
"prisma": "^6.6.0",
"typescript": "^5.8.3",
"googleapis": "^144.0.0",
"protobufjs": "^7.5.2",
"quicklime": "^1.2.0",
"sharp": "^0.33.5"
}
}
-124
View File
@@ -1,124 +0,0 @@
{
"name": "blitzkit",
"private": true,
"version": "1.27.11",
"description": "🎉 Tools for everything World of Tanks Blitz",
"type": "module",
"types": "./types.d.ts",
"workspaces": [
"packages/*",
"submodules/*"
],
"scripts": {
"build:assets": "cd packages/scripts && bun run build:assets",
"build:averages": "bun --filter=@blitzkit/scripts run build:averages",
"build:bot": "cd packages/scripts && bun run build:bot",
"build:website": "bun build:prisma --no-hints && cd packages/website && bun run build",
"build:prisma": "prisma generate",
"build:sitemap": "bun scripts/sitemap.ts",
"build:docs": "vitepress build docs",
"build:ratingleaderboard": "cd packages/scripts && bun run build:ratingleaderboard",
"dev:bot": "bun run build:bot --dev && bun start:bot",
"start:bot": "bun dist/bot/main.js",
"dev:docs": "vitepress dev docs",
"dev:website": "cd packages/website-ue && bun run dev",
"dev:api": "bun --filter=@blitzkit/api run dev",
"setup:depotdownloader": "bun --filter=@blitzkit/scripts run setup:depotdownloader",
"setup:patches": "bun --filter=@blitzkit/scripts run setup:patches",
"start:website": "bun --filter=@blitzkit/website run start",
"host:docs": "vitepress preview docs",
"lint": "tsc",
"lint:website": "cd packages/website && bun run lint",
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"",
"update": "yarn upgrade --latest",
"tunnel": "cloudflared tunnel --url http://localhost:3000",
"discoverids": "scripts/discoverIds.ts",
"build:protos": "bun packages/scripts/src/protos.ts",
"preview:website": "cd packages/website && bun run preview",
"build:reviews": "cd packages/scripts && bun build:reviews",
"build:ids": "cd packages/scripts && bun build:ids",
"prunestrings": "cd packages/scripts && bun prunestrings"
},
"license": "MIT",
"dependencies": {
"satori": "^0.10.14",
"@resvg/resvg-js": "^2.6.2",
"discord.js": "^14.16.3",
"fuzzysort": "^2.0.4",
"bepaint": "^0.1.0-beta.3",
"markdown-escape": "^2.0.0",
"@types/markdown-escape": "^1.1.3",
"@types/adm-zip": "^0.5.5",
"@types/lz4js": "^0.2.1",
"@octokit/plugin-throttling": "^8.2.0",
"@octokit/rest": "^20.1.1",
"@prisma/client": "^6.6.0",
"adm-zip": "^0.5.16",
"crc": "^4.3.2",
"dotenv": "^16.4.5",
"lz4js": "^0.2.0",
"ts-proto": "^2.2.2",
"yaml": "^2.5.0",
"ts-deepmerge": "^7.0.2",
"@types/decompress": "^4.2.7",
"@types/progress": "^2.0.7",
"@gltf-transform/core": "^4.0.10",
"@gltf-transform/extensions": "^4.0.10",
"@gltf-transform/functions": "^4.0.10",
"csv-parse": "^5.5.6",
"decompress": "^4.2.1",
"esbuild": "^0.19.12",
"fast-xml-parser": "^4.4.1",
"progress": "^2.0.3",
"puppeteer": "^23.10.4",
"@astrojs/check": "^0.9.4",
"@astrojs/sitemap": "^3.2.1",
"@playform/compress": "^0.1.9",
"@playform/inline": "^0.1.2",
"@types/js-cookie": "^3.0.6",
"@types/three": "^0.160.0",
"astro-compressor": "^1.0.0",
"@astrojs/partytown": "^2.1.4",
"@astrolib/seo": "^1.0.0-beta.8",
"@fontsource/inter": "^5.2.5",
"@nanostores/persistent": "^0.10.2",
"@nanostores/react": "^0.7.3",
"@nivo/bar": "^0.87.0",
"@nivo/line": "^0.88.0",
"@radix-ui/colors": "^3.0.0",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/themes": "^3.1.4",
"@react-three/drei": "^9.115.0",
"@react-three/fiber": "^8.17.10",
"compare-versions": "^6.1.1",
"echarts": "^5.5.1",
"immer": "^10.1.1",
"js-cookie": "^3.0.5",
"markdown-to-jsx": "^7.5.0",
"nanostores": "^0.11.3",
"react-device-detect": "^2.2.3",
"react-promise-suspense": "^0.3.4",
"three": "^0.169.0",
"urlon": "^3.1.0",
"zustand": "^4.5.5",
"@types/bun": "latest",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.14.1",
"prettier": "^3.5.3",
"prisma": "^6.6.0",
"typescript": "^5.8.3",
"googleapis": "^144.0.0",
"lodash-es": "^4.17.21",
"protobufjs": "^7.5.2",
"quicklime": "^1.2.0",
"sharp": "^0.33.5",
"@blitzkit/core": "workspace:*",
"@blitzkit/i18n": "workspace:*",
"@astrojs/react": "^4.3.0",
"@types/react": "^19.1.5",
"@types/react-dom": "^19.1.5",
"astro": "^5.8.0",
"react": "^19.1.0",
"react-dom": "^19.1.0"
}
}
Regular → Executable
BIN
View File
Binary file not shown.
+25
View File
@@ -0,0 +1,25 @@
# BlitzKit 1.27.16
## Features
- Added links to GuidesBlitz to Tankopedia to aid users understand tanks beyond just numbers and stats
- Added a gun depression and yaw visualizer
## Changes
- Made the colors of the advanced highlighting mode more subtle
- Made the Tankopedia stat lines more condensed
- You can now scroll the page even with your cursor on the Tankopedia sandbox
## Fixes
- Fixed a bug where the range of increased gun depression was incorrectly doubled
- Removed an unintended space before the "x" in the "Gun damaged" stat
- Fixed an issue causing the 3D renderer to render frames even when off screen
- Fixed a differential issue causing excessive rerendering of the stat lines
- Images no longer exceed the width of the device in the docs
- Assault shells no longer scale the module damage with distance
## Technical Changes
- Removed all code relating to the canceled "Shooting Range" mode
+18
View File
@@ -0,0 +1,18 @@
# BlitzKit 1.27.17
This update was released early for the website. Today, it'll be officially announced and released for the bot
## Features
- Added the shell reload visualizer to aid people in getting a feel for how long shell reloads really are
- Added lines of stats for the burst shells
## Changes
- Removed the redundant word "Shell" from "Shell reload" and "Shell reloads"
- Removed the penetration over distance slider
## Fixes
- Fixed the DPM of tanks with burst shells like the ATAC
- The DPM will still be different from the game due to a bug in World of Tanks Blitz; I am trying to get Wargaming to fix it; trust BlitzKit!
+19 -11
View File
@@ -1,19 +1,18 @@
{
"name": "blitzkit",
"private": true,
"version": "1.27.15",
"version": "1.27.17",
"description": "🎉 Tools for everything World of Tanks Blitz",
"type": "module",
"types": "./types.d.ts",
"workspaces": [
"packages/*",
"submodules/*"
"packages/*"
],
"scripts": {
"build:assets": "cd packages/scripts && bun run build:assets",
"build:averages": "bun --filter=@blitzkit/scripts run build:averages",
"build:bot": "cd packages/scripts && bun run build:bot",
"build:website": "cd packages/website-ue && bun run build",
"build:website": "bun build:prisma --no-hints && cd packages/website && bun run build",
"build:prisma": "prisma generate",
"build:sitemap": "bun scripts/sitemap.ts",
"build:docs": "vitepress build docs",
@@ -31,21 +30,30 @@
"lint": "tsc",
"lint:website": "cd packages/website && bun run lint",
"format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"",
"update": "yarn upgace --latest",
"update": "yarn upgrade --latest",
"tunnel": "cloudflared tunnel --url http://localhost:3000",
"discoverids": "scripts/discoverIds.ts",
"build:protos": "bun packages/scripts/src/protos.ts",
"preview:website": "cd packages/website && bun run preview",
"build:reviews": "cd packages/scripts && bun build:reviews",
"build:ids": "cd packages/scripts && bun build:ids",
"prunestrings": "cd packages/scripts && bun prunestrings",
"build:ffi": "bun build:windows:ffi",
"build:windows:ffi": "dotnet publish src/BlitzKit.FFI -r win-x64 /p:PublishAot=true",
"build:linux:ffi": "dotnet publish src/BlitzKit.FFI -r linux-x64 /p:PublishAot=true"
"prunestrings": "cd packages/scripts && bun prunestrings"
},
"license": "MIT",
"dependencies": {
"@types/bun": "^1.2.15",
"prettier": "^3.5.3"
"cheerio": "^1.1.2",
"googleapis": "^144.0.0",
"lodash-es": "^4.17.21",
"protobufjs": "^7.5.2",
"quicklime": "^1.2.0",
"sharp": "^0.33.5"
},
"devDependencies": {
"@types/bun": "latest",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.14.1",
"prettier": "^3.5.3",
"prisma": "^6.6.0",
"typescript": "^5.8.3"
}
}
+16
View File
@@ -3,8 +3,24 @@
"private": true,
"type": "module",
"types": "src/types.d.ts",
"dependencies": {
"@blitzkit/core": "workspace:*",
"@blitzkit/i18n": "workspace:*",
"satori": "^0.10.14",
"@resvg/resvg-js": "^2.6.2",
"discord.js": "^14.16.3",
"react": "^18.3.1",
"@radix-ui/colors": "^3.0.0",
"fuzzysort": "^2.0.4",
"bepaint": "^0.1.0-beta.3",
"dotenv": "^16.4.5",
"markdown-escape": "^2.0.0"
},
"scripts": {
"dev": "bun --watch src/main.ts",
"start": "bun src/main.ts"
},
"devDependencies": {
"@types/markdown-escape": "^1.1.3"
}
}
+3 -8
View File
@@ -197,15 +197,10 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
turret.guns.forEach((gun) => {
if (
gun.gun_type!.value.base.research_cost !== undefined &&
typeof gun.gun_type!.value.base.research_cost
.research_cost_type!.value === 'number'
gun.research_cost !== undefined &&
typeof gun.research_cost.research_cost_type!.value === 'number'
) {
gunXps.set(
gun.gun_type!.value.base.id,
gun.gun_type!.value.base.research_cost.research_cost_type!
.value,
);
gunXps.set(gun.id, gun.research_cost.research_cost_type!.value);
}
});
});
+1 -8
View File
@@ -1,11 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": [
"src/**/*",
"../core/src/blitzkit/supportedLocales.ts",
"../i18n/src/unwrapper.ts"
],
"compilerOptions": {
"baseUrl": "../../"
}
"include": ["src/**/*", "../core/src/blitzkit/supportedLocales.ts", "../i18n/src/unwrapper.ts"]
}
+14 -3
View File
@@ -4,9 +4,20 @@
"type": "module",
"main": "src/index.ts",
"dependencies": {
"@types/js-yaml": "^4.0.9",
"js-yaml": "^4.1.0",
"@octokit/plugin-throttling": "^8.2.0",
"@octokit/rest": "^20.1.1",
"@prisma/client": "^6.6.0",
"adm-zip": "^0.5.16",
"crc": "^4.3.2",
"dotenv": "^16.4.5",
"lz4js": "^0.2.0",
"pickleparser": "^0.2.1",
"protobufjs": "^7.4.0",
"ts-proto": "^2.2.2",
"@blitzkit/closed": "workspace:*"
"yaml": "^2.5.0"
},
"devDependencies": {
"@types/adm-zip": "^0.5.5",
"@types/lz4js": "^0.2.1"
}
}
@@ -1,99 +0,0 @@
import { CatalogItem } from '@protos/blitz_items';
import { Any } from '@protos/google/protobuf/any';
import { lowerFirst } from 'lodash-es';
import { compareUint8Arrays } from '../../../scripts/src/core/github/compareUint8Arrays';
import { MessageFns } from '../protos';
export class CatalogItemAccessor {
public id: string;
components: Record<string, Any> = {};
count: number;
constructor(private item: CatalogItem) {
this.id = item.catalog_id;
this.count = item.components.length;
for (const component of item.components) {
if (
component.value === undefined ||
component.value.component === undefined
) {
continue;
}
this.components[component.key] = component.value.component;
}
}
undiscriminatedId() {
return this.id.split('.').slice(1).join('.');
}
static fromComponent(id: string, component: Any) {
return this.fromComponents(id, [component]);
}
static fromComponents(id: string, components: Any[]) {
return new CatalogItemAccessor({
catalog_id: id,
components: components.map((component) => ({
key: lowerFirst(
component.type_url.slice(2).split('.').slice(1).join('.'),
),
value: { type_url: component.type_url, component },
})),
});
}
addComponent(key: string, value: Any) {
this.components[key] = value;
return this;
}
getOptional<Type>(Message: MessageFns<Type>, component: string) {
if (component in this.components) {
return Message.decode(this.components[component].value);
}
}
get<Type>(Message: MessageFns<Type>, component: string) {
const value = this.getOptional(Message, component);
if (value === undefined) throw new Error(`Unknown component: ${component}`);
return value;
}
pack() {
const item: CatalogItem = { catalog_id: this.id, components: [] };
for (const componentId in this.components) {
const component = this.components[componentId];
item.components.push({
key: componentId,
value: { type_url: component.type_url, component },
});
}
return item;
}
encode() {
return CatalogItem.encode(this.pack()).finish();
}
equals(other: CatalogItemAccessor) {
return (
this.count === other.count &&
this.item.components.every(
(component) =>
component.value === undefined ||
(component.key in other.components &&
(component.value.component === undefined ||
compareUint8Arrays(
other.components[component.key].value,
component.value.component.value,
))),
)
);
}
}
-1
View File
@@ -18,7 +18,6 @@ export * from './getTankStats';
export * from './idToRegion';
export * from './isOnGoingRatingSeason';
export * from './isValidBlitzId';
export * from './metadata';
export * from './nations';
export * from './normalizeExtra';
export * from './normalizeIds';
@@ -1,89 +0,0 @@
/*
export class SocketMetadataAccessor {
items: Record<string, CatalogItemAccessor> = {};
constructor(catalogItems: CatalogItem[]) {
for (const item of catalogItems) {
if (item.catalog_id in this.items) {
throw new Error(`Duplicate catalog ID: ${item.catalog_id}`);
}
this.items[item.catalog_id] = new CatalogItemAccessor(item);
}
}
get(item: string) {
if (item in this.items) return this.items[item];
throw new Error(`Unknown catalog ID: ${item}`);
}
add(item: CatalogItemAccessor) {
this.items[item.id] = item;
return this;
}
getAllTanks() {
return this.get('CatalogItemsGroupEntity.all_tanks').get(
CatalogItemsComponent,
'catalogItemsGroupComponent',
);
}
injectBlitzkit() {
this.injectBlitzKitTankComponents();
this.injectBlitzKitAllTanksEntity();
return this;
}
injectBlitzKitTankComponents() {
for (const item of this.getAllTanks().catalog_items) {
const tank = this.get(item);
const value = BlitzKitTankCatalogComponent.encode({
id: 'TEST',
set: 'TEST',
}).finish();
tank.addComponent('blitzkitTankCatalogComponent', {
type_url:
'./blitzkit_static_tank_component.BlitzKitTankCatalogComponent',
value,
});
}
}
injectBlitzKitAllTanksEntity() {
const allTanksEntities: BlitzkitAllTanksComponent = { tanks: [] };
for (const item of this.getAllTanks().catalog_items) {
allTanksEntities.tanks.push(this.get(item).pack());
}
this.add(
CatalogItemAccessor.fromComponent(
'BlitzKitAllTanksEntity.blitzkit_all_tanks',
{
type_url:
'./blitzkit_static_all_tanks_component.BlitzkitAllTanksComponent',
value: BlitzkitAllTanksComponent.encode(allTanksEntities).finish(),
},
),
);
}
}
export class RemoteMetadataAccessor {
constructor(private base: string) {}
async get(item: string) {
const response = await fetch(`${this.base}/${item}.pb`);
if (!response.ok) throw new Error(`Unknown catalog ID: ${item}`);
const buffer = await response.arrayBuffer();
const catalogItem = CatalogItem.decode(new Uint8Array(buffer));
return new CatalogItemAccessor(catalogItem);
}
}
*/
-131
View File
@@ -1,131 +0,0 @@
import { CatalogItem } from '@protos/blitz_items';
import { Metadata } from '@protos/blitz_metadata';
import { LocalizationResourcesComponent } from '@protos/blitz_static_localization_resources_component';
import { RemoteStorageComponent } from '@protos/blitz_static_remote_storage_component';
import { load } from 'js-yaml';
import { minimatch } from 'minimatch';
import { CatalogItemAccessor } from 'packages/core/src/blitz/catalogItemAccessor';
interface LocalizationConfig {
namespaces: string[];
}
export class MetadataAccessor {
items: Record<string, CatalogItem> = {};
constructor(metadata: Metadata) {
for (const item of metadata.catalog_items) {
this.items[item.catalog_id] = item;
}
}
get(item: string) {
if (item in this.items) return new CatalogItemAccessor(this.items[item]);
throw new Error(`Item ${item} does not exist in socket metadata`);
}
add(item: CatalogItemAccessor) {
this.items[item.id] = item.pack();
}
filter(predicate: (item: string) => boolean) {
const items: CatalogItemAccessor[] = [];
for (const item in this.items) {
if (predicate(item)) {
items.push(new CatalogItemAccessor(this.items[item]));
}
}
return items;
}
async strings(locale: string) {
const clientConfig = await this.get(
'ClientConfigsEntity.default_client_config',
);
const localizationResources = clientConfig.get(
LocalizationResourcesComponent,
'localizationResourcesComponent',
);
if (!localizationResources.remote_storage) {
throw new Error('Localization resources not found');
}
const { relative_path } = localizationResources.remote_storage;
let remoteStorage: RemoteStorageComponent | undefined = undefined;
for (const candidateId of localizationResources.remote_storage.production) {
const item = await this.get(candidateId);
const candidate = item.get(
RemoteStorageComponent,
'remoteStorageComponent',
);
if (
remoteStorage === undefined ||
candidate.download_speed_weight > remoteStorage.download_speed_weight
) {
remoteStorage = candidate;
}
}
if (remoteStorage === undefined) {
throw new Error('No suitable production remote storage found');
}
const config = await fetch(
`${remoteStorage.url}/${relative_path}/config.yaml`,
)
.then((response) => response.text())
.then((text) => load(text) as LocalizationConfig);
const strings: Record<string, string> = {};
for (const namespace of config.namespaces) {
Object.assign(
strings,
await fetch(
`${remoteStorage.url}/${relative_path}/${namespace}/${locale}.yaml`,
)
.then((response) => response.text())
.then((text) => {
const strings: Record<string, string> = {};
const parsed = load(text) as Record<string, string>;
for (const key in parsed) {
strings[key] = parsed[key].replaceAll('\\"', '"');
}
return strings;
}),
);
}
return strings;
}
async stringsPartial(locale: string, patterns: string[]) {
const strings = await this.strings(locale);
const filtered: Record<string, string> = {};
for (const key in strings) {
if (patterns.some((pattern) => minimatch(key, pattern))) {
filtered[key] = strings[key];
}
}
return filtered;
}
toArray() {
return Object.values(this.items);
}
toSortedArray() {
return this.toArray().sort((a, b) =>
a.catalog_id.localeCompare(b.catalog_id),
);
}
}
@@ -1,8 +0,0 @@
import { BlitzKitAvatars } from '@protos/blitzkit';
import { MetadataAccessor } from '../../blitz/metadata';
export abstract class BlitzkitAPI {
abstract metadata: MetadataAccessor;
abstract avatars(): Promise<BlitzKitAvatars>;
}
-15
View File
@@ -1,15 +0,0 @@
import { BlitzKitAvatars } from '@protos/blitzkit';
import { MetadataAccessor } from '../../blitz/metadata';
import { BlitzkitAPI } from './abstract';
export class BlitzkitClientAPI extends BlitzkitAPI {
get metadata(): MetadataAccessor {
throw new Error('Cannot access metadata from client');
}
async avatars() {
const response = await fetch('/api/internal/avatars.pb');
const buffer = await response.arrayBuffer();
return BlitzKitAvatars.decode(new Uint8Array(buffer));
}
}
-3
View File
@@ -1,3 +0,0 @@
export * from './abstract';
export * from './client';
export * from './server';
-37
View File
@@ -1,37 +0,0 @@
import { ProfileAvatarComponent } from '@protos/blitz_static_profile_avatar_component';
import { SellableComponent } from '@protos/blitz_static_sellable_component';
import { StuffUIComponent } from '@protos/blitz_static_stuff_ui_component';
import { BlitzKitAvatars } from '@protos/blitzkit';
import { BlitzkitAPI } from '.';
import { MetadataAccessor } from '../../blitz/metadata';
export class BlitzkitServerAPI extends BlitzkitAPI {
constructor(public metadata: MetadataAccessor) {
super();
}
async avatars() {
const avatars: BlitzKitAvatars = { avatars: [] };
for (const item of this.metadata.filter((item) =>
item.startsWith('ProfileAvatarEntity.'),
)) {
const stuff = item.get(StuffUIComponent, 'UIComponent');
const avatar = item.get(ProfileAvatarComponent, 'profileAvatarComponent');
const sellable = item.getOptional(SellableComponent, 'sellableComponent');
avatars.avatars.push({
id: item.undiscriminatedId(),
name: stuff.display_name,
obtaining: stuff.obtaining_methods,
description: stuff.description,
grade: stuff.grade,
category: avatar.category,
hidden_if_not_obtained: avatar.hidden_if_not_obtained,
sale: sellable?.reward,
});
}
return avatars;
}
}
-1
View File
@@ -41,7 +41,6 @@ export * from './uniqueGuns';
export * from './unwrapBlitzkitResponse';
export * from './votes';
// export * from './withCORSProxy';
export * from './api';
export * from './createDefaultProvisions';
export * from './gallery';
export * from './metaSortTank';
+39 -27
View File
@@ -1,6 +1,6 @@
import {
GunDefinition,
GunDefinitionAutoReloaderProperties,
GunDefinitionAutoReloader,
ShellDefinition,
} from '../protos';
@@ -15,34 +15,46 @@ export function resolveDpm(
let dps: number;
if (gun.gun_type!.$case === 'regular') {
dps = alpha / (reloadCoefficient * gun.gun_type!.value.extension.reload);
dps = alpha / (reloadCoefficient * gun.gun_type!.value.reload);
} else if (gun.gun_type!.$case === 'auto_loader') {
dps =
(alpha * gun.gun_type!.value.extension.shell_count) /
(gun.gun_type!.value.extension.clip_reload * reloadCoefficient +
(gun.gun_type!.value.extension.shell_count - 1) *
gun.gun_type!.value.extension.intra_clip *
intraClipCoefficient);
} else {
const mostOptimalShell =
gun.gun_type!.value.extension.shell_reloads.reduce<null | {
index: number;
reload: number;
}>((current, reloadRaw, index) => {
const reload =
reloadRaw * reloadCoefficient +
(index > 0
? (
gun.gun_type!.value
.extension as GunDefinitionAutoReloaderProperties
).intra_clip * intraClipCoefficient
: 0);
const damage = alpha * gun.gun_type!.value.shell_count;
let time = gun.gun_type!.value.clip_reload * reloadCoefficient;
if (current === null || reload < current.reload) {
return { index, reload };
}
return current;
}, null)!;
if (gun.burst) {
time +=
(gun.gun_type!.value.shell_count / gun.burst.count - 1) *
gun.gun_type!.value.intra_clip *
intraClipCoefficient;
time +=
(gun.gun_type!.value.shell_count / gun.burst.count) *
(gun.burst.count - 1) *
gun.burst.interval;
} else {
time +=
(gun.gun_type!.value.shell_count - 1) *
gun.gun_type!.value.intra_clip *
intraClipCoefficient;
}
dps = damage / time;
} else {
const mostOptimalShell = gun.gun_type!.value.shell_reloads.reduce<null | {
index: number;
reload: number;
}>((current, reloadRaw, index) => {
const reload =
reloadRaw * reloadCoefficient +
(index > 0
? (gun.gun_type!.value as GunDefinitionAutoReloader).intra_clip *
intraClipCoefficient
: 0);
if (current === null || reload < current.reload) {
return { index, reload };
}
return current;
}, null)!;
dps = alpha / mostOptimalShell?.reload;
}
+7 -9
View File
@@ -1,5 +1,5 @@
import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
import { deburr } from 'lodash-es';
import { SUPPORTED_LOCALES } from '../../../i18n/src/strings';
import { I18nString } from '../protos';
import { fetchCamouflageDefinitions } from './camouflageDefinitions';
import { fetchTankDefinitions } from './tankDefinitions';
@@ -26,8 +26,8 @@ export async function fetchTankNames() {
searchableNameDeburr,
camouflages: tank.camouflages
?.map((id) =>
locales.supported.map(
({ locale }) =>
SUPPORTED_LOCALES.map(
(locale) =>
camouflageDefinitions.camouflages[id]?.name.locales[locale],
),
)
@@ -42,11 +42,9 @@ export async function fetchTankNames() {
}
export const SEARCH_KEYS = [
...locales.supported
.map(({ locale }) => [
`searchableName.locales.${locale}`,
`searchableNameDeburr.locales.${locale}`,
])
.flat(),
...SUPPORTED_LOCALES.map((locale) => [
`searchableName.locales.${locale}`,
`searchableNameDeburr.locales.${locale}`,
]).flat(),
'camouflages',
];
+2 -2
View File
@@ -7,8 +7,8 @@ export function uniqueGuns(turrets: TurretDefinition[]) {
forEachRight(turrets, (turret) => {
forEachRight(turret.guns, (gun) => {
if (!gunIds.has(gun.gun_type!.value.base.id)) {
gunIds.add(gun.gun_type!.value.base.id);
if (!gunIds.has(gun.id)) {
gunIds.add(gun.id);
guns.push({ turret, gun });
}
});
+22 -33
View File
@@ -108,58 +108,47 @@ message GunDefinition {
GunDefinitionAutoLoader auto_loader = 2;
GunDefinitionAutoReloader auto_reloader = 3;
}
required uint32 id = 4;
required float rotation_speed = 5;
optional ResearchCost research_cost = 6;
required uint32 weight = 7;
required I18nString name = 8;
required uint32 tier = 9;
repeated ShellDefinition shells = 10;
required float camouflage_loss = 11;
required float aim_time = 12;
required float dispersion_base = 13;
required float dispersion_traverse = 14;
required float dispersion_shot = 15;
required float dispersion_damaged = 16;
repeated Unlock unlocks = 17;
required uint32 shell_capacity = 18;
optional AssaultRanges assault_ranges = 19;
optional Burst burst = 20;
}
message GunDefinitionRegular {
required GunDefinitionBase base = 1;
required GunDefinitionRegularProperties extension = 2;
}
message GunDefinitionRegularProperties {
required float reload = 1;
}
message GunDefinitionAutoLoader {
required GunDefinitionBase base = 1;
required GunDefinitionAutoLoaderProperties extension = 2;
}
message GunDefinitionAutoLoaderProperties {
required float clip_reload = 1;
required float intra_clip = 2;
required float shell_count = 3;
}
message GunDefinitionAutoReloader {
required GunDefinitionBase base = 1;
required GunDefinitionAutoReloaderProperties extension = 2;
message Burst {
required uint32 count = 1;
required float interval = 2;
}
message GunDefinitionAutoReloaderProperties {
message GunDefinitionAutoReloader {
repeated float shell_reloads = 1;
required float intra_clip = 2;
required float shell_count = 3;
}
message GunDefinitionBase {
required uint32 id = 1;
required float rotation_speed = 2;
optional ResearchCost research_cost = 3;
required uint32 weight = 4;
required I18nString name = 5;
required uint32 tier = 6;
repeated ShellDefinition shells = 7;
required float camouflage_loss = 8;
required float aim_time = 9;
required float dispersion_base = 10;
required float dispersion_traverse = 11;
required float dispersion_shot = 12;
required float dispersion_damaged = 13;
repeated Unlock unlocks = 14;
required uint32 shell_capacity = 15;
optional AssaultRanges assault_ranges = 16;
}
message AssaultRanges {
repeated AssaultRange ranges = 1;
repeated ShellType types = 2;
File diff suppressed because it is too large. Load diff
+1 -4
View File
@@ -1,7 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*"],
"compilerOptions": {
"baseUrl": "../../"
}
"include": ["src/**/*", "../i18n/src/strings.ts"]
}
+5 -1
View File
@@ -2,5 +2,9 @@
"name": "@blitzkit/i18n",
"private": true,
"type": "module",
"main": "src/index.ts"
"main": "src/index.ts",
"dependencies": {
"ts-deepmerge": "^7.0.2"
},
"devDependencies": {}
}
+86 -13
View File
@@ -1,8 +1,89 @@
import type en from '../strings/en.json';
import { merge } from 'ts-deepmerge';
import en from '../strings/en.json';
import es from '../strings/es.json';
import fr from '../strings/fr.json';
import ja from '../strings/ja.json';
import pl from '../strings/pl.json';
import pt from '../strings/pt.json';
import ru from '../strings/ru.json';
import uk from '../strings/uk.json';
import zh from '../strings/zh.json';
export type BlitzKitStrings = typeof en;
export const SUPPORTED_LOCALE_FLAGS: Record<string, string> = {
const stringsRaw = { en, es, fr, ja, pl, pt, ru, uk, zh };
export const SUPPORTED_LOCALES = Object.keys(stringsRaw) as SupportedLocale[];
export const DEFAULT_LOCALE = SUPPORTED_LOCALES[0];
function redact(string: string) {
const matches = [...string.matchAll(/%s\d+/g)];
const characters = string
.split('')
.map((char) => (char === ' ' ? ' ' : '█')) as string[];
matches.forEach((match) => {
const start = match.index;
const matchText = match[0];
for (let i = 0; i < matchText.length; i++) {
characters[start + i] = matchText[i];
}
});
return characters.join('');
}
function nuke(object: TranslationTree) {
for (const key in object) {
const typedKey = key as keyof typeof object;
if (typeof object[typedKey] === 'string') {
const value = object[typedKey];
object[typedKey] = redact(value as string);
} else nuke(object[typedKey] as TranslationTree);
}
}
const stringsPartial: Partial<Record<SupportedLocale, TranslationTree>> = {};
for (const locale in stringsRaw) {
if (locale === DEFAULT_LOCALE) {
stringsPartial[DEFAULT_LOCALE] = stringsRaw[DEFAULT_LOCALE];
continue;
}
const localeStrings = stringsRaw[locale as SupportedLocale];
stringsPartial[locale as SupportedLocale] = merge(
stringsRaw[DEFAULT_LOCALE],
localeStrings,
);
if (import.meta.env.PUBLIC_DEBUG_MISSING_I18N === 'true') {
nuke(stringsPartial[locale as SupportedLocale]!);
}
}
export const STRINGS = stringsPartial as Record<
SupportedLocale,
BlitzKitStrings
>;
export type SupportedLocale = keyof typeof stringsRaw;
export const SUPPORTED_LOCALE_BLITZ_MAP: Record<SupportedLocale, string> = {
en: 'en',
es: 'es',
fr: 'fr',
ja: 'ja',
pt: 'pt',
ru: 'ru',
uk: 'uk',
zh: 'zh-Hans',
pl: 'pl',
};
export const SUPPORTED_LOCALE_FLAGS: Record<SupportedLocale, string> = {
en: '🇺🇸',
es: '🇪🇸',
fr: '🇫🇷',
@@ -14,14 +95,6 @@ export const SUPPORTED_LOCALE_FLAGS: Record<string, string> = {
pl: '🇵🇱',
};
export const LOCALE_NAMES: Record<string, string> = {
en: 'English',
es: 'Español',
fr: 'Français',
ja: '日本語',
pt: 'Português',
ru: 'Русский',
uk: 'Українська',
zh: '中文',
pl: 'Polski',
};
export type TranslationTree = { [key: string]: TranslationNode };
export type TranslationNode = string | TranslationTree;
+2 -2
View File
@@ -1,5 +1,5 @@
import { I18nString } from '@blitzkit/core';
import locales from '../locales.json' with { type: 'json' };
import { DEFAULT_LOCALE } from './strings';
export function unwrapper(locale: string) {
return function (i18nString: I18nString) {
@@ -7,6 +7,6 @@ export function unwrapper(locale: string) {
return i18nString.locales[locale];
}
return i18nString.locales[locales.default];
return i18nString.locales[DEFAULT_LOCALE];
};
}
+18 -42
View File
@@ -6,14 +6,6 @@
},
"common": {
"grades": {
"0": "Unspecified grade",
"1": "Common",
"2": "Rare",
"3": "Epic",
"4": "Legendary"
},
"units": {
"m": "%s1m",
"mm": "%s1mm",
@@ -21,8 +13,7 @@
"hp": "%s1hp",
"x": "x%s1",
"percentage": "%s1%",
"deg_s": "%s1°/s",
"times": "x%s1"
"deg_s": "%s1°/s"
},
"gun_types": {
@@ -870,13 +861,6 @@
},
"tools": {
"internal": {
"name": "Internal",
"button": "Internal",
"description": "BlitzKit internal tools",
"seo_description": "BlitzKit internal tools"
},
"guess": {
"name": "Guess the tank",
"button": "Guess away",
@@ -929,6 +913,13 @@
"percentage": "%s1%",
"ghost_factory": "Ghost Factory",
"dead_rail": "Dead Rail"
},
"flexibility": {
"yaw": "Yaw",
"pitch": "Pitch",
"min": "Min",
"max": "Max"
}
},
@@ -1042,7 +1033,6 @@
"review": {
"title": "Review videos",
"disclaimer": "BlitzKit does not endorse any review videos.",
"join": "Apply to %s1 YouTuber program"
},
@@ -1106,8 +1096,8 @@
"dpmEffective": "Effective at 60s",
"shells": "Shells",
"mostOptimalShellIndex": "Most optimal shell",
"shellReloads": "Shell reloads",
"shellReload": "Shell reload",
"shellReloads": "Reloads",
"shellReload": "Reload",
"intraClip": "Intra-clip",
"caliber": "Caliber",
"penetration": "Penetration",
@@ -1165,7 +1155,9 @@
"shellRange": "Shell range",
"shellCapacity": "Shell capacity",
"gunType": "Gun type",
"penetrationAt250m": "Penetration at 250m"
"penetrationAt250m": "Penetration at 250m",
"burstShells": "Burst shell count",
"burstInterShell": "Burst inter-shell"
}
},
@@ -1273,33 +1265,17 @@
}
}
},
"avatars": {
"name": "Avatars",
"gallery": {
"name": "Gallery",
"button": "Browse all",
"seo_description": "All World of Tanks Blitz player profile avatars",
"description": "Player profile avatars",
"no_description": "This avatar has no description.",
"download": "Download",
"enlarge": "Enlarge",
"hidden": "Hidden",
"unnamed_avatars": "Unnamed avatars",
"description": "Blitz player avatars",
"seo_description": "All World of Tanks Blitz player avatars",
"last_updated": "Last updated on %s1",
"search": {
"hint": "Search avatars...",
"results": "%s1 avatars"
},
"categories": {
"0": "Unspecified",
"1": "Premium",
"2": "Special",
"3": "Accumulative",
"4": "Other",
"5": "Temporary"
}
},
"session": {
"name": "Session",
"button": "Start tracking",
+11 -4
View File
@@ -810,6 +810,12 @@
"percentage": "%s1%",
"ghost_factory": "Ghost Factory",
"dead_rail": "Dead Rail"
},
"flexibility": {
"yaw": "Yaw",
"pitch": "Pitch",
"min": "Min",
"max": "Max"
}
},
"sandbox": {
@@ -912,7 +918,6 @@
},
"review": {
"title": "Vídeos de reseña",
"disclaimer": "BlitzKit no respalda ningún vídeo de reseñas.",
"join": "Aplica para el programa de YouTube de %s1"
},
"configuration": {
@@ -973,8 +978,8 @@
"dpmEffective": "Effective at 60s",
"shells": "Proyectiles",
"mostOptimalShellIndex": "Proyectil más óptimo",
"shellReloads": "Tiempos de recarga",
"shellReload": "Tiempo de recarga",
"shellReloads": "Reloads",
"shellReload": "Recarga",
"intraClip": "Intra-clip",
"caliber": "Calibre",
"penetration": "Penetración",
@@ -1032,7 +1037,9 @@
"shellRange": "Rango del proyectil",
"shellCapacity": "Capacidad de proyectiles",
"gunType": "Tipo de cañón",
"penetrationAt250m": "Penetración a 250 m"
"penetrationAt250m": "Penetración a 250 m",
"burstShells": "Burst shell count",
"burstInterShell": "Burst inter-shell"
}
},
"firepower": {
+11 -4
View File
@@ -810,6 +810,12 @@
"percentage": "%s1%",
"ghost_factory": "Ghost Factory",
"dead_rail": "Dead Rail"
},
"flexibility": {
"yaw": "Yaw",
"pitch": "Pitch",
"min": "Min",
"max": "Max"
}
},
"sandbox": {
@@ -912,7 +918,6 @@
},
"review": {
"title": "Review videos",
"disclaimer": "BlitzKit does not endorse any review videos.",
"join": "Apply to %s1 YouTuber program"
},
"configuration": {
@@ -973,8 +978,8 @@
"dpmEffective": "Effective at 60s",
"shells": "Shells",
"mostOptimalShellIndex": "Most optimal shell",
"shellReloads": "Shell reloads",
"shellReload": "Shell reload",
"shellReloads": "Reloads",
"shellReload": "Reload",
"intraClip": "Intra-clip",
"caliber": "Caliber",
"penetration": "Penetration",
@@ -1032,7 +1037,9 @@
"shellRange": "Shell range",
"shellCapacity": "Shell capacity",
"gunType": "Gun type",
"penetrationAt250m": "Penetration at 250m"
"penetrationAt250m": "Penetration at 250m",
"burstShells": "Burst shell count",
"burstInterShell": "Burst inter-shell"
}
},
"firepower": {
+10 -3
View File
@@ -810,6 +810,12 @@
"percentage": "%s1%",
"ghost_factory": "Ghost Factory",
"dead_rail": "Dead Rail"
},
"flexibility": {
"yaw": "Yaw",
"pitch": "Pitch",
"min": "Min",
"max": "Max"
}
},
"sandbox": {
@@ -912,7 +918,6 @@
},
"review": {
"title": "Review videos",
"disclaimer": "BlitzKitはいかなるレビュー動画も後援していません。",
"join": "%s1 YouTuberプログラムに応募する"
},
"configuration": {
@@ -973,7 +978,7 @@
"dpmEffective": "60秒での有効値",
"shells": "砲弾",
"mostOptimalShellIndex": "最適な弾数",
"shellReloads": "自動装填時間",
"shellReloads": "Reloads",
"shellReload": "装填時間",
"intraClip": "砲弾再装填時間",
"caliber": "口径",
@@ -1032,7 +1037,9 @@
"shellRange": "射程",
"shellCapacity": "総弾数",
"gunType": "主砲タイプ",
"penetrationAt250m": "250m での貫通力"
"penetrationAt250m": "250m での貫通力",
"burstShells": "Burst shell count",
"burstInterShell": "Burst inter-shell"
}
},
"firepower": {
+11 -4
View File
@@ -810,6 +810,12 @@
"percentage": "%s1%",
"ghost_factory": "Ghost Factory",
"dead_rail": "Dead Rail"
},
"flexibility": {
"yaw": "Yaw",
"pitch": "Pitch",
"min": "Min",
"max": "Max"
}
},
"sandbox": {
@@ -912,7 +918,6 @@
},
"review": {
"title": "Review videos",
"disclaimer": "BlitzKit does not endorse any review videos.",
"join": "Apply to %s1 YouTuber program"
},
"configuration": {
@@ -973,8 +978,8 @@
"dpmEffective": "Effective at 60s",
"shells": "Shells",
"mostOptimalShellIndex": "Most optimal shell",
"shellReloads": "Shell reloads",
"shellReload": "Shell reload",
"shellReloads": "Reloads",
"shellReload": "Reload",
"intraClip": "Intra-clip",
"caliber": "Caliber",
"penetration": "Penetration",
@@ -1032,7 +1037,9 @@
"shellRange": "Shell range",
"shellCapacity": "Shell capacity",
"gunType": "Gun type",
"penetrationAt250m": "Penetration at 250m"
"penetrationAt250m": "Penetration at 250m",
"burstShells": "Burst shell count",
"burstInterShell": "Burst inter-shell"
}
},
"firepower": {
+11 -4
View File
@@ -810,6 +810,12 @@
"percentage": "%s1%",
"ghost_factory": "Ghost Factory",
"dead_rail": "Dead Rail"
},
"flexibility": {
"yaw": "Yaw",
"pitch": "Pitch",
"min": "Min",
"max": "Max"
}
},
"sandbox": {
@@ -912,7 +918,6 @@
},
"review": {
"title": "Vídeos de análise",
"disclaimer": "BlitzKit não aprova nenhum vídeo de análise.",
"join": "Candidate-se para %s1 um programa YouTuber"
},
"configuration": {
@@ -973,8 +978,8 @@
"dpmEffective": "Efetivo aos 60s",
"shells": "Projéteis",
"mostOptimalShellIndex": "Projétil mais ideal",
"shellReloads": "Recarga de projétil",
"shellReload": "Recarregamento de projétil",
"shellReloads": "Reloads",
"shellReload": "Recarga",
"intraClip": "Intra-clipe",
"caliber": "Calibre",
"penetration": "Penetração",
@@ -1032,7 +1037,9 @@
"shellRange": "Alcance de projétil",
"shellCapacity": "Capacidade de projéteis",
"gunType": "Tipo de arma",
"penetrationAt250m": "Penetração à 250m"
"penetrationAt250m": "Penetração à 250m",
"burstShells": "Burst shell count",
"burstInterShell": "Burst inter-shell"
}
},
"firepower": {
+12 -5
View File
@@ -788,7 +788,7 @@
"tank_seo_description": "%s1 броня, статистика, оборудование, снабжение и многое другое. %s2 УВМ, %s3hp альфа, %s4mm пробитие, %s5км/ч лучшая скорость, %s6hp здоровье.",
"visualizers": {
"assault": {
"title": "Assault shells"
"title": "Штурмовые снаряды"
},
"rotator": {
"hull": "Корпус",
@@ -810,6 +810,12 @@
"percentage": "%s1%",
"ghost_factory": "Промзона",
"dead_rail": "Эшелон"
},
"flexibility": {
"yaw": "Отклонение",
"pitch": "Наклон",
"min": "Вниз",
"max": "Вверх"
}
},
"sandbox": {
@@ -912,7 +918,6 @@
},
"review": {
"title": "Видеообзоры",
"disclaimer": "BlitzKit не несёт ответственности за видеообзоры.",
"join": "Предложить ютубера для программы %s1"
},
"configuration": {
@@ -973,13 +978,13 @@
"dpmEffective": "Эффективность за 60с",
"shells": "Снарядов в барабане",
"mostOptimalShellIndex": "Самый эффективный снаряд",
"shellReloads": "Перезарядка снарядов",
"shellReloads": "Reloads",
"shellReload": "Перезарядка",
"intraClip": "Между снарядами",
"caliber": "Калибр",
"penetration": "Пробитие",
"damage": "Урон",
"damageWithoutAssault": "Damage without assault shells",
"damageWithoutAssault": "Урон без штурмовых снарядов",
"clipDamage": "Урон с барабана",
"moduleDamage": "Урон по модулям",
"explosionRadius": "Разлет осколков",
@@ -1032,7 +1037,9 @@
"shellRange": "Дальность снаряда",
"shellCapacity": "Боезапас",
"gunType": "Тип орудия",
"penetrationAt250m": "Пробитие на 250м"
"penetrationAt250m": "Пробитие на 250м",
"burstShells": "Burst shell count",
"burstInterShell": "Burst inter-shell"
}
},
"firepower": {
+11 -4
View File
@@ -810,6 +810,12 @@
"percentage": "%s1%",
"ghost_factory": "Ghost Factory",
"dead_rail": "Dead Rail"
},
"flexibility": {
"yaw": "Yaw",
"pitch": "Pitch",
"min": "Min",
"max": "Max"
}
},
"sandbox": {
@@ -912,7 +918,6 @@
},
"review": {
"title": "Review videos",
"disclaimer": "BlitzKit does not endorse any review videos.",
"join": "Apply to %s1 YouTuber program"
},
"configuration": {
@@ -973,8 +978,8 @@
"dpmEffective": "Effective at 60s",
"shells": "Снаряди",
"mostOptimalShellIndex": "Most optimal shell",
"shellReloads": "Shell reloads",
"shellReload": "Shell reload",
"shellReloads": "Reloads",
"shellReload": "Час перезаряджання",
"intraClip": "Intra-clip",
"caliber": "Калібр",
"penetration": "Бронепробиття",
@@ -1032,7 +1037,9 @@
"shellRange": "Shell range",
"shellCapacity": "Shell capacity",
"gunType": "Тип гармати",
"penetrationAt250m": "Penetration at 250m"
"penetrationAt250m": "Penetration at 250m",
"burstShells": "Burst shell count",
"burstInterShell": "Burst inter-shell"
}
},
"firepower": {
+11 -4
View File
@@ -810,6 +810,12 @@
"percentage": "%s1%",
"ghost_factory": "幽灵工厂",
"dead_rail": "死亡铁道"
},
"flexibility": {
"yaw": "Yaw",
"pitch": "Pitch",
"min": "Min",
"max": "Max"
}
},
"sandbox": {
@@ -912,7 +918,6 @@
},
"review": {
"title": "回顾影片",
"disclaimer": "BlitzKit 不支援任何影片回顾。",
"join": "应用到 %s1 YouTuber程序"
},
"configuration": {
@@ -973,8 +978,8 @@
"dpmEffective": "60秒内生效",
"shells": "炮弹 ",
"mostOptimalShellIndex": "最优炮弹",
"shellReloads": "弹匣装填",
"shellReload": "炮弹装填",
"shellReloads": "Reloads",
"shellReload": "装填时间",
"intraClip": "炮弹装填",
"caliber": "口径",
"penetration": "穿深",
@@ -1032,7 +1037,9 @@
"shellRange": "射程",
"shellCapacity": "携弹量",
"gunType": "炮管类型",
"penetrationAt250m": "两百五十米处的穿透力"
"penetrationAt250m": "两百五十米处的穿透力",
"burstShells": "Burst shell count",
"burstInterShell": "Burst inter-shell"
}
},
"firepower": {
+17
View File
@@ -12,5 +12,22 @@
"build:bot": "bun src/buildBot.ts",
"build:ratingleaderboard": "bun src/buildRatingLeaderboard.ts",
"prunestrings": "bun src/pruneStrings.ts"
},
"dependencies": {
"@blitzkit/core": "workspace:*",
"@blitzkit/i18n": "workspace:*",
"@gltf-transform/core": "^4.0.10",
"@gltf-transform/extensions": "^4.0.10",
"@gltf-transform/functions": "^4.0.10",
"csv-parse": "^5.5.6",
"decompress": "^4.2.1",
"esbuild": "^0.19.12",
"fast-xml-parser": "^4.4.1",
"progress": "^2.0.3",
"puppeteer": "^23.10.4"
},
"devDependencies": {
"@types/decompress": "^4.2.7",
"@types/progress": "^2.0.7"
}
}
+20 -25
View File
@@ -12,7 +12,6 @@ import {
EquipmentSlot,
GameDefinitions,
GunDefinition,
GunDefinitionBase,
I18nString,
MapDefinitions,
ModelDefinitions,
@@ -274,6 +273,7 @@ interface VehicleDefinitions {
pumpGunMode?: boolean;
pumpGunReloadTimes?: string;
clip?: { count: number; rate: number };
burst?: { count: number; rate: number };
models: { undamaged: string };
extras?: {
trayShell?: {
@@ -1304,7 +1304,7 @@ export async function definitions() {
}
});
const base = {
tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({
id: gunId,
weight: gunListEntry.weight,
rotation_speed: gunListEntry.rotationSpeed,
@@ -1324,41 +1324,38 @@ export async function definitions() {
unlocks: resolveUnlocks(gun.unlocks),
shell_capacity: gun.maxAmmo ?? gunListEntry.maxAmmo,
assault_ranges,
} satisfies GunDefinitionBase;
tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({
burst:
gun.burst && gun.burst.count > 1
? {
count: gun.burst.count,
interval: 60 / gun.burst.rate,
}
: undefined,
gun_type:
gunType === 'regular'
? {
$case: 'regular',
value: {
base,
extension: { reload: gun.reloadTime },
reload: gun.reloadTime,
},
}
: gunType === 'autoReloader'
? {
$case: 'auto_reloader',
value: {
base,
extension: {
intra_clip: 60 / gun.clip!.rate,
shell_count: gunClipCount,
shell_reloads: gun
.pumpGunReloadTimes!.split(' ')
.map(Number),
},
intra_clip: 60 / gun.clip!.rate,
shell_count: gunClipCount,
shell_reloads: gun
.pumpGunReloadTimes!.split(' ')
.map(Number),
},
}
: {
$case: 'auto_loader',
value: {
base,
extension: {
intra_clip: 60 / gun.clip!.rate,
clip_reload: gun.reloadTime,
shell_count: gunClipCount,
},
intra_clip: 60 / gun.clip!.rate,
clip_reload: gun.reloadTime,
shell_count: gunClipCount,
},
},
} satisfies GunDefinition);
@@ -1407,7 +1404,7 @@ export async function definitions() {
tankDefinitions.tanks[tankId].turrets[turretIndex].guns[
gunIndex
].gun_type!.value.base!.shells.push({
].shells.push({
id: shellId,
name: shellName,
velocity: gunShellEntry.speed,
@@ -1495,9 +1492,7 @@ export async function definitions() {
turret.research_cost = turretXps.get(turret.id);
Object.values(turret.guns).forEach((gunRaw) => {
gunRaw.gun_type!.value.base!.research_cost = gunXps.get(
gunRaw.gun_type!.value.base!.id,
);
gunRaw.research_cost = gunXps.get(gunRaw.id);
});
});
@@ -62,6 +62,7 @@ export async function tankIcons() {
const nationVehicleId = tank.id;
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
const parameters = await readYAMLDVPL<TankParameters>(
`${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
);
@@ -1,7 +1,7 @@
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 { readdir } from 'fs/promises';
import { extractModel } from '../core/blitz/extractModel';
import { readXMLDVPL } from '../core/blitz/readXMLDVPL';
import { readYAMLDVPL } from '../core/blitz/readYAMLDVPL';
@@ -32,6 +32,7 @@ export async function tankModels() {
if (tankKey.includes('tutorial_bot')) continue;
const id = toUniqueId(nation, tank.id);
const parameters = await readYAMLDVPL<TankParameters>(
`${DATA}/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
);
+30 -40
View File
@@ -1,9 +1,10 @@
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 ROOT = 'packages/core/src/protos';
const exec = promisify(execSync);
let executableFileExtension: string;
@@ -14,44 +15,33 @@ if (existsSync('node_modules/.bin/protoc-gen-ts_proto.exe')) {
executableFileExtension = '';
}
// await compile('packages/core/src/protos');
await compile('submodules/blitzkit-closed/src/unreal/protos');
const filesRaw = await readdir(`${ROOT}`);
await Promise.all(
filesRaw.map(async (file) => {
if (!file.endsWith('.proto')) {
await rm(`${ROOT}/${file}`);
}
}),
);
async function compile(root: string) {
const filesRaw = await readdir(root);
const files = filesRaw.filter((file) => file.endsWith('.proto'));
const args = [
`--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=.',
...files.map((file) => `${ROOT}/${file}`),
];
for (const file of filesRaw) {
if (!file.endsWith('.ts')) continue;
await rm(`${root}/${file}`);
}
const allFiles = filesRaw.filter(
(file) => file.endsWith('.proto') && !file.startsWith('_'),
);
for (const files of chunk(allFiles, 32)) {
const args = [
`-I=${root}`,
`--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}`),
];
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'),
);
}
await exec(`protoc ${args.join(' ')}`);
await writeFile(
`${ROOT}/index.ts`,
'// @ts-nocheck\n' +
files
.map((file) => file.replace('.proto', ''))
.map((file) => `export * from './${file}.ts';`)
.join('\n'),
);
+1 -4
View File
@@ -1,7 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*"],
"compilerOptions": {
"baseUrl": "../../"
}
"include": ["src/**/*"]
}
-24
View File
@@ -1,24 +0,0 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# jetbrains setting folder
.idea/
-47
View File
@@ -1,47 +0,0 @@
# Astro Starter Kit: Minimal
```sh
bun create astro@latest -- --template minimal
```
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](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).
-14
View File
@@ -1,14 +0,0 @@
// @ts-check
import sitemap from '@astrojs/sitemap';
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
export default defineConfig({
integrations: [react(), sitemap()],
devToolbar: { enabled: false },
outDir: '../../dist/website',
prefetch: true,
site: 'https://blitzkit.app',
});
-48
View File
@@ -1,48 +0,0 @@
{
"name": "@blitzkit/website-ue",
"type": "module",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro",
"lint": "astro check"
},
"dependencies": {
"@astrojs/partytown": "^2.1.4",
"@astrojs/react": "^4.3.0",
"@astrojs/sitemap": "^3.2.1",
"@astrolib/seo": "^1.0.0-beta.8",
"@blitzkit/closed": "workspace:*",
"@blitzkit/core": "workspace:*",
"@radix-ui/colors": "^3.0.0",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/themes": "^3.1.4",
"@react-three/drei": "^9.115.0",
"@react-three/fiber": "^8.17.10",
"@types/ffi-napi": "^4.0.10",
"@types/lodash-es": "^4.17.12",
"@types/pako": "^2.0.3",
"@types/react": "^19.1.5",
"@types/react-dom": "^19.1.5",
"@types/ref-napi": "^3.0.12",
"@types/semver": "^7.7.0",
"@types/three": "^0.160.0",
"astro": "^5.8.0",
"diff": "^8.0.2",
"ffi-napi": "^4.0.3",
"fuzzysort": "^3.1.0",
"immer": "^10.1.1",
"lodash-es": "^4.17.21",
"minimatch": "^10.0.1",
"minizlib": "^3.0.2",
"pako": "^2.1.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"ref-napi": "^3.0.3",
"semver": "^7.7.2",
"sharp": "^0.34.2",
"three": "^0.169.0",
"zustand": "^4.5.5"
}
}
@@ -1,11 +0,0 @@
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_741_237)">
<rect width="60" height="60" fill="#FDD1EA"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M54.6274 0L55.4558 0.828427L54.0416 2.24264L51.799 0L54.6274 0ZM5.37258 0L4.54416 0.828427L5.95837 2.24264L8.20101 0L5.37258 0ZM48.9706 0L52.6274 3.65685L51.2132 5.07107L46.1421 0L48.9706 0ZM11.0294 0L7.37258 3.65685L8.7868 5.07107L13.8579 0L11.0294 0ZM43.3137 0L49.799 6.48528L48.3848 7.8995L40.4853 0H43.3137ZM16.6863 0L10.201 6.48528L11.6152 7.8995L19.5147 0L16.6863 0ZM37.6569 0L46.9706 9.31371L45.5564 10.7279L34.8284 0L37.6569 0ZM22.3431 0L13.0294 9.31371L14.4437 10.7279L25.1716 0L22.3431 0ZM32 0L44.1421 12.1421L42.7279 13.5563L30 0.828427L17.2721 13.5563L15.8579 12.1421L28 0H32ZM0.284271 0L28.2843 28L26.8701 29.4142L0 2.54416V0L0.284271 0ZM0 5.37258L25.4558 30.8284L24.0416 32.2426L0 8.20101L0 5.37258ZM0 11.0294L22.6274 33.6569L21.2132 35.0711L0 13.8579L0 11.0294ZM0 16.6863L19.799 36.4853L18.3848 37.8995L0 19.5147L0 16.6863ZM0 22.3431L16.9706 39.3137L15.5563 40.7279L0 25.1716L0 22.3431ZM0 28L14.1421 42.1421L12.7279 43.5564L0 30.8284V28ZM0 33.6569L11.3137 44.9706L9.8995 46.3848L0 36.4853L0 33.6569ZM0 39.3137L8.48528 47.799L7.07107 49.2132L0 42.1421L0 39.3137ZM0 44.9706L5.65685 50.6274L4.24264 52.0416L0 47.799L0 44.9706ZM0 50.6274L2.82843 53.4558L1.41421 54.8701L0 53.4558L0 50.6274ZM54.6274 60L30 35.3726L5.37258 60H8.20101L30 38.201L51.799 60H54.6274ZM48.9706 60L30 41.0294L11.0294 60H13.8579L30 43.8579L46.1421 60H48.9706ZM43.3137 60L30 46.6863L16.6863 60H19.5147L30 49.5147L40.4853 60H43.3137ZM37.6569 60L30 52.3431L22.3431 60H25.1716L30 55.1716L34.8284 60H37.6569ZM32 60L30 58L28 60H32ZM59.7157 0L31.7157 28L33.1299 29.4142L60 2.54416V0L59.7157 0ZM60 5.37258L34.5442 30.8284L35.9584 32.2426L60 8.20101V5.37258ZM60 11.0294L37.3726 33.6569L38.7868 35.0711L60 13.8579V11.0294ZM60 16.6863L40.201 36.4853L41.6152 37.8995L60 19.5147V16.6863ZM60 22.3431L43.0294 39.3137L44.4436 40.7279L60 25.1716V22.3431ZM60 28L45.8579 42.1421L47.2721 43.5564L60 30.8284V28ZM60 33.6569L48.6863 44.9706L50.1005 46.3848L60 36.4853V33.6569ZM60 39.3137L51.5147 47.799L52.9289 49.2132L60 42.1421V39.3137ZM60 44.9706L54.3431 50.6274L55.7574 52.0416L60 47.799V44.9706ZM60 50.6274L57.1716 53.4558L58.5858 54.8701L60 53.4558V50.6274ZM39.8995 16.3848L41.3137 14.9706L30 3.65685L18.6863 14.9706L20.1005 16.3848L30 6.48528L39.8995 16.3848ZM37.0711 19.2132L38.4853 17.799L30 9.31371L21.5147 17.799L22.9289 19.2132L30 12.1421L37.0711 19.2132ZM34.2426 22.0416L35.6569 20.6274L30 14.9706L24.3431 20.6274L25.7574 22.0416L30 17.799L34.2426 22.0416ZM31.4142 24.8701L32.8284 23.4558L30 20.6274L27.1716 23.4558L28.5858 24.8701L30 23.4558L31.4142 24.8701ZM56.8701 59.4142L58.2843 58L30 29.7157L1.71573 58L3.12994 59.4142L30 32.5442L56.8701 59.4142Z" fill="#FFBECE"/>
</g>
<defs>
<clipPath id="clip0_741_237">
<rect width="60" height="60" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 676 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 692 B

@@ -1,10 +0,0 @@
import { Grade } from '@protos/blitz_static_standard_grades_enum';
import type { BadgeProps } from '@radix-ui/themes';
export const GRADE_COLORS = {
[Grade.GRADE_UNSPECIFIED]: 'gray',
[Grade.GRADE_COMMON]: 'gray',
[Grade.GRADE_RARE]: 'blue',
[Grade.GRADE_EPIC]: 'purple',
[Grade.GRADE_LEGENDARY]: 'amber',
} satisfies Record<Grade, BadgeProps['color']>;
@@ -1,57 +0,0 @@
import { type FlexProps, Box, Flex, Skeleton, Text } from '@radix-ui/themes';
import { useRef } from 'react';
import { useIntersection } from '../../hooks/useIntersection';
import { useLocale } from '../../hooks/useLocale';
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
import { InlineSkeleton } from '../InlineSkeleton';
import type { AvatarGroupProps } from './Group';
import { Stack } from './Stack';
const MAX_AVATARS_PREVIEW = 5;
export function Content({
...props
}: FlexProps & MaybeSkeletonComponentProps<AvatarGroupProps>) {
const { strings } = useLocale();
const container = useRef<HTMLDivElement>(null);
useIntersection(() => props.skeleton && props.onIntersection?.(), container, {
disabled: !props.skeleton || props.onIntersection === undefined,
});
return (
<Flex
ref={container}
direction="column"
gap="3"
style={{ cursor: props.skeleton ? undefined : 'pointer' }}
{...props}
>
<Box width="100%" style={{ aspectRatio: '7 / 8' }} position="relative">
{props.skeleton && <Skeleton height="100%" />}
{!props.skeleton && (
<Stack avatars={[...props.avatars].slice(-MAX_AVATARS_PREVIEW)} />
)}
</Box>
<Text
size="2"
wrap="nowrap"
align="center"
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
fontStyle:
!props.skeleton && props.name === undefined ? 'italic' : undefined,
}}
>
{props.skeleton ? (
<InlineSkeleton width="100%" />
) : (
(props.name ?? strings.website.tools.avatars.unnamed_avatars)
)}
</Text>
</Flex>
);
}
@@ -1,28 +0,0 @@
import type { BlitzKitAvatar } from '@protos/blitzkit';
import { Dialog } from '@radix-ui/themes';
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
import { Content } from './Content';
import { Popup } from './Popup';
export interface AvatarGroupProps {
name?: string;
avatars: { avatar: BlitzKitAvatar }[];
}
export function AvatarGroup(
props: MaybeSkeletonComponentProps<AvatarGroupProps>,
) {
if (props.skeleton) return <Content {...props} />;
return (
<Dialog.Root>
<Dialog.Trigger>
<Content {...props} />
</Dialog.Trigger>
<Dialog.Content>
<Popup {...props} />
</Dialog.Content>
</Dialog.Root>
);
}
@@ -1,118 +0,0 @@
import { Grade } from '@protos/blitz_static_standard_grades_enum';
import {
CardStackIcon,
DownloadIcon,
ExternalLinkIcon,
EyeNoneIcon,
StarFilledIcon,
} from '@radix-ui/react-icons';
import { Badge, Box, Button, Dialog, Flex, Link, Text } from '@radix-ui/themes';
import { Var } from '../../core/radix/var';
import { useLocale } from '../../hooks/useLocale';
import { RewardBadge } from '../RewardBadge';
import { GRADE_COLORS } from './Colors';
import type { AvatarGroupProps } from './Group';
export function Popup({ name, avatars }: AvatarGroupProps) {
const { strings, gameStrings } = useLocale();
return (
<>
<Dialog.Title>{name}</Dialog.Title>
<Flex direction="column" gap="5" mt="5">
{[...avatars].reverse().map(({ avatar }, index) => (
<Flex key={index} gap="5">
<Box
m="1"
mb="4"
height="10rem"
style={{
aspectRatio: '7 / 8',
borderRadius: Var('radius-4'),
outline: `${Var('space-1')} solid ${Var(`${GRADE_COLORS[avatar.grade]}-9`)}`,
boxShadow: `0 0 ${
1.5 * (avatar.grade / Grade.GRADE_LEGENDARY)
}rem ${Var(`${GRADE_COLORS[avatar.grade]}-9`)}`,
backgroundImage: 'url(/assets/sad-ghost.png)',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
backgroundSize: '3rem',
}}
position="relative"
>
<Box
width="100%"
height="100%"
style={{
backgroundImage: `url(/api/avatars/${avatar.id}.webp)`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
<Badge
mt="1"
style={{
position: 'absolute',
top: '100%',
left: '50%',
transform: 'translate(-50%, -50%)',
}}
variant="solid"
color={GRADE_COLORS[avatar.grade]}
>
<StarFilledIcon /> {strings.common.grades[avatar.grade]}
</Badge>
</Box>
<Flex direction="column" gap="3" justify="center">
<Flex gap="1">
<Badge color="gray" variant="surface">
<CardStackIcon />
{strings.website.tools.avatars.categories[avatar.category]}
</Badge>
{avatar.hidden_if_not_obtained && (
<Badge color="gray" variant="surface">
<EyeNoneIcon /> {strings.website.tools.avatars.hidden}
</Badge>
)}
{avatar.sale && (
<RewardBadge
reward={avatar.sale}
color="gray"
variant="surface"
/>
)}
</Flex>
<Text>
{gameStrings[avatar.description] ??
(gameStrings[avatar.obtaining]
? undefined
: strings.website.tools.avatars.no_description)}{' '}
{gameStrings[avatar.obtaining]}
</Text>
<Flex gap="2">
<Link href={`/api/avatars/${avatar.id}.webp`} download>
<Button variant="solid">
<DownloadIcon /> {strings.website.tools.avatars.download}
</Button>
</Link>
<Link href={`/api/avatars/${avatar.id}.webp`} target="_blank">
<Button variant="outline">
<ExternalLinkIcon /> {strings.website.tools.avatars.enlarge}
</Button>
</Link>
</Flex>
</Flex>
</Flex>
))}
</Flex>
</>
);
}
@@ -1,64 +0,0 @@
import { Badge, Box } from '@radix-ui/themes';
import { literals } from 'packages/i18n/src';
import { Var } from '../../core/radix/var';
import { useLocale } from '../../hooks/useLocale';
import type { AvatarGroupProps } from './Group';
const dx = '4px';
export function Stack({ avatars }: { avatars: AvatarGroupProps['avatars'] }) {
const { strings } = useLocale();
return (
<>
{avatars.map(({ avatar }, index) => (
<Box
key={index}
position="absolute"
top={`calc(${dx} * ${index})`}
left={`calc(${dx} * ${index})`}
width={`calc(100% - ${dx} * ${avatars.length - 1})`}
height={`calc(100% - ${dx} * ${avatars.length - 1})`}
style={{
filter: `brightness(${(index + 1) / avatars.length})`,
borderRadius: Var('radius-4'),
boxShadow: Var('shadow-2'),
overflow: 'hidden',
backgroundImage: 'url(/assets/sad-ghost.png)',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
backgroundSize: '3rem',
}}
>
<Box
width="100%"
height="100%"
style={{
backgroundImage: `url(/api/avatars/${avatar.id}.webp)`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
{avatars.length > 1 && index === avatars.length - 1 && (
<Box
width="100%"
height="100%"
style={{
background: `linear-gradient(-45deg, ${Var('black-a9')} 25%, ${Var('black-a1')} 50%)`,
}}
/>
)}
</Box>
))}
{avatars.length > 1 && (
<Box position="absolute" bottom="1" right="1">
<Badge variant="solid" color="gray" highContrast size="2">
{literals(strings.common.units.times, [`${avatars.length}`])}
</Badge>
</Box>
)}
</>
);
}
@@ -1,30 +0,0 @@
import { Theme, type ThemeProps } from '@radix-ui/themes';
import type { ReactNode } from 'react';
interface BlitzKitThemeProps extends ThemeProps {
children: ReactNode;
}
export function BlitzKitTheme({
children,
className,
style,
...props
}: BlitzKitThemeProps) {
return (
<Theme
style={{ minHeight: 'unset', display: 'contents', ...style }}
className={`blitzkit-theme ${className}`}
grayColor="mauve"
appearance="dark"
panelBackground="solid"
radius="full"
accentColor="iris"
suppressHydrationWarning
suppressContentEditableWarning
{...props}
>
{children}
</Theme>
);
}
@@ -1,18 +0,0 @@
.policies-agreement {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--color-overlay);
z-index: 2;
}
.policies-agreement .content-wrapper {
width: 100%;
background: var(--color-panel-solid);
}
.policies-agreement .content {
max-width: 60rem;
}
@@ -1,157 +0,0 @@
import { assertSecret, idToRegion } from '@blitzkit/core';
import { Button, Flex, Heading, Text } from '@radix-ui/themes';
import { useEffect, useState } from 'react';
import {
LocaleProvider,
useLocale,
type LocaleAcceptorProps,
} from '../../hooks/useLocale';
import { App } from '../../stores/app';
import { CURRENT_POLICIES_AGREEMENT_INDEX } from '../../stores/app/constants';
import { LinkI18n } from '../LinkI18n';
import './index.css';
type Extension =
| {
status: 'ok';
data: {
access_token: string;
account_id: number;
expires_at: number;
};
}
| {
status: 'error';
};
export function Checks({ localeData }: LocaleAcceptorProps) {
return (
<LocaleProvider localeData={localeData}>
<App.Provider>
<Content />
</App.Provider>
</LocaleProvider>
);
}
/**
* Wargaming: 14 (refresh: 7)
*/
function Content() {
const { locale } = useLocale();
const logins = App.use((state) => state.logins);
const mutateApp = App.useMutation();
const appStore = App.useStore();
const policiesAgreementIndex = App.use(
(state) => state.policiesAgreementIndex,
);
const isLegal = window.location.pathname.startsWith('/legal');
const [showPoliciesAgreement, setShowPoliciesAgreement] = useState(false);
useEffect(() => {
function unsubscribe() {
window.removeEventListener('scroll', show);
window.removeEventListener('pointermove', show);
window.removeEventListener('pointerdown', show);
}
function show() {
setShowPoliciesAgreement(true);
unsubscribe();
}
if (
policiesAgreementIndex !== CURRENT_POLICIES_AGREEMENT_INDEX &&
!isLegal
) {
window.addEventListener('scroll', show);
window.addEventListener('pointermove', show);
window.addEventListener('pointerdown', show);
}
return unsubscribe;
}, [policiesAgreementIndex, isLegal]);
useEffect(() => {
if (logins.wargaming) {
const expiresIn = logins.wargaming.expires - Date.now();
const expiresInDays = expiresIn / 1000 / 60 / 60 / 24;
if (expiresInDays < 0) {
mutateApp((draft) => {
draft.logins.wargaming = undefined;
});
} else if (expiresInDays < 7) {
const { wargaming } = appStore.getState().logins;
if (!wargaming) return;
fetch(
`https://api.worldoftanks.${idToRegion(
wargaming.id,
)}/wot/auth/prolongate/?application_id=${assertSecret(
import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID,
)}&access_token=${wargaming.token}`,
)
.then((response) => response.json() as Promise<Extension>)
.then((json) => {
mutateApp((draft) => {
if (json.status === 'error') {
draft.logins.wargaming = undefined;
} else {
draft.logins.wargaming = {
id: wargaming.id,
token: json.data.access_token,
expires: json.data.expires_at * 1000,
};
}
});
});
}
}
});
if (!showPoliciesAgreement) return null;
return (
<Flex className="policies-agreement" align="end">
<Flex p="8" className="content-wrapper" justify="center">
<Flex align="start" gap="4" className="content" direction="column">
<Flex direction="column" gap="2">
<Heading>
{policiesAgreementIndex === -1
? "BlitzKit's policies"
: "BlitzKit's policies have changed"}
</Heading>
<Text>
This website utilizes cookies to perform analytics and personalize
your experience. You can learn more through{' '}
<LinkI18n locale={locale} href="/docs/legal/privacy-policy">
our privacy policy
</LinkI18n>
. By using BlitzKit, you also agree to our{' '}
<LinkI18n locale={locale} href="/docs/legal/terms-of-service">
terms of service
</LinkI18n>
.
</Text>
</Flex>
<Button
mt="2"
onClick={() => {
appStore.setState({
policiesAgreementIndex: CURRENT_POLICIES_AGREEMENT_INDEX,
});
setShowPoliciesAgreement(false);
}}
>
I agree
</Button>
</Flex>
</Flex>
</Flex>
);
}
@@ -1 +0,0 @@
Permission denied
@@ -1,134 +0,0 @@
---
import { Box, Code, Flex, Heading, Link, Text } from '@radix-ui/themes';
import packageJson from '../../../../package.json';
import { getStrings } from '../core/i18n/getStrings';
import { LinkI18n } from './LinkI18n';
import { LocaleSwitcherThemeWrapper } from './LocaleSwitcher';
interface Props {
locale: string;
}
const { locale } = Astro.props;
const localeData = { locale };
const strings = await getStrings(locale);
---
<style>
.footer {
background-color: var(--color-panel);
}
</style>
<Flex className="footer" gap="6" p="6" direction="column" align="center">
<Flex
align="center"
justify="between"
maxWidth="40rem"
width="100%"
flexGrow="1"
gap="5"
position="relative"
direction={{ initial: 'column', sm: 'row' }}
>
<Flex
direction="column"
align={{
initial: 'center',
sm: 'start',
}}
>
<Flex align="center" gap="2">
<Heading> BlitzKit </Heading>
<Code size="1" color="gray" variant="ghost">{packageJson.version}</Code>
</Flex>
<Text color="gray">{strings.website.footer.motto}</Text>
<Text size="1" color="gray">
{strings.website.footer.copyright}
</Text>
<Box mt="2">
<LocaleSwitcherThemeWrapper {localeData} client:load />
</Box>
</Flex>
<Flex
gap="6"
width={{
initial: '100%',
sm: 'auto',
}}
>
<Flex
direction="column"
gap="1"
align={{
initial: 'end',
sm: 'start',
}}
flexGrow={{
initial: '1',
sm: '0',
}}
flexBasis={{
initial: '0',
sm: 'auto',
}}
>
<Heading size="3">{strings.website.footer.about.title}</Heading>
<Flex
direction="column"
align={{
initial: 'end',
sm: 'start',
}}
>
<LinkI18n {locale} href="/docs/about/credits" color="gray">
{strings.website.footer.about.credits}
</LinkI18n>
<Link
href="https://discord.gg/nDt7AjGJQH"
target="_blank"
color="gray"
>
{strings.website.footer.about.discord}
</Link>
<Link
href="https://github.com/tresabhi/blitzkit"
target="_blank"
color="gray"
>
{strings.website.footer.about.github}
</Link>
<LinkI18n {locale} href="/docs/patches" color="gray">
{strings.website.footer.about.patches}
</LinkI18n>
</Flex>
</Flex>
<Flex
direction="column"
gap="1"
flexGrow={{
initial: '1',
sm: '0',
}}
flexBasis={{
initial: '0',
sm: 'auto',
}}
>
<Heading size="3">{strings.website.footer.legal.title}</Heading>
<Flex direction="column">
<LinkI18n {locale} href="/docs/legal/privacy-policy" color="gray">
{strings.website.footer.legal.privacy}
</LinkI18n>
<LinkI18n {locale} href="/docs/legal/terms-of-service" color="gray">
{strings.website.footer.legal.terms}
</LinkI18n>
</Flex>
</Flex>
</Flex>
</Flex>
</Flex>
@@ -1,156 +0,0 @@
---
import { Code, Flex, Heading, Text } from '@radix-ui/themes';
import { resolveBranchName } from '../core/blitzkit/branches';
import { getStrings } from '../core/i18n/getStrings';
import { literals } from '@blitzkit/i18n/src/literals';
interface Props {
locale: string;
}
const { locale } = Astro.props;
const strings = await getStrings(locale);
const branchName = resolveBranchName();
const isBranchNamed = branchName !== undefined;
---
<style>
#home-hero {
--p-1: 0%;
--p-2: 0%;
--p-3: 0%;
--p-4: 0%;
--p-7: 0%;
--g-1-dark: var(--blue-3);
--g-1-light: var(--blue-7);
--g-2-dark: var(--violet-3);
--g-2-light: var(--violet-7);
--g-3-dark: var(--purple-3);
--g-3-light: var(--purple-7);
--g-4-dark: var(--crimson-3);
--g-4-light: var(--crimson-7);
--g-7-dark: var(--tomato-3);
--g-7-light: var(--tomato-7);
--g-1: color-mix(in srgb, var(--g-1-dark), var(--g-1-light) var(--p-1));
--g-2: color-mix(in srgb, var(--g-2-dark), var(--g-2-light) var(--p-2));
--g-3: color-mix(in srgb, var(--g-3-dark), var(--g-3-light) var(--p-3));
--g-4: color-mix(in srgb, var(--g-4-dark), var(--g-4-light) var(--p-4));
--g-7: color-mix(in srgb, var(--g-7-dark), var(--g-7-light) var(--p-7));
background: linear-gradient(
45deg,
var(--g-1),
var(--g-2),
var(--g-3),
var(--g-4),
var(--g-7)
);
}
#home-hero[data-variant='pride'] {
--g-1-dark: var(--crimson-3);
--g-1-light: var(--crimson-7);
--g-2-dark: var(--orange-3);
--g-2-light: var(--orange-7);
--g-3-dark: var(--yellow-3);
--g-3-light: var(--yellow-7);
--g-4-dark: var(--blue-3);
--g-4-light: var(--blue-7);
--g-7-dark: var(--purple-3);
--g-7-light: var(--purple-7);
}
#home-hero[data-variant='spooky'] {
--g-1-dark: var(--orange-3);
--g-1-light: var(--orange-7);
--g-2-dark: var(--tomato-3);
--g-2-light: var(--tomato-7);
--g-3-dark: var(--orange-3);
--g-3-light: var(--orange-7);
--g-4-dark: var(--tomato-3);
--g-4-light: var(--tomato-7);
--g-7-dark: var(--orange-3);
--g-7-light: var(--orange-7);
}
</style>
<script>
import { clamp, times } from 'lodash-es';
const n = 5;
const homeHero = document.getElementById('home-hero');
if (homeHero) {
switch (new Date().getMonth()) {
case 5:
homeHero.dataset.variant = 'pride';
break;
case 9:
homeHero.dataset.variant = 'spooky';
break;
}
window.addEventListener('pointermove', (event) => {
const x = event.clientX / window.innerWidth;
const c = clamp(
(1 -
(event.clientY + homeHero.getBoundingClientRect().top) /
window.innerHeight) **
2,
0,
1,
);
// https://www.desmos.com/calculator/ndz2poxk32
times(n, (i) => {
const p = c * clamp(-n * Math.abs(x - i / (n - 1)) + 1, 0, 3);
homeHero.style.setProperty(`--p-${i + 1}`, `${p * 100}%`);
});
});
}
</script>
<Flex
id="home-hero"
align="center"
justify="center"
position="relative"
px="4"
py="9"
overflow="hidden"
>
<Flex
direction="column"
gap="6"
align="center"
justify="center"
position="relative"
width="100%"
>
<Flex direction="column" align="center" gap="1">
<Heading size={{ initial: '8', xs: '9' }} weight="bold" align="center">
<Flex align="center" gap={{ initial: '2', xs: '4' }}>
BlitzKit
{
isBranchNamed && (
<Code
size={{ initial: '7', xs: '8' }}
color="gray"
variant="outline"
>
{branchName}
</Code>
)
}
</Flex>
</Heading>
<Text color="gray" align="center" size="4"
>{literals(strings.website.home.hero.credit, ['TrèsAbhi'])}</Text
>
</Flex>
</Flex>
</Flex>
@@ -1,7 +0,0 @@
import { Skeleton, type SkeletonProps } from '@radix-ui/themes';
export function InlineSkeleton({ className, ...props }: SkeletonProps) {
return (
<Skeleton style={{ display: 'inline-block' }} height="1em" {...props} />
);
}
@@ -1,11 +0,0 @@
import { Flex, Text } from '@radix-ui/themes';
import { ffi } from '../../core/blitzkit/ffi';
export function FFIEcho() {
return (
<Flex direction="column" align="start" gap="2">
<Text>{ffi.debug_echo(new Date().toLocaleString())}</Text>
<img src="https://i.imgflip.com/9wrr7u.gif" />
</Flex>
);
}
@@ -1,10 +0,0 @@
import { Code, Text } from '@radix-ui/themes';
import { ffi } from '../../core/blitzkit/ffi';
export function FFIFileCount() {
return (
<Text>
<Code>{ffi.debug_get_file_count().toLocaleString()}</Code> files loaded
</Text>
);
}
@@ -1,46 +0,0 @@
---
import locales from '@blitzkit/i18n/locales.json';
import { DiscordLogoIcon, PaperPlaneIcon } from '@radix-ui/react-icons';
import { Button, Flex, Heading, Link, Text } from '@radix-ui/themes';
import { PageWrapper } from '../components/PageWrapper';
import { getStrings } from '../core/i18n/getStrings';
import BaseLayout from '../layouts/BaseLayout.astro';
export interface Props {
path: string;
locale: string;
}
const { path, locale } = Astro.props;
const strings = await getStrings(locale);
const resolvedPath = `/${locale === locales.default ? '' : `${locale}/`}${path}`;
---
<BaseLayout {locale}>
<link
slot="head"
rel="canonical"
href={`${Astro.url.origin}${resolvedPath}`}
/>
<meta slot="head" http-equiv="refresh" content={`0; url=${resolvedPath}`} />
<PageWrapper align="center" justify="center" gap="0">
<Heading>{strings.website.redirect.title}</Heading>
<Text color="gray">{strings.website.redirect.body}</Text>
<Flex mt="4" gap="2">
<Link href={resolvedPath}>
<Button>
<PaperPlaneIcon /> Redirect
</Button>
</Link>
<Link href="https://discord.gg/nDt7AjGJQH">
<Button variant="outline">
<DiscordLogoIcon /> Discord
</Button>
</Link>
</Flex>
</PageWrapper>
</BaseLayout>
@@ -1,14 +0,0 @@
import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
import { Link, type LinkProps } from '@radix-ui/themes';
export function LinkI18n({
locale,
href,
...props
}: LinkProps & { locale: string }) {
const prefix = locale === locales.default ? '' : `/${locale}`;
return (
<Link href={href?.startsWith('/') ? `${prefix}${href}` : href} {...props} />
);
}
@@ -1,51 +0,0 @@
import locales from '@blitzkit/i18n/locales.json' with { type: 'json' };
import { Select } from '@radix-ui/themes';
import { LOCALE_NAMES, SUPPORTED_LOCALE_FLAGS } from 'packages/i18n/src';
import type { LocaleAcceptorProps } from '../hooks/useLocale';
import { BlitzKitTheme } from './BlitzKitTheme';
export function LocaleSwitcherThemeWrapper({
localeData,
}: LocaleAcceptorProps) {
return (
<BlitzKitTheme style={{ background: 'transparent' }}>
<LocaleSwitcher localeData={localeData} />
</BlitzKitTheme>
);
}
export function LocaleSwitcher({ localeData }: LocaleAcceptorProps) {
return (
<Select.Root
defaultValue={localeData.locale}
onValueChange={(locale) => {
localStorage.setItem('preferred-locale', locale);
let rawPath = window.location.pathname;
for (const supportedLocale of locales.supported) {
if (
window.location.pathname.startsWith(`/${supportedLocale.locale}`)
) {
rawPath = rawPath.replace(`/${supportedLocale.locale}`, '');
break;
}
}
window.location.pathname = `${
locale === locales.default ? '' : `/${locale}`
}${rawPath}`;
}}
>
<Select.Trigger variant="classic" />
<Select.Content>
{locales.supported.map(({ locale }) => (
<Select.Item key={locale} value={locale}>
{SUPPORTED_LOCALE_FLAGS[locale]} {LOCALE_NAMES[locale]}
</Select.Item>
))}
</Select.Content>
</Select.Root>
);
}
@@ -1,245 +0,0 @@
---
import {
Cross1Icon,
DiscordLogoIcon,
GearIcon,
HamburgerMenuIcon,
} from '@radix-ui/react-icons';
import { Box, Code, Flex, IconButton, Link } from '@radix-ui/themes';
import { tools } from '../constants/tools';
import { resolveBranchName } from '../core/blitzkit/branches';
import { getStrings } from '../core/i18n/getStrings';
import { Var } from '../core/radix/var';
import { LinkI18n } from './LinkI18n';
export interface Props {
locale: string;
}
const { locale } = Astro.props;
const toolsFiltered = tools.filter((tool) => tool.enabled);
const branchName = resolveBranchName();
const isBranchNamed = branchName !== undefined;
const strings = await getStrings(locale);
---
<style>
.navbar,
.padder {
transition-duration: 1s;
}
body.no-navbar .navbar {
transform: translateY(-100%);
}
body.no-navbar .padder {
height: 0;
}
.navbar[data-expanded='false'] *[data-expanded] {
display: none;
}
.navbar[data-expanded='true'] {
height: 100%;
}
.navbar[data-expanded='false'] .content {
background-color: var(--black-a8);
backdrop-filter: blur(4rem);
box-shadow: var(--shadow-3);
}
.navbar[data-expanded='true'] .content {
background-color: var(--gray-1);
}
.navbar[data-expanded='true'] *[data-closed] {
display: none;
}
</style>
<script>
const navbar = document.querySelector<HTMLDivElement>('.navbar');
const hamburger = document.querySelector<HTMLButtonElement>('.hamburger');
navbar?.addEventListener('click', () => {
navbar.dataset.expanded = 'false';
});
hamburger?.addEventListener('click', (event) => {
if (!navbar) return;
event.stopPropagation();
navbar.dataset.expanded = `${navbar.dataset.expanded === 'false'}`;
});
</script>
<Flex
direction="column"
align="center"
data-expanded="false"
className="navbar"
width="100%"
top="0"
position="sticky"
style={{
zIndex: 1,
backdropFilter: 'blur(4rem)',
}}
>
<Flex
className="content"
width="100%"
p={{ initial: '4', sm: '3' }}
direction="column"
align="center"
>
<Flex gap="4" direction="column" maxWidth="80rem" width="100%">
<Flex align="center">
<LinkI18n
{locale}
color="gray"
highContrast
href="/"
underline="hover"
weight="medium"
>
<Flex align="center" gap="1">
BlitzKit
{
isBranchNamed && (
<Code color="gray" size="1" variant="outline">
{branchName}
</Code>
)
}
</Flex>
</LinkI18n>
<Box flexGrow="1" />
<Flex
align="center"
gap={{ initial: '4', md: '6' }}
display={{ initial: 'none', sm: 'flex' }}
justify="center"
className="tools"
>
{
toolsFiltered.map((tool) => {
const selected = Astro.url.pathname.includes(`/${tool.id}`);
const toolStrings =
strings.website.tools[
(tool.strings ??
tool.id) as keyof typeof strings.website.tools
];
return (
<LinkI18n
{locale}
weight={selected ? 'medium' : 'regular'}
color="gray"
highContrast={selected}
size="2"
href={`/${tool.id}`}
underline="hover"
>
{toolStrings.name}
</LinkI18n>
);
})
}
</Flex>
<Box flexGrow="1" />
<Flex align="center" gap="4">
<Link
style={{ display: 'flex', alignContent: 'center' }}
color="gray"
href="https://discord.gg/nDt7AjGJQH"
underline="none"
target="_blank"
>
<DiscordLogoIcon width="1em" height="1em" />
</Link>
<LinkI18n
{locale}
style={{ display: 'flex', alignContent: 'center' }}
color="gray"
href="/settings"
underline="none"
>
<GearIcon width="1em" height="1em" />
</LinkI18n>
<Flex align="center" display={{ initial: 'flex', sm: 'none' }}>
<IconButton variant="ghost" color="gray" className="hamburger">
<Cross1Icon data-expanded />
<HamburgerMenuIcon data-closed />
</IconButton>
</Flex>
</Flex>
</Flex>
<Flex
direction="column"
data-expanded
gap="3"
display={{ initial: 'flex', sm: 'none' }}
>
{
toolsFiltered.map((tool) => {
const toolStrings =
strings.website.tools[
(tool.strings ?? tool.id) as keyof typeof strings.website.tools
];
return (
<Link
color="gray"
highContrast
style={{ display: 'contents' }}
href={`/${tool.id}`}
>
<Box
style={{
borderRadius: Var('radius-2'),
backgroundImage: `url(/assets/images/${tool.id}.webp)`,
backgroundSize: '200%',
backgroundPosition: 'right center',
}}
>
<Flex
py="4"
justify="end"
flexGrow="1"
style={{
background: `linear-gradient(90deg, transparent, ${Var('gray-1')} 90%)`,
}}
>
{toolStrings.name}
</Flex>
</Box>
</Link>
);
})
}
</Flex>
</Flex>
</Flex>
<Box
data-expanded
flexGrow="1"
width="100%"
display={{ initial: 'block', sm: 'none' }}
style={{
backgroundColor: Var('black-a7'),
backdropFilter: 'blur(0.5rem)',
WebkitBackdropFilter: 'blur(0.5rem)',
}}
/>
</Flex>
@@ -1,52 +0,0 @@
import { Flex, type FlexProps, Theme } from '@radix-ui/themes';
import type { ComponentProps } from 'react';
import { BlitzKitTheme } from './BlitzKitTheme';
type PageWrapperProps = FlexProps & {
color?: ComponentProps<typeof Theme>['accentColor'];
containerProps?: ComponentProps<typeof Theme>;
noFlex1?: boolean;
noMinHeight?: boolean;
};
export function PageWrapper({
style,
color,
children,
noFlex1 = false,
containerProps,
noMinHeight = false,
...props
}: PageWrapperProps) {
return (
<BlitzKitTheme
accentColor={color}
style={{
flex: noFlex1 ? undefined : 1,
display: 'flex',
minHeight: noMinHeight ? 'unset' : 'calc(100svh - 3rem)',
flexDirection: 'column',
...containerProps?.style,
}}
{...containerProps}
>
<Flex
direction="column"
gap="8"
px="4"
py="8"
width="100%"
maxWidth="80rem"
style={{
margin: 'auto',
boxSizing: 'border-box',
flex: 1,
...style,
}}
{...props}
>
{children}
</Flex>
</BlitzKitTheme>
);
}
@@ -1,47 +0,0 @@
---
import { assertSecret } from '@blitzkit/core';
const PUBLIC_GOOGLE_TAG_MANAGER_ID = assertSecret(
import.meta.env.PUBLIC_GOOGLE_TAG_MANAGER_ID,
);
---
{/* google analytics */}
<script
is:inline
async
type="text/partytown"
src={`https://www.googletagmanager.com/gtag/js?id=${PUBLIC_GOOGLE_TAG_MANAGER_ID}`}
></script>
<script
is:inline
define:vars={{ PUBLIC_GOOGLE_TAG_MANAGER_ID }}
type="text/partytown"
>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', PUBLIC_GOOGLE_TAG_MANAGER_ID);
</script>
{/* nitro ads */}
{
/*
<script is:inline data-cfasync="false">
window.nitroAds = window.nitroAds || {
createAd: function () {
return new Promise((e) => {
window.nitroAds.queue.push(['createAd', arguments, e]);
});
},
addUserToken: function () {
window.nitroAds.queue.push(['addUserToken', arguments]);
},
queue: [],
};
</script>
*/
}
@@ -1,42 +0,0 @@
import type { StandardReward } from '@protos/blitz_static_standard_reward';
import { Badge, type BadgeProps } from '@radix-ui/themes';
import { useLocale } from '../hooks/useLocale';
interface RewardBadgeProps {
reward: StandardReward;
}
export function RewardBadge({
reward,
...props
}: RewardBadgeProps & BadgeProps) {
const { locale } = useLocale();
if (reward.reward_list.length !== 1) {
throw new Error('reward_list must be length 1');
}
const reward0 = reward.reward_list[0];
Object.entries(reward0).forEach(([key, value]) => {
if (key !== 'currency_reward' && value !== undefined) {
throw new Error(`unsupported reward type: ${key}`);
}
});
return (
<Badge {...props}>
{reward0.currency_reward && (
<>
<img
src={`/api/currencies/${reward0.currency_reward.currency_catalog_id.replace(
'CurrencyEntity.',
'',
)}.webp`}
/>
{reward0.currency_reward.amount.toLocaleString(locale)}
</>
)}
</Badge>
);
}
@@ -1,113 +0,0 @@
---
import { CaretRightIcon } from '@radix-ui/react-icons';
import { Box, Button, Flex, Heading, Text } from '@radix-ui/themes';
import type { Tool } from '../constants/tools';
import { getStrings } from '../core/i18n/getStrings';
import { Var } from '../core/radix/var';
import { LinkI18n } from './LinkI18n';
interface Props {
tool: Tool;
locale: string;
}
const { tool, locale } = Astro.props;
const strings = await getStrings(locale);
const disabled = false;
const toolStrings =
strings.website.tools[
(tool.strings ?? tool.id) as keyof typeof strings.website.tools
];
---
<Flex
style={{
backgroundImage: `url(/assets/tool-backgrounds/${tool.id}.webp)`,
backgroundSize: 'cover',
backgroundPosition: 'center',
borderRadius: Var('radius-2'),
}}
height={tool.significant ? '20rem' : '16rem'}
data-disabled={disabled}
data-significant={tool.significant}
position="relative"
overflow="hidden"
flexGrow="1"
flexBasis={tool.significant ? undefined : { initial: undefined, sm: '0' }}
>
<LinkI18n
{locale}
className="link"
underline="none"
href={disabled ? '#' : `/${tool.id}`}
style={{
display: 'flex',
width: '100%',
height: '100%',
flexDirection: 'column',
color: 'inherit',
}}
>
<Box
flexGrow="1"
style={{
backgroundImage: `url(/assets/tool-backgrounds/${tool.id}.webp)`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
<Flex
px={{ initial: '5', md: tool.significant ? '8' : '6' }}
py={{ initial: '4', sm: tool.significant ? '6' : '4' }}
gap="4"
align="center"
justify="between"
width="100%"
direction={{ initial: 'column', xs: 'row' }}
style={{
backgroundColor: 'var(--color-panel-translucent)',
backdropFilter: 'blur(64px)',
WebkitBackdropFilter: 'blur(64px)',
}}
>
<Flex
direction="column"
justify="center"
align={{ initial: 'center', xs: 'start' }}
>
<Heading
align={{ initial: 'center', sm: 'left' }}
size={{ initial: '6', sm: tool.significant ? '7' : '5' }}
weight="medium"
>
{toolStrings.name}
</Heading>
<Text
align={{ initial: 'center', sm: 'left' }}
size={{ initial: '3', sm: tool.significant ? '4' : '3' }}
color="gray"
>
{toolStrings.description}
</Text>
</Flex>
{
!disabled && (
<Button
highContrast={tool.button.contrast}
size={{
initial: undefined,
sm: tool.significant ? '3' : undefined,
}}
color={tool.button.color}
style={{ cursor: 'inherit' }}
>
{toolStrings.button}
<CaretRightIcon />
</Button>
)
}
</Flex>
</LinkI18n>
</Flex>
Loaded 100 of 218 files, more files were not shown because too many files have changed in this diff. Show more