Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f58d06b3c3 | ||
|
|
2c44bd8374 | ||
|
|
699b445f79 | ||
|
|
26bb350709 | ||
|
|
6fb51e703d | ||
|
|
7a7c73d77b | ||
|
|
f945786e2f | ||
|
|
c678830fd2 | ||
|
|
006b2fef78 | ||
|
|
26a0c1f176 | ||
|
|
403d1e7849 | ||
|
|
c9d8f6b445 | ||
|
|
73c53d6ef5 | ||
|
|
f8e8fd4016 | ||
|
|
15b29f439a | ||
|
|
fd96cd5975 | ||
|
|
66b9a9076f | ||
|
|
746c7963de | ||
|
|
ae96ae34c0 | ||
|
|
1f690d11c7 | ||
|
|
8cf3da12fa | ||
|
|
a8ae806f8b | ||
|
|
8a6841adea | ||
|
|
5e88f5a73d | ||
|
|
aa1f357538 | ||
|
|
ca270e2010 | ||
|
|
17264c1adc | ||
|
|
2627aba938 | ||
|
|
a2574efb9c | ||
|
|
d3ab4bac51 | ||
|
|
ad78e45f1b | ||
|
|
5ea7bbd6a1 | ||
|
|
2b955a281b | ||
|
|
676601b06e | ||
|
|
b81d9985ee | ||
|
|
d66d25a261 | ||
|
|
402d054b30 | ||
|
|
61d6d42742 | ||
|
|
03e06b050e | ||
|
|
cca46957bc | ||
|
|
3c92ab3a19 | ||
|
|
6c31f663f6 | ||
|
|
bd140f0eda | ||
|
|
a3d8fcb960 | ||
|
|
06304cf075 | ||
|
|
416d061df1 | ||
|
|
47c87970cd | ||
|
|
ff3ed9d656 | ||
|
|
4e9c3df5eb | ||
|
|
c100a1e655 | ||
|
|
26cc0cc723 | ||
|
|
f697f94bd5 | ||
|
|
30fa18f0fd | ||
|
|
c45af8e156 | ||
|
|
f95a6e3495 | ||
|
|
0e637d772e | ||
|
|
a834d48a38 | ||
|
|
f550386fd9 | ||
|
|
0a4b81cde4 | ||
|
|
e1cc60ea5e | ||
|
|
a40860901b | ||
|
|
f3e96718eb | ||
|
|
a00411919d | ||
|
|
b298874f0d | ||
|
|
7011d4d20f | ||
|
|
d9ce16b41a | ||
|
|
f0e0d849d5 | ||
|
|
6d2573ee95 | ||
|
|
4c77cc2282 | ||
|
|
32f6916266 | ||
|
|
3a91e082d5 | ||
|
|
2184c1c703 | ||
|
|
95ad6bade7 | ||
|
|
8bda4a5331 | ||
|
|
16c77f587a | ||
|
|
a218c791a9 | ||
|
|
dccdf55bfc | ||
|
|
1ba6e70c56 | ||
|
|
0e76184813 | ||
|
|
c674da29d5 | ||
|
|
ba4e6dfd68 | ||
|
|
980fd6e50e | ||
|
|
44e7cd7505 | ||
|
|
3a60535564 | ||
|
|
9abfeb749c | ||
|
|
0442c789f1 | ||
|
|
76dc9f0742 | ||
|
|
ff56f1441e | ||
|
|
e3db886923 | ||
|
|
249f62759e | ||
|
|
7767481633 | ||
|
|
a97b1383d8 | ||
|
|
1b224b3949 | ||
|
|
018aa33a7f | ||
|
|
4b7b8b3392 | ||
|
|
272824a72f | ||
|
|
ac02d65db0 | ||
|
|
9bcaff9fd3 | ||
|
|
7b650bf39b | ||
|
|
ee1ecb83b7 | ||
|
|
d6e37119a8 | ||
|
|
385cc3b6d2 | ||
|
|
b4936bf13b | ||
|
|
e1e9403c60 | ||
|
|
0828d50830 | ||
|
|
0817ba18bd | ||
|
|
121a80d079 | ||
|
|
932c414b0c | ||
|
|
3a38441d42 | ||
|
|
3e039d34c2 | ||
|
|
2bae132d18 | ||
|
|
bc0004574a | ||
|
|
2e208507c3 | ||
|
|
33209a6d31 | ||
|
|
13311245a5 | ||
|
|
4067357b54 | ||
|
|
fd6b69edc1 | ||
|
|
2504833a29 | ||
|
|
6d31dcb74b | ||
|
|
a149af50fd | ||
|
|
13927927b3 | ||
|
|
68edf520c5 | ||
|
|
f7933a1295 | ||
|
|
9a1c09490f | ||
|
|
c8b5c216e3 | ||
|
|
344954513a | ||
|
|
f8029bdf67 | ||
|
|
d9c7d31017 | ||
|
|
79dba0fe2c | ||
|
|
c0376182f8 | ||
|
|
8e3b5de249 | ||
|
|
44711a58c7 | ||
|
|
e7337145d2 | ||
|
|
c116b40dfc | ||
|
|
d88f7ad539 | ||
|
|
b5d61cebb3 | ||
|
|
551108e047 | ||
|
|
5feec9bf1e | ||
|
|
313691d3e9 | ||
|
|
950c99d3d7 | ||
|
|
80cc74a945 | ||
|
|
4e586253c0 | ||
|
|
08980158e4 | ||
|
|
1e917c7b89 | ||
|
|
8d3678ddcb | ||
|
|
6a58c5ea1d | ||
|
|
b9bbf22f2c | ||
|
|
c99ed0cb53 | ||
|
|
cef18d4f64 | ||
|
|
824169fc25 | ||
|
|
841b5708ae | ||
|
|
8ad110f73b | ||
|
|
0869ea09c8 | ||
|
|
0e57943d33 | ||
|
|
8b01b7c192 | ||
|
|
a7bd671309 | ||
|
|
406ad52c8d | ||
|
|
b6c4c4e852 | ||
|
|
f193d5c503 | ||
|
|
6a06cb17a3 | ||
|
|
bf218b0975 | ||
|
|
5412c479c8 | ||
|
|
ccb86ae824 | ||
|
|
001709c25c | ||
|
|
e50b8d11bf | ||
|
|
ea782af079 | ||
|
|
0ebf9f6c3d | ||
|
|
6fec467ec8 | ||
|
|
8b0f1cdfed | ||
|
|
f6008bd288 | ||
|
|
1776c8fc15 | ||
|
|
99f41b9805 | ||
|
|
d4f11fc52a | ||
|
|
750aeb39c9 | ||
|
|
0f355bc2fc | ||
|
|
6ebd3063ed | ||
|
|
b070cad92a | ||
|
|
204fbbff59 | ||
|
|
98e7760142 | ||
|
|
6c1c088d44 | ||
|
|
0b7f717057 | ||
|
|
728cb43f05 | ||
|
|
ff02f06cbc | ||
|
|
7a817cbe8c | ||
|
|
58e0876aa0 | ||
|
|
f84f846e76 | ||
|
|
ddb869e767 | ||
|
|
015d457d23 | ||
|
|
ba6a83a9cb | ||
|
|
689985ed93 | ||
|
|
bf9262f1f1 | ||
|
|
821bd363ad | ||
|
|
4a9fc65e71 | ||
|
|
81877f5abd | ||
|
|
01eed3e045 | ||
|
|
7bc423b570 | ||
|
|
dba56f8946 | ||
|
|
c1f765d5f7 | ||
|
|
7f5b5b614f | ||
|
|
5cd149b2b9 | ||
|
|
0bc2d0aaf6 | ||
|
|
1dc9a341d1 | ||
|
|
2ad1e71176 | ||
|
|
ca6973f529 | ||
|
|
323d624ec7 | ||
|
|
49f84db473 | ||
|
|
7399c402af | ||
|
|
cbfcb29691 | ||
|
|
ae616fbaf3 | ||
|
|
35275d4aa8 | ||
|
|
dda63830ec | ||
|
|
79b4f05809 | ||
|
|
b50c47b744 | ||
|
|
f7c6fdd861 | ||
|
|
47b583821e | ||
|
|
369ac113fa | ||
|
|
fc4d8b5ad0 | ||
|
|
72e1cf7d6a | ||
|
|
dd96556e2a | ||
|
|
a7ee51526a | ||
|
|
610b59f4c6 | ||
|
|
63ce327458 | ||
|
|
6ec7a50320 | ||
|
|
60a29ef2e7 | ||
|
|
52c3a0abe0 | ||
|
|
5bbe5c2720 | ||
|
|
c0c6a21e43 | ||
|
|
a7f93698a6 | ||
|
|
1a18058228 | ||
|
|
67024daa25 | ||
|
|
de6137d1a5 | ||
|
|
11e87bd519 | ||
|
|
3b4e089756 | ||
|
|
4a6f5845f9 | ||
|
|
e9684ce033 | ||
|
|
27abf1b0de | ||
|
|
f5f3f70e35 | ||
|
|
2d282a3261 | ||
|
|
a2d0d2d8c0 | ||
|
|
ea412086d4 | ||
|
|
455679a981 | ||
|
|
a21bc6174d | ||
|
|
fae1a89402 | ||
|
|
5a1a0c59f1 | ||
|
|
c98271f783 | ||
|
|
033660c2d9 | ||
|
|
1a639c51e1 | ||
|
|
1366579dd1 | ||
|
|
f5ed2c2c40 | ||
|
|
1598de9f5f | ||
|
|
6f47f5cc99 | ||
|
|
1117dcb935 | ||
|
|
01a58f9aa8 | ||
|
|
9096644d51 | ||
|
|
d42fc0e3f6 | ||
|
|
17d1526b6f | ||
|
|
db8ac195d4 | ||
|
|
c5f5f69fd6 | ||
|
|
44203223fd | ||
|
|
bdabadcd49 | ||
|
|
220e4388d4 | ||
|
|
51b2d1ce86 | ||
|
|
f3c1825c57 | ||
|
|
359dfcd102 | ||
|
|
649cdd893f | ||
|
|
0cb974acf5 | ||
|
|
9bca5dccc7 | ||
|
|
d707f1cc5d | ||
|
|
4ed4395535 | ||
|
|
0209d48475 | ||
|
|
437bee4a85 | ||
|
|
4b8e73fa57 | ||
|
|
b96f925d9a | ||
|
|
1551dc50fd | ||
|
|
daf4cef61c | ||
|
|
496e127f64 | ||
|
|
da1af66c77 | ||
|
|
01409985a2 | ||
|
|
a86ac0619a | ||
|
|
385d165512 | ||
|
|
fcfc97b964 | ||
|
|
53d05e73a6 | ||
|
|
40a1eb861d | ||
|
|
1d3cb95b89 | ||
|
|
010303a818 | ||
|
|
7281b9fed0 | ||
|
|
c5785c7ae6 | ||
|
|
698731cb9a | ||
|
|
99bae8f2c5 | ||
|
|
5034ec1aa0 | ||
|
|
be821c1f8b | ||
|
|
a364b79a9b | ||
|
|
0aa6e1e79a | ||
|
|
d2a6379131 | ||
|
|
07ce61d19f | ||
|
|
ad9f2d8609 | ||
|
|
57702d8a43 | ||
|
|
9a30af3818 | ||
|
|
23751fbdf3 | ||
|
|
db6d181f78 | ||
|
|
56ca4dcc61 | ||
|
|
d08b537721 | ||
|
|
c1a2edb489 | ||
|
|
164b7fa454 | ||
|
|
428483cf4c | ||
|
|
89f02eb399 | ||
|
|
4ce127516c | ||
|
|
6f4f487dfe | ||
|
|
57dd60e804 | ||
|
|
d24fd2250c | ||
|
|
e00bcdfc06 | ||
|
|
67dd204ee6 | ||
|
|
e130583fa4 | ||
|
|
6ad20a0fcf | ||
|
|
9fad644d7a | ||
|
|
8cb523dd06 | ||
|
|
b50ee23a26 | ||
|
|
a973cf699b | ||
|
|
102abf49c4 | ||
|
|
8929e503ea | ||
|
|
a2a794eb74 | ||
|
|
2e95f5dd95 | ||
|
|
23a42230cb | ||
|
|
e66daed7ae | ||
|
|
f2d0f68f07 | ||
|
|
be4af0bf84 | ||
|
|
15172e8b33 | ||
|
|
c6b7918525 | ||
|
|
4f1c4421e5 | ||
|
|
7b830f5a71 | ||
|
|
6ce1135835 | ||
|
|
66eaaef6de | ||
|
|
48fbf59036 | ||
|
|
0e0f4fbe9f | ||
|
|
6ba6e10108 | ||
|
|
cf8fa4ffb4 | ||
|
|
2b0180dce2 | ||
|
|
caee848a47 | ||
|
|
d01b7cf707 | ||
|
|
8921f84c38 | ||
|
|
6830df01fa | ||
|
|
67da59ef41 | ||
|
|
036cc9915d | ||
|
|
57b6ba19f0 | ||
|
|
32b5593d07 | ||
|
|
3b757562d2 | ||
|
|
4c8c2d0868 | ||
|
|
b2f266401f | ||
|
|
5541eca23d | ||
|
|
122d65840e | ||
|
|
ed4b72d85c | ||
|
|
4033e8618f | ||
|
|
42cada1811 | ||
|
|
ff7ed213c8 | ||
|
|
a0367af06b | ||
|
|
58c2965c53 | ||
|
|
edae41409a | ||
|
|
383d3a36ef | ||
|
|
6a958da390 | ||
|
|
ef154f99d5 | ||
|
|
4a20102de3 | ||
|
|
4e170f2917 | ||
|
|
a70d788f6b |
No files matched your search
@@ -1,82 +0,0 @@
|
||||
name: Assets
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: main
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
asset_repo:
|
||||
type: choice
|
||||
required: false
|
||||
description: What asset repo to write to
|
||||
options:
|
||||
- blitzkit/assets
|
||||
- blitzkit/assets-preview
|
||||
- blitzkit/assets-dev
|
||||
default: blitzkit/assets
|
||||
|
||||
asset_source:
|
||||
type: string
|
||||
required: false
|
||||
description: "Archive URL or Steam to download (example: steam:APP_ID/DEPOT_ID or zip:URL_TO_ZIP)"
|
||||
default: "steam:444200/444202"
|
||||
|
||||
schedule:
|
||||
- cron: "0 2 * * *"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.asset_repo }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||
WOTB_DLC_CDN: ${{ secrets.WOTB_DLC_CDN }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_ASSET_REPO: ${{ github.event.inputs.asset_repo != '' && github.event.inputs.asset_repo || secrets.PUBLIC_ASSET_REPO }}
|
||||
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
|
||||
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 }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }}
|
||||
STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }}
|
||||
|
||||
jobs:
|
||||
build_assets:
|
||||
name: Build assets
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GH_TOKEN }}
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun run setup
|
||||
|
||||
- name: AppArmor hack
|
||||
run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
|
||||
|
||||
- name: Build assets
|
||||
run: bun run build:assets --all-targets --source=${{ github.event.inputs.asset_source || 'steam:444200/444202' }}
|
||||
@@ -1,54 +0,0 @@
|
||||
name: Averages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 8 * * *"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
|
||||
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
|
||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||
ALLOW_ROBOTS: "true"
|
||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
|
||||
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
|
||||
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
|
||||
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GH_TOKEN }}
|
||||
- run: bun run setup
|
||||
|
||||
- name: Build averages
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
run: bun run build:averages --production
|
||||
@@ -1,34 +0,0 @@
|
||||
name: Bot
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build bot
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GH_TOKEN }}
|
||||
- run: bun run setup
|
||||
|
||||
- name: Build bot
|
||||
run: bun run build:bot
|
||||
|
||||
- name: Archive build
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: bot
|
||||
path: dist/bot
|
||||
@@ -1,36 +0,0 @@
|
||||
name: 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 }}
|
||||
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 }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GH_TOKEN }}
|
||||
- run: bun run setup
|
||||
|
||||
- name: Discover IDs
|
||||
run: bun build:ids --production
|
||||
@@ -1,61 +0,0 @@
|
||||
name: Host
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_ASSET_FAST_USER: ${{ secrets.PUBLIC_ASSET_FAST_USER }}
|
||||
PUBLIC_ASSET_FAST_REPO: ${{ secrets.PUBLIC_ASSET_FAST_REPO }}
|
||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||
ALLOW_ROBOTS: ${{ secrets.ALLOW_ROBOTS }}
|
||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
|
||||
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
|
||||
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
|
||||
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
|
||||
PUBLIC_BRANCH: ${{ secrets.PUBLIC_BRANCH }}
|
||||
PUBLIC_GUIDE_WEBSITE: ${{ secrets.PUBLIC_GUIDE_WEBSITE }}
|
||||
SERVICING: ${{ secrets.SERVICING }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
jobs:
|
||||
host:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Download release assets
|
||||
run: |
|
||||
DOWNLOAD_URL=$(curl -s https://api.github.com/repos/blitzkit/blitzkit/releases/latest \
|
||||
| grep "browser_download_url.*bot.zip" \
|
||||
| cut -d : -f 2,3 \
|
||||
| tr -d \")
|
||||
curl -L -o bot.zip $DOWNLOAD_URL
|
||||
mkdir -p dist/bot
|
||||
unzip bot.zip -d dist/bot
|
||||
|
||||
- name: Install sharp
|
||||
run: bun init -y && bun add sharp
|
||||
|
||||
- name: Move directories
|
||||
run: mv dist/bot/prisma .
|
||||
|
||||
- name: Run bot
|
||||
run: bun run dist/bot/main.js
|
||||
@@ -1,46 +0,0 @@
|
||||
name: Rating Asia
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 17 * * *"
|
||||
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
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 }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
jobs:
|
||||
host:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GH_TOKEN }}
|
||||
- run: bun run setup
|
||||
|
||||
- name: Generate leaderboard
|
||||
run: bun build:ratingleaderboard --region=asia
|
||||
@@ -1,46 +0,0 @@
|
||||
name: Rating Europe
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 23 * * *"
|
||||
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
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 }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
jobs:
|
||||
host:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GH_TOKEN }}
|
||||
- run: bun run setup
|
||||
|
||||
- name: Generate leaderboard
|
||||
run: bun build:ratingleaderboard --region=eu
|
||||
@@ -1,46 +0,0 @@
|
||||
name: Rating North America
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 5 * * *"
|
||||
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
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 }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
jobs:
|
||||
host:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GH_TOKEN }}
|
||||
- run: bun run setup
|
||||
|
||||
- name: Generate leaderboard
|
||||
run: bun build:ratingleaderboard --region=com
|
||||
@@ -1,38 +0,0 @@
|
||||
name: Reviews
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: 0 0 * * 2
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GH_TOKEN }}
|
||||
- run: bun run setup
|
||||
|
||||
- name: Find reviews
|
||||
run: bun run build:reviews
|
||||
@@ -0,0 +1,110 @@
|
||||
name: Website
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
# schedule:
|
||||
# - cron: "0 4 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
env:
|
||||
# blitz
|
||||
WOTB_SERVER: ${{ secrets.WOTB_SERVER }}
|
||||
WOTB_VERSION: ${{ secrets.WOTB_VERSION }}
|
||||
PUBLIC_WOTB_CLIENT: ../../temp/game
|
||||
WOTB_USMAP: ../closed/blitz.usmap
|
||||
|
||||
# google
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
||||
|
||||
# steam
|
||||
STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }}
|
||||
STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }}
|
||||
STEAM_APP: 3341250
|
||||
STEAM_OS: windows
|
||||
STEAM_FINGERPRINT: Manifest_UFSFiles_Win64.txt
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build chunk ${{ matrix.chunk }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
chunk: [pages, api, media]
|
||||
|
||||
steps:
|
||||
- name: Free disk space
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Set up Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- name: Set up Dotnet
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: "10.0.x"
|
||||
|
||||
- name: Set up Protoc
|
||||
uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- name: Set up project
|
||||
run: |
|
||||
bun run setup
|
||||
./scripts/setup_depot_downloader.sh
|
||||
|
||||
- name: Download fingerprint
|
||||
run: |
|
||||
./scripts/download_fingerprint.sh
|
||||
|
||||
# - name: Download cached game
|
||||
# uses: actions/cache@v5
|
||||
# id: game-cache
|
||||
# with:
|
||||
# path: temp/game
|
||||
# key: ${{ runner.os }}-${{ hashFiles(format('temp/game/{0}', env.FINGERPRINT)) }}
|
||||
|
||||
- name: Download Steam game
|
||||
# if: steps.game-cache.outputs.cache-hit != 'true'
|
||||
run: scripts/download_game.sh
|
||||
|
||||
# - name: Build
|
||||
# run: bun run build-website
|
||||
# env:
|
||||
# BUILD_CHUNK: ${{ matrix.chunk }}
|
||||
|
||||
# - name: Upload artifact
|
||||
# id: upload
|
||||
# uses: actions/upload-pages-artifact@v4
|
||||
# with:
|
||||
# path: dist/website
|
||||
|
||||
# - name: Trigger target repository
|
||||
# run: |
|
||||
# curl -X POST \
|
||||
# -H "Authorization: token ${{ secrets.GH_TOKEN }}" \
|
||||
# -H "Accept: application/vnd.github.v3+json" \
|
||||
# https://github.com \
|
||||
# -d '{ "event_type": "deploy-pages", "client_payload": { "artifact_url": "${{ steps.upload.outputs.artifact_url }}" } }'
|
||||
@@ -1,72 +0,0 @@
|
||||
name: Website
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
schedule:
|
||||
- cron: "0 4 * * *"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
env:
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||
ALLOW_ROBOTS: ${{ secrets.ALLOW_ROBOTS }}
|
||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||
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 }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build website
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: arduino/setup-protoc@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GH_TOKEN }}
|
||||
- uses: actions/configure-pages@v5
|
||||
- run: bun run setup
|
||||
|
||||
- name: Build website
|
||||
run: bun run build:website
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: dist/website
|
||||
|
||||
deploy-github-pages:
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
name: Deploy to GitHub Pages
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
environment:
|
||||
name: Production
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -22,7 +22,7 @@ depots
|
||||
public/sitemap*
|
||||
/firebase.json
|
||||
target
|
||||
# packages/core/src/protos/*.ts
|
||||
packages/website-ue/src/protos/*.ts
|
||||
/.vercel
|
||||
.astro
|
||||
.vercel
|
||||
@@ -53,3 +53,4 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
*.lscache
|
||||
@@ -1,3 +1,6 @@
|
||||
[submodule "packages/closed"]
|
||||
path = packages/closed
|
||||
url = https://github.com/blitzkit/closed.git
|
||||
[submodule "packages/cue4parse"]
|
||||
path = packages/cue4parse
|
||||
url = https://github.com/tresabhi/CUE4Parse.git
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"tabWidth": 2
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,257 +0,0 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"AABBOX",
|
||||
"Abhi",
|
||||
"absarc",
|
||||
"accountinfo",
|
||||
"adsense",
|
||||
"aeson",
|
||||
"alphatest",
|
||||
"analyticsdata",
|
||||
"analyticsreporting",
|
||||
"APAC",
|
||||
"apcr",
|
||||
"appinfo",
|
||||
"applypatches",
|
||||
"artefacts",
|
||||
"astrojs",
|
||||
"astrolib",
|
||||
"atgm",
|
||||
"autocrop",
|
||||
"Autoloader",
|
||||
"Autoreloader",
|
||||
"AYUV",
|
||||
"battleresults",
|
||||
"bbox",
|
||||
"beforeinstallprompt",
|
||||
"bepaint",
|
||||
"betakrieg",
|
||||
"BINORMAL",
|
||||
"Binormals",
|
||||
"bkni",
|
||||
"BKRL",
|
||||
"BKRTS",
|
||||
"BKRTSMANIFEST",
|
||||
"bkwd",
|
||||
"blitzkit",
|
||||
"blitzrinth",
|
||||
"blitzstars",
|
||||
"bufbuild",
|
||||
"cacheable",
|
||||
"Callouts",
|
||||
"Camo",
|
||||
"Carro",
|
||||
"CCPAOPTOUT",
|
||||
"CDON",
|
||||
"clanid",
|
||||
"classname",
|
||||
"cleandist",
|
||||
"closedkrieg",
|
||||
"cockroackdb",
|
||||
"Coef",
|
||||
"Combattimento",
|
||||
"cooldown",
|
||||
"corelations",
|
||||
"corsproxy",
|
||||
"CUBEMAP",
|
||||
"CUBETEXCOORD",
|
||||
"customperiod",
|
||||
"datasource",
|
||||
"Dava",
|
||||
"dbid",
|
||||
"Ddpf",
|
||||
"Ddsd",
|
||||
"deadrail",
|
||||
"debugflags",
|
||||
"deburr",
|
||||
"decalmask",
|
||||
"decompressor",
|
||||
"décor",
|
||||
"dedup",
|
||||
"defence",
|
||||
"degressive",
|
||||
"depotdownloader",
|
||||
"devkrieg",
|
||||
"diacriticless",
|
||||
"DIDS",
|
||||
"donotremove",
|
||||
"drei",
|
||||
"Droodles",
|
||||
"dvpd",
|
||||
"dvpl",
|
||||
"DVPLCRC",
|
||||
"dvpm",
|
||||
"dxgi",
|
||||
"easimon",
|
||||
"echarts",
|
||||
"Everytime",
|
||||
"FASTNAME",
|
||||
"favicons",
|
||||
"flexdirection",
|
||||
"fontsource",
|
||||
"FOURCC",
|
||||
"frameloop",
|
||||
"fsformat",
|
||||
"Fullscreen",
|
||||
"fullstats",
|
||||
"fuzzysort",
|
||||
"gamemode",
|
||||
"gameplay",
|
||||
"glsl",
|
||||
"GLTF",
|
||||
"GSOR",
|
||||
"gtag",
|
||||
"haha",
|
||||
"Herdbreaker",
|
||||
"Hetz",
|
||||
"highp",
|
||||
"iife",
|
||||
"imgur",
|
||||
"immer",
|
||||
"Inclusivity",
|
||||
"JOINTINDEX",
|
||||
"JOINTWEIGHT",
|
||||
"kranvagn",
|
||||
"krieg",
|
||||
"Leaderboard",
|
||||
"leaderboards",
|
||||
"lerp",
|
||||
"LINEARSIZE",
|
||||
"linktarget",
|
||||
"lmao",
|
||||
"loadouts",
|
||||
"loddist",
|
||||
"lods",
|
||||
"lossrate",
|
||||
"Maddoxkkm",
|
||||
"malum",
|
||||
"mantlets",
|
||||
"mediump",
|
||||
"metallicness",
|
||||
"metalness",
|
||||
"minitelrose",
|
||||
"Minotauro",
|
||||
"MIPMAPCOUNT",
|
||||
"mišek",
|
||||
"mmnow",
|
||||
"modeltransform",
|
||||
"MURICA",
|
||||
"nanostores",
|
||||
"NEGATIVEX",
|
||||
"NEGATIVEY",
|
||||
"NEGATIVEZ",
|
||||
"networkidle",
|
||||
"NITROPAY",
|
||||
"nivo",
|
||||
"nmatname",
|
||||
"noout",
|
||||
"normalmap",
|
||||
"oneof",
|
||||
"oopsies",
|
||||
"Oopsy",
|
||||
"opentest",
|
||||
"OPTML",
|
||||
"otemp",
|
||||
"outdir",
|
||||
"outfile",
|
||||
"ownedtanks",
|
||||
"padder",
|
||||
"partytown",
|
||||
"patchnotes",
|
||||
"PIXELFORMAT",
|
||||
"playerachievements",
|
||||
"playerinfo",
|
||||
"playform",
|
||||
"pngjs",
|
||||
"POSITIVEX",
|
||||
"POSITIVEY",
|
||||
"POSITIVEZ",
|
||||
"precompute",
|
||||
"Promisable",
|
||||
"proto",
|
||||
"protobuf",
|
||||
"protoc",
|
||||
"protos",
|
||||
"proxyclient",
|
||||
"prunestrings",
|
||||
"Pyogenics",
|
||||
"ratingleaderboard",
|
||||
"raycaster",
|
||||
"Redirector",
|
||||
"Relogin",
|
||||
"researchable",
|
||||
"Resvg",
|
||||
"rizz",
|
||||
"roboto",
|
||||
"rtsc",
|
||||
"RTSM",
|
||||
"SCPG",
|
||||
"scroller",
|
||||
"searchclans",
|
||||
"searchplayers",
|
||||
"searchtanks",
|
||||
"SHAREDEXP",
|
||||
"SKLLD",
|
||||
"smoothstep",
|
||||
"Smri",
|
||||
"Specularity",
|
||||
"SSAO",
|
||||
"startcase",
|
||||
"statkit",
|
||||
"statsfull",
|
||||
"steamapps",
|
||||
"Stringifying",
|
||||
"struct",
|
||||
"structs",
|
||||
"strvk",
|
||||
"subnation",
|
||||
"Synx",
|
||||
"tankaverages",
|
||||
"Tankerch",
|
||||
"tankmen",
|
||||
"tankopedia",
|
||||
"tankopedias",
|
||||
"tankstats",
|
||||
"tauri",
|
||||
"texconv",
|
||||
"TEXCOORD",
|
||||
"tresabhi",
|
||||
"tungstentip",
|
||||
"Turretless",
|
||||
"TYPELESS",
|
||||
"ubjson",
|
||||
"Unicum",
|
||||
"UNLOCALIZED",
|
||||
"UNORM",
|
||||
"unpickled",
|
||||
"Unregistering",
|
||||
"unrotate",
|
||||
"unwrapper",
|
||||
"userns",
|
||||
"vercel",
|
||||
"vickers",
|
||||
"vitepress",
|
||||
"Vovko",
|
||||
"wargaming",
|
||||
"webp",
|
||||
"wgpkg",
|
||||
"whitespaces",
|
||||
"WHSS",
|
||||
"winrate",
|
||||
"wireframe",
|
||||
"Woah",
|
||||
"WOTB",
|
||||
"wotblitz",
|
||||
"wotbreplay",
|
||||
"WOTINSPECTOR",
|
||||
"xffn",
|
||||
"xkdkw",
|
||||
"XMLDVPL",
|
||||
"YAMLDVPL",
|
||||
"yolo",
|
||||
"youtuber",
|
||||
"youtubers",
|
||||
"zoomable"
|
||||
],
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": ".",
|
||||
},
|
||||
{
|
||||
"path": "C:/Program Files/WindowsApps/7458BE2C.WorldofTanksBlitz_11.1.256.0_x64__x4tje2y229k00/Data",
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"words": [
|
||||
"gltf",
|
||||
"amogos",
|
||||
"Tankopedia"
|
||||
]
|
||||
}
|
||||
@@ -1,51 +1,19 @@
|
||||
{
|
||||
"name": "blitzkit",
|
||||
"version": "1.30.2",
|
||||
"version": "2.0.0-beta.3",
|
||||
"license": "BlitzKit License",
|
||||
"private": true,
|
||||
"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 && bun run build",
|
||||
"build:prisma": "prisma generate",
|
||||
"build:proxyclient": "cd packages/scripts && bun run build:proxyclient",
|
||||
"build:catalogitemaccesssor": "cd packages/scripts && bun run build:catalogitemaccesssor",
|
||||
"build:ratingleaderboard": "cd packages/scripts && bun run build:ratingleaderboard",
|
||||
"build:app": "cd packages/app && bun run build",
|
||||
"dev:bot": "bun run build:bot --dev && bun start:bot --dev",
|
||||
"start:bot": "bun dist/bot/main.js",
|
||||
"dev:docs": "vitepress dev docs",
|
||||
"dev:website": "cd packages/website && bun run dev",
|
||||
"dev:app": "cd packages/app && bun run dev",
|
||||
"dev": "bun run dev:website",
|
||||
"dev:api": "bun --filter=@blitzkit/api run dev",
|
||||
"setup:depotdownloader": "bun --filter=@blitzkit/scripts run setup:depotdownloader",
|
||||
"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": "cd packages/scripts && bun run build:protos",
|
||||
"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",
|
||||
"setup": "bun install && bun run build:prisma && bun run build:proxyclient",
|
||||
"patchnotes": "cd packages/scripts && bun run patchnotes"
|
||||
"setup": "chmod a+x scripts/*.sh && ./scripts/setup.sh",
|
||||
"dev": "./scripts/dev.sh",
|
||||
"build-game": "./scripts/build_game.sh",
|
||||
"build-website": "./scripts/build_website.sh"
|
||||
},
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.18.0",
|
||||
"prisma": "^6.18.0",
|
||||
"sharp": "^0.34.5",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,50 @@
|
||||
export * from './assertSecret';
|
||||
export * from './asset';
|
||||
export * from './availableProvisions';
|
||||
export * from './averageDefinitions';
|
||||
export * from "./assertSecret";
|
||||
export * from "./asset";
|
||||
export * from "./availableProvisions";
|
||||
export * from "./averageDefinitions";
|
||||
// export * from './camouflageDefinitions';
|
||||
// export * from './checkConsumableProvisionInclusivity';
|
||||
export * from './coefficient';
|
||||
export * from './consumableDefinitions';
|
||||
export * from "./coefficient";
|
||||
export * from "./consumableDefinitions";
|
||||
// export * from './context';
|
||||
export * from './createDefaultSkills';
|
||||
export * from './degressiveStat';
|
||||
export * from './discoveredIdDefinitions';
|
||||
export * from './emblemURL';
|
||||
export * from './equipmentDefinitions';
|
||||
export * from './eventManager';
|
||||
export * from './gameDefinitions';
|
||||
export * from './getArchivedLatestSeasonNumber';
|
||||
export * from "./createDefaultSkills";
|
||||
export * from "./degressiveStat";
|
||||
export * from "./discoveredIdDefinitions";
|
||||
export * from "./emblemURL";
|
||||
export * from "./equipmentDefinitions";
|
||||
export * from "./eventManager";
|
||||
export * from "./gameDefinitions";
|
||||
export * from "./getArchivedLatestSeasonNumber";
|
||||
// export * from './getArchivedRatingInfo';
|
||||
export * from './getArchivedRatingLeaderboard';
|
||||
export * from "./getArchivedRatingLeaderboard";
|
||||
// export * from './getArchivedRatingMidnightLeaderboard';
|
||||
export * from './getTimeDaysAgo';
|
||||
export * from './isExplosive';
|
||||
export * from './mapDefinitions';
|
||||
export * from './modelDefinitions';
|
||||
export * from './normalizeBoundingBox';
|
||||
export * from "./getTimeDaysAgo";
|
||||
export * from "./isExplosive";
|
||||
export * from "./mapDefinitions";
|
||||
export * from "./modelDefinitions";
|
||||
export * from "./normalizeBoundingBox";
|
||||
// export * from './parseBkni';
|
||||
// export * from './patientFetch';
|
||||
export * from './patientFetchJSON';
|
||||
export * from './progressiveStat';
|
||||
export * from './provisionDefinitions';
|
||||
export * from './resolveDpm';
|
||||
export * from './resolvePenetrationCoefficient';
|
||||
export * from './reviews';
|
||||
export * from './skillDefinitions';
|
||||
export * from './sum';
|
||||
export * from './tankDefinitions';
|
||||
export * from './tankIcon';
|
||||
export * from './tankNames';
|
||||
export * from './unionBoundingBox';
|
||||
export * from './uniqueGuns';
|
||||
export * from './unwrapBlitzkitResponse';
|
||||
export * from './votes';
|
||||
export * from "./patientFetchJSON";
|
||||
export * from "./progressiveStat";
|
||||
export * from "./provisionDefinitions";
|
||||
export * from "./resolveDpm";
|
||||
export * from "./resolvePenetrationCoefficient";
|
||||
export * from "./reviews";
|
||||
export * from "./skillDefinitions";
|
||||
export * from "./sum";
|
||||
export * from "./tankDefinitions";
|
||||
export * from "./tankIcon";
|
||||
export * from "./tankNames";
|
||||
export * from "./unionBoundingBox";
|
||||
export * from "./uniqueGuns";
|
||||
export * from "./unwrapBlitzkitResponse";
|
||||
export * from "./votes";
|
||||
// export * from './withCORSProxy';
|
||||
export * from './createDefaultProvisions';
|
||||
export * from './gallery';
|
||||
export * from './metaSortTank';
|
||||
export * from './popularTanks';
|
||||
export * from './romanize';
|
||||
export * from './youtubers';
|
||||
export * from "./createDefaultProvisions";
|
||||
export * from "./gallery";
|
||||
export * from "./metaSortTank";
|
||||
export * from "./popularTanks";
|
||||
export * from "./romanize";
|
||||
export * from "./sluggify.ts";
|
||||
export * from "./youtubers";
|
||||
@@ -0,0 +1,16 @@
|
||||
import { deburr } from "lodash-es";
|
||||
|
||||
const nonAlphanumericRegex = /[^a-z0-9]/g;
|
||||
const multipleDashesRegex = /--+/g;
|
||||
const trailingDashRegex = /-$/g;
|
||||
const leadingDashRegex = /^-/g;
|
||||
|
||||
export function sluggify(value: string) {
|
||||
let slug = deburr(value).toLowerCase();
|
||||
slug = slug.replaceAll(nonAlphanumericRegex, "-");
|
||||
slug = slug.replaceAll(multipleDashesRegex, "-");
|
||||
slug = slug.replaceAll(trailingDashRegex, "");
|
||||
slug = slug.replaceAll(leadingDashRegex, "");
|
||||
|
||||
return slug;
|
||||
}
|
||||
@@ -4,7 +4,6 @@ export * from "./imgur";
|
||||
export * from "./math";
|
||||
export * from "./patreon";
|
||||
export * from "./protobuf";
|
||||
export * from "./protos";
|
||||
export * from "./statistics";
|
||||
export * from "./streams";
|
||||
export * from "./types";
|
||||
@@ -3,6 +3,7 @@ export * from "./fisherYates";
|
||||
export * from "./formatCompact";
|
||||
export * from "./isNumber";
|
||||
export * from "./NaNFallback";
|
||||
export * from "./normalizeAngleDeg";
|
||||
export * from "./normalizeAngleRad";
|
||||
export * from "./normalToEuler";
|
||||
export * from "./numberToRomanNumeral";
|
||||
@@ -0,0 +1,6 @@
|
||||
export function normalizeAngleDeg(angle: number) {
|
||||
const newAngle = angle % 360;
|
||||
if (newAngle > 360) return newAngle - 360;
|
||||
if (newAngle < -360) return newAngle + 360;
|
||||
return newAngle;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
[*.cs]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
@@ -0,0 +1,57 @@
|
||||
## A streamlined .gitignore for modern .NET projects
|
||||
## including temporary files, build results, and
|
||||
## files generated by popular .NET tools. If you are
|
||||
## developing with Visual Studio, the VS .gitignore
|
||||
## https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
## has more thorough IDE-specific entries.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/Dotnet.gitignore
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# Others
|
||||
~$*
|
||||
*~
|
||||
CodeCoverage/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
@@ -0,0 +1,165 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using BlitzKit.Game.Models;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.Compression;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Assets.Objects.Properties;
|
||||
using CUE4Parse.UE4.IO.Objects;
|
||||
using CUE4Parse.UE4.Objects.Engine;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.JavaScript.NodeApi;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace BlitzKit.Game.JSExport;
|
||||
|
||||
[JSExport]
|
||||
public partial class GameInterface
|
||||
{
|
||||
private readonly BlitzFileProvider provider;
|
||||
|
||||
public HashSet<string> files;
|
||||
public string[] tanksDirectoryNations;
|
||||
|
||||
public GameInterface(string directory, string map, string temp)
|
||||
{
|
||||
provider = new(directory, map);
|
||||
|
||||
files = [.. provider.Files.Keys];
|
||||
|
||||
var tanksBase = "Blitz/Content/Tanks/";
|
||||
tanksDirectoryNations =
|
||||
[
|
||||
.. files
|
||||
.Where(path => path.StartsWith(tanksBase))
|
||||
.Select(path => path[tanksBase.Length..])
|
||||
.Select(path => path.Split('/')[0])
|
||||
.Where(nation => !nation.EndsWith(".uasset") && nation != "TankStub")
|
||||
.Distinct(),
|
||||
];
|
||||
|
||||
string oodlePath = $"{temp}/oodle.so";
|
||||
OodleHelper.DownloadOodleDll(ref oodlePath!);
|
||||
OodleHelper.Initialize(oodlePath);
|
||||
}
|
||||
|
||||
public HashSet<string> Files => files;
|
||||
public string[] TanksDirectoryNations => tanksDirectoryNations;
|
||||
|
||||
public byte[] TankBigIcon(string tankId, string pdaName)
|
||||
{
|
||||
foreach (var nation in tanksDirectoryNations)
|
||||
{
|
||||
var path = $"Blitz/Content/Tanks/{nation}/{tankId}/{pdaName}.{pdaName}";
|
||||
|
||||
if (provider.TryLoadPackageObject<UPrimaryDataAsset>(path, out var pda))
|
||||
{
|
||||
var icon = pda.Get<FSoftObjectPath>("BigIcon");
|
||||
var uTexture = icon.Load<UTexture2D>();
|
||||
var cTexture = uTexture.Decode(ETexturePlatform.DesktopMobile);
|
||||
|
||||
if (cTexture is null)
|
||||
{
|
||||
Console.WriteLine($"No tank big icon found for {tankId} in {pdaName}");
|
||||
return [];
|
||||
}
|
||||
|
||||
var bitmap = cTexture.ToSkBitmap();
|
||||
var data = bitmap.Encode(SKEncodedImageFormat.Webp, 80);
|
||||
|
||||
return data.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"No tank big icon found for {tankId} in {pdaName}");
|
||||
return [];
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"T_UI_Flag_(\w+)_S")]
|
||||
private static partial Regex FlagNameRegex();
|
||||
|
||||
public byte[] Flag(string nation)
|
||||
{
|
||||
var flagsBase = "Blitz/Content/UI/Textures/Flag/TankCard/";
|
||||
|
||||
foreach (var path in provider.Files.Keys)
|
||||
{
|
||||
if (!path.StartsWith(flagsBase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var fileName = Path.GetFileNameWithoutExtension(path);
|
||||
var match = FlagNameRegex().Match(fileName);
|
||||
|
||||
if (
|
||||
match.Success
|
||||
&& match.Groups[1].Value.Equals(nation, StringComparison.CurrentCultureIgnoreCase)
|
||||
)
|
||||
{
|
||||
if (provider.TryLoadPackageObject<UTexture2D>($"{path}.{fileName}", out var uTexture))
|
||||
{
|
||||
CTexture? cTexture = uTexture.Decode(ETexturePlatform.DesktopMobile);
|
||||
|
||||
if (cTexture is null)
|
||||
{
|
||||
Console.WriteLine($"No flag found for {nation}");
|
||||
return [];
|
||||
}
|
||||
|
||||
SKBitmap bitmap = cTexture.ToSkBitmap();
|
||||
SKData data = bitmap.Encode(SKEncodedImageFormat.Webp, 80);
|
||||
|
||||
return data.ToArray();
|
||||
}
|
||||
|
||||
Console.WriteLine($"No flag found for {nation}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine($"No flag found for {nation}");
|
||||
return [];
|
||||
}
|
||||
|
||||
private static readonly string equipmentPDAPrefix =
|
||||
"Blitz/Content/TanksStuff/Equipment/PDA_Equipment";
|
||||
private static readonly string equipmentPDASuffix = ".uasset";
|
||||
|
||||
public byte[] EquipmentIcon(string[] names)
|
||||
{
|
||||
List<string> candidates = [];
|
||||
|
||||
foreach (var name in names)
|
||||
{
|
||||
candidates.AddRange([
|
||||
$"{equipmentPDAPrefix}{name}{equipmentPDASuffix}",
|
||||
$"{equipmentPDAPrefix}_{name}{equipmentPDASuffix}",
|
||||
]);
|
||||
}
|
||||
|
||||
string? candidate = null;
|
||||
foreach (var c in candidates)
|
||||
{
|
||||
if (!files.Contains(c))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
candidate = c;
|
||||
break;
|
||||
}
|
||||
|
||||
if (candidate is null)
|
||||
{
|
||||
Console.WriteLine($"No equipment pda found for {string.Join(", ", names)}");
|
||||
return [];
|
||||
}
|
||||
|
||||
var pda = provider.PDA(candidate);
|
||||
var icon = pda.Get<FSoftObjectPath>("Icon");
|
||||
|
||||
return provider.Image(icon);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.Compression;
|
||||
using CUE4Parse.FileProvider;
|
||||
using CUE4Parse.MappingsProvider;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Exceptions;
|
||||
using CUE4Parse.UE4.Objects.Engine;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse.Utils;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace BlitzKit.Game.Models;
|
||||
|
||||
public class BlitzFileProvider : DefaultFileProvider
|
||||
{
|
||||
public BlitzFileProvider(string directory, string map)
|
||||
: base(
|
||||
directory: directory,
|
||||
searchOption: SearchOption.AllDirectories,
|
||||
versions: new(EGame.GAME_UE5_5),
|
||||
pathComparer: StringComparer.OrdinalIgnoreCase
|
||||
)
|
||||
{
|
||||
MappingsContainer = new FileUsmapTypeMappingsProvider(map);
|
||||
|
||||
Initialize();
|
||||
Mount();
|
||||
}
|
||||
|
||||
public UPrimaryDataAsset PDA(string path)
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension(path);
|
||||
var combined = $"{path}.{name}";
|
||||
return LoadPackageObject<UPrimaryDataAsset>(combined);
|
||||
}
|
||||
|
||||
public byte[] Image(FSoftObjectPath path)
|
||||
{
|
||||
var uTexture = path.Load<UTexture2D>();
|
||||
return Image(uTexture);
|
||||
}
|
||||
|
||||
private readonly SKEncodedImageFormat imageFormat = SKEncodedImageFormat.Webp;
|
||||
private readonly int imageQuality = 80;
|
||||
|
||||
public byte[] Image(UTexture2D uTexture)
|
||||
{
|
||||
var cTexture = uTexture.Decode(ETexturePlatform.DesktopMobile);
|
||||
|
||||
ArgumentNullException.ThrowIfNull(cTexture);
|
||||
|
||||
var bitmap = cTexture.ToSkBitmap();
|
||||
var data = bitmap.Encode(imageFormat, imageQuality);
|
||||
|
||||
return data.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>BlitzKit.Game</RootNamespace>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<NodeApiAssemblyJSModuleType>esm</NodeApiAssemblyJSModuleType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../cue4parse/CUE4Parse/CUE4Parse.csproj" />
|
||||
<ProjectReference Include="../cue4parse/CUE4Parse-Conversion/CUE4Parse-Conversion.csproj" />
|
||||
<PackageReference Include="Microsoft.JavaScript.NodeApi" Version="0.9.19" />
|
||||
<PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.9.19" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,3 @@
|
||||
<Solution>
|
||||
<Project Path="game.csproj" />
|
||||
</Solution>
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@blitzkit/game",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"build": "dotnet build"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-api-dotnet": "^0.9.19"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "../bin/Debug/net9.0/linux-x64/game";
|
||||
@@ -1,4 +1,2 @@
|
||||
export * from "./literals";
|
||||
export * from "./strings";
|
||||
export * from "./unwrapper";
|
||||
export * from "./wrapper";
|
||||
@@ -1,16 +1,45 @@
|
||||
export function literals(
|
||||
string: string,
|
||||
literals?: Record<string, string | number | undefined>
|
||||
literals?: Record<string, string | number | undefined>,
|
||||
) {
|
||||
for (const key in literals) {
|
||||
const mutated = string.replaceAll(`{${key}}`, `${literals[key]}`);
|
||||
|
||||
if (import.meta.env.DEV && mutated === string) {
|
||||
console.warn(`Could not find literal {${key}} in string "${string}"`);
|
||||
}
|
||||
|
||||
string = mutated;
|
||||
string = string.replaceAll(`{${key}}`, `${literals[key]}`);
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
export function literalsArray<Type, Literal = Type | string>(
|
||||
string: string,
|
||||
literals?: Record<string, Literal>,
|
||||
) {
|
||||
if (!literals) return [string];
|
||||
|
||||
let array: (string | Literal)[] = [string];
|
||||
|
||||
for (const key in literals) {
|
||||
const value = literals[key];
|
||||
const next: (string | Literal)[] = [];
|
||||
|
||||
for (const chunk of array) {
|
||||
if (typeof chunk !== "string") {
|
||||
next.push(chunk);
|
||||
continue;
|
||||
}
|
||||
|
||||
const parts = chunk.split(`{${key}}`);
|
||||
|
||||
for (let index = 0; index < parts.length; index++) {
|
||||
if (parts[index]) next.push(parts[index]);
|
||||
|
||||
if (index < parts.length - 1) {
|
||||
next.push(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
array = next;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
@@ -1,68 +1,3 @@
|
||||
import { merge } from "ts-deepmerge";
|
||||
import locales from "../locales.json";
|
||||
import en from "../strings/en.json";
|
||||
import es from "../strings/es.json";
|
||||
import fr from "../strings/fr.json";
|
||||
import ja from "../strings/ja.json";
|
||||
import pl from "../strings/pl.json";
|
||||
import pt from "../strings/pt.json";
|
||||
import ru from "../strings/ru.json";
|
||||
import uk from "../strings/uk.json";
|
||||
import zh from "../strings/zh.json";
|
||||
import type en from "../strings/en.json";
|
||||
|
||||
export type BlitzKitStrings = typeof en;
|
||||
|
||||
const stringsRaw: Record<string, TranslationTree> = {
|
||||
en,
|
||||
es,
|
||||
fr,
|
||||
ja,
|
||||
pl,
|
||||
pt,
|
||||
ru,
|
||||
uk,
|
||||
zh,
|
||||
};
|
||||
|
||||
export const STRINGS: Record<string, BlitzKitStrings> = {};
|
||||
|
||||
for (const locale in stringsRaw) {
|
||||
if (locale === locales.default) {
|
||||
STRINGS[locales.default] = stringsRaw[locales.default] as BlitzKitStrings;
|
||||
continue;
|
||||
}
|
||||
|
||||
const localeStrings = stringsRaw[locale];
|
||||
STRINGS[locale] = merge(
|
||||
stringsRaw[locales.default],
|
||||
localeStrings,
|
||||
) as BlitzKitStrings;
|
||||
}
|
||||
|
||||
export const SUPPORTED_LOCALE_BLITZ_MAP: Record<string, string> = {
|
||||
en: "en",
|
||||
es: "es",
|
||||
fr: "fr",
|
||||
ja: "ja",
|
||||
pt: "pt",
|
||||
ru: "ru",
|
||||
uk: "uk",
|
||||
zh: "zh-Hans",
|
||||
pl: "pl",
|
||||
};
|
||||
|
||||
export const SUPPORTED_LOCALE_FLAGS: Record<string, string> = {
|
||||
en: "🇺🇸",
|
||||
es: "🇪🇸",
|
||||
fr: "🇫🇷",
|
||||
ja: "🇯🇵",
|
||||
pt: "🇵🇹",
|
||||
ru: "🇷🇺",
|
||||
uk: "🇺🇦",
|
||||
zh: "🇨🇳",
|
||||
pl: "🇵🇱",
|
||||
};
|
||||
|
||||
export type TranslationTree = { [key: string]: TranslationNode };
|
||||
|
||||
export type TranslationNode = string | TranslationTree;
|
||||
export type Strings = typeof en;
|
||||
@@ -1,12 +0,0 @@
|
||||
import { I18nString } from "../../core/src";
|
||||
import locales from "../locales.json";
|
||||
|
||||
export function unwrapper(locale = locales.default) {
|
||||
return function (i18nString: I18nString) {
|
||||
if (locale in i18nString.locales) {
|
||||
return i18nString.locales[locale];
|
||||
}
|
||||
|
||||
return i18nString.locales[locales.default];
|
||||
};
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import locales from "../locales.json";
|
||||
import { type BlitzKitStrings, STRINGS } from "./strings";
|
||||
|
||||
export function wrapper(slicer: (strings: BlitzKitStrings) => string) {
|
||||
const defaultString = slicer(STRINGS[locales.default] as BlitzKitStrings);
|
||||
|
||||
const object = { [locales.default]: defaultString };
|
||||
|
||||
for (const { locale } of locales.supported) {
|
||||
const string = slicer(STRINGS[locale] as BlitzKitStrings);
|
||||
if (string !== defaultString) object[locale] = string;
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
@@ -1,5 +1,21 @@
|
||||
{
|
||||
"name": "@blitzkit/scripts",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build:assets": "bun src/buildAssets/index.ts",
|
||||
"build:averages": "bun src/averages.ts",
|
||||
"setup:depotdownloader": "bun src/setupDepotDownloader.ts",
|
||||
"setup:patches": "bun src/applyPatches.ts",
|
||||
"build:reviews": "bun src/reviews.ts",
|
||||
"build:ids": "bun src/discoverIds.ts",
|
||||
"build:bot": "bun src/buildBot.ts",
|
||||
"build:ratingleaderboard": "bun src/buildRatingLeaderboard.ts",
|
||||
"build:proxyclient": "bun src/buildProxyClient.ts",
|
||||
"build:catalogitemaccessor": "bun src/buildCatalogItemAccessor.ts",
|
||||
"build:protos": "bun src/protos.ts",
|
||||
"patchnotes": "bun src/patchNotes.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blitzkit/closed": "workspace:*",
|
||||
"@blitzkit/core": "workspace:*",
|
||||
@@ -33,22 +49,5 @@
|
||||
"ts-proto": "^2.7.7",
|
||||
"unzipit": "^1.4.3",
|
||||
"yaml": "^2.8.1"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build:assets": "bun src/buildAssets/index.ts",
|
||||
"build:averages": "bun src/averages.ts",
|
||||
"setup:depotdownloader": "bun src/setupDepotDownloader.ts",
|
||||
"setup:patches": "bun src/applyPatches.ts",
|
||||
"build:reviews": "bun src/reviews.ts",
|
||||
"build:ids": "bun src/discoverIds.ts",
|
||||
"build:bot": "bun src/buildBot.ts",
|
||||
"build:ratingleaderboard": "bun src/buildRatingLeaderboard.ts",
|
||||
"build:proxyclient": "bun src/buildProxyClient.ts",
|
||||
"build:catalogitemaccesssor": "bun src/buildCatalogItemAccessor.ts",
|
||||
"prunestrings": "bun src/pruneStrings.ts",
|
||||
"build:protos": "bun src/protos.ts",
|
||||
"patchnotes": "bun src/patchNotes.ts"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,50 @@
|
||||
import { readdir, readFile, writeFile } from "fs/promises";
|
||||
import { lowerFirst } from "lodash-es";
|
||||
|
||||
const SOURCES = "../../packages/closed/protos";
|
||||
const TARGET = "../../packages/closed/src/unreal/catalogItemAccessor.ts";
|
||||
|
||||
const componentPattern = /^message (\w+)Component {/gm;
|
||||
const componentPattern = /^export interface (\w+Component) {/gm;
|
||||
const componentSuffixPattern = /Component$/;
|
||||
const tsSuffixPattern = /\.ts$/;
|
||||
const blitzStaticPrefixPattern = /^BlitzStatic/;
|
||||
|
||||
const files = await readdir(SOURCES).then((files) =>
|
||||
files.filter(
|
||||
(file) =>
|
||||
file.startsWith("blitz_static_") && file.endsWith("_component.proto")
|
||||
)
|
||||
);
|
||||
let imports = "";
|
||||
let methods = "";
|
||||
|
||||
const imports: { name: string; file: string }[] = [];
|
||||
const files = await readdir(SOURCES);
|
||||
|
||||
for (const file of files) {
|
||||
const content = await readFile(`${SOURCES}/${file}`).then((buffer) =>
|
||||
buffer.toString()
|
||||
);
|
||||
const components = content.matchAll(componentPattern);
|
||||
if (!file.endsWith("_component.ts")) continue;
|
||||
|
||||
for (const match of components) {
|
||||
const name = match[1];
|
||||
const duplicate = imports.find((_import) => _import.name === name);
|
||||
const buffer = await readFile(`${SOURCES}/${file}`);
|
||||
const content = buffer.toString();
|
||||
const matches = content.matchAll(componentPattern);
|
||||
|
||||
if (duplicate) {
|
||||
console.warn(
|
||||
`Duplicate component ${name} across ${duplicate.file}.proto and ${file}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
for (const match of matches) {
|
||||
const interfaceName = match[1];
|
||||
const componentName = interfaceName.split("_").at(-1)!;
|
||||
const fieldName = lowerFirst(
|
||||
componentName.replace(blitzStaticPrefixPattern, ""),
|
||||
);
|
||||
const methodName = componentName.replace(componentSuffixPattern, "");
|
||||
const importName = file.replace(tsSuffixPattern, "");
|
||||
|
||||
const importFile = file.replace(".proto", "");
|
||||
imports += `import { ${interfaceName} } from "@protos/${importName}";\n`;
|
||||
|
||||
imports.push({ name, file: importFile });
|
||||
methods += ` ${methodName}(name = "${fieldName}") {\n`;
|
||||
methods += ` return this.component(name, ${interfaceName});`;
|
||||
methods += "\n }\n";
|
||||
}
|
||||
}
|
||||
|
||||
let content = "";
|
||||
let body = "";
|
||||
|
||||
for (const _import of imports) {
|
||||
content += `import { ${_import.name}Component } from "@protos/${_import.file}";\n`;
|
||||
}
|
||||
|
||||
content +=
|
||||
body += imports;
|
||||
body +=
|
||||
'import { BaseCatalogItemAccessor } from "./baseCatalogItemAccessor";\n\n';
|
||||
content +=
|
||||
"export class CatalogItemAccessor extends BaseCatalogItemAccessor {\n";
|
||||
body += `export class CatalogItemAccessor extends BaseCatalogItemAccessor {\n`;
|
||||
body += methods;
|
||||
body += `}\n`;
|
||||
|
||||
for (const _import of imports) {
|
||||
const strippedName = _import.name.replace("BlitzStatic", "");
|
||||
const defaultName = strippedName[0].toLowerCase() + strippedName.slice(1);
|
||||
|
||||
content += ` ${strippedName}(name = "${defaultName}Component") {\n`;
|
||||
content += ` return this.component(name, ${_import.name}Component);\n`;
|
||||
content += " }\n";
|
||||
}
|
||||
|
||||
content += "}\n";
|
||||
|
||||
await writeFile(TARGET, content);
|
||||
await writeFile(TARGET, body);
|
||||
@@ -1,7 +1,8 @@
|
||||
import { readFile, writeFile } from "fs/promises";
|
||||
|
||||
const IMPORT = "@protos/auto_legacy_proxy_client";
|
||||
const SOURCE = "../../packages/closed/protos/auto_legacy_proxy_client.proto";
|
||||
const SOURCE =
|
||||
"../../packages/closed/protos/blitz/auto_legacy_proxy_client.proto";
|
||||
const TARGET = "../../packages/closed/src/unreal/methodPopulatedProxyClient.ts";
|
||||
|
||||
const requestPattern = /^message (\w+)Request {/gm;
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
import { exec as execSync } from "child_process";
|
||||
import { execFile } from "child_process";
|
||||
import { existsSync } from "fs";
|
||||
import { readdir, rm } from "fs/promises";
|
||||
import { promisify } from "util";
|
||||
import { mkdir, readdir, rm, writeFile } from "fs/promises";
|
||||
|
||||
const MAX_COMMAND_LENGTH = 2 ** 11;
|
||||
const TS_PROTO_EXECUTABLE_LOCATIONS = [
|
||||
"./node_modules/.bin/protoc-gen-ts_proto",
|
||||
"../../node_modules/.bin/protoc-gen-ts_proto",
|
||||
];
|
||||
|
||||
const roots = [
|
||||
"../../packages/core/src/protos",
|
||||
"../../packages/closed/protos",
|
||||
];
|
||||
|
||||
const exec = promisify(execSync);
|
||||
const ROOT = "../closed/protos";
|
||||
|
||||
let tsProtoExecutableLocation: string | undefined = undefined;
|
||||
|
||||
@@ -36,42 +28,37 @@ if (!tsProtoExecutableLocation) {
|
||||
);
|
||||
}
|
||||
|
||||
for (const root of roots) {
|
||||
const filesRaw = await readdir(`${root}`);
|
||||
const files: string[] = [];
|
||||
await mkdir("../../temp", { recursive: true });
|
||||
|
||||
for (const file of filesRaw) {
|
||||
if (file.endsWith(".proto")) files.push(file);
|
||||
if (file.endsWith(".ts")) await rm(`${root}/${file}`);
|
||||
}
|
||||
let args = "";
|
||||
|
||||
while (files.length > 0) {
|
||||
let command = [
|
||||
"protoc",
|
||||
`--plugin=${tsProtoExecutableLocation}`,
|
||||
"--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}`,
|
||||
`-I=${root}`,
|
||||
].join(" ");
|
||||
args += `--plugin=${tsProtoExecutableLocation}\n`;
|
||||
args += "--ts_proto_opt=esModuleInterop=true\n";
|
||||
args += "--ts_proto_opt=oneof=unions-value\n";
|
||||
args += "--ts_proto_opt=unrecognizedEnum=false\n";
|
||||
args += "--ts_proto_opt=snakeToCamel=false\n";
|
||||
args += `--ts_proto_out=${ROOT}\n`;
|
||||
|
||||
while (true) {
|
||||
if (files.length === 0) break;
|
||||
|
||||
const file = files.at(-1);
|
||||
const newLine = ` ${root}/${file}`;
|
||||
|
||||
if (command.length + newLine.length < MAX_COMMAND_LENGTH) {
|
||||
command += newLine;
|
||||
files.pop();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await exec(command);
|
||||
}
|
||||
for (const dir of await readdir(ROOT)) {
|
||||
args += `-I=${ROOT}/${dir}\n`;
|
||||
}
|
||||
|
||||
const filesRaw = await readdir(`${ROOT}`, { recursive: true });
|
||||
const files: string[] = [];
|
||||
|
||||
for (const file of filesRaw) {
|
||||
if (file.endsWith(".proto")) {
|
||||
files.push(file);
|
||||
args += `${ROOT}/${file}\n`;
|
||||
}
|
||||
|
||||
if (file.endsWith(".ts")) await rm(`${ROOT}/${file}`);
|
||||
}
|
||||
|
||||
await writeFile("../../temp/protoc.txt", args);
|
||||
|
||||
execFile("protoc", ["@../../temp/protoc.txt"], (error, stdout, stderr) => {
|
||||
if (error) throw new Error(error.message);
|
||||
if (stderr) console.error(stderr);
|
||||
if (stdout) console.log(stdout);
|
||||
});
|
||||
@@ -4,8 +4,9 @@
|
||||
"main": "src/index.ts",
|
||||
"dependencies": {
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/react": "^19.2.14",
|
||||
"immer": "^10.1.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^19.1.1"
|
||||
"react": "^19.2.5"
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export class Varuna<Type, Arguments = void> {
|
||||
|
||||
constructor(
|
||||
private creator: Type | ((...args: Arguments[]) => Type),
|
||||
private persistence?: string
|
||||
private persistence?: string,
|
||||
) {
|
||||
if (typeof creator !== "function") this.initialize(creator);
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export class Varuna<Type, Arguments = void> {
|
||||
}
|
||||
|
||||
use<Slice = Type>(
|
||||
slicer: (state: Type) => Slice = (state) => state as unknown as Slice
|
||||
slicer: (state: Type) => Slice = (state) => state as unknown as Slice,
|
||||
) {
|
||||
return this.useDeferred(slicer, slicer(this.state));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import react from "@astrojs/react";
|
||||
import strings from "@blitzkit/i18n/strings/en.json";
|
||||
import { mauve, mauveDark } from "@radix-ui/colors";
|
||||
import favicons from "astro-favicons";
|
||||
import { defineConfig } from "astro/config";
|
||||
import packageJSON from "../../package.json";
|
||||
|
||||
export default defineConfig({
|
||||
devToolbar: { enabled: false },
|
||||
|
||||
vite: {
|
||||
server: { allowedHosts: [] },
|
||||
|
||||
esbuild: {
|
||||
minifyIdentifiers: true,
|
||||
minifySyntax: false,
|
||||
minifyWhitespace: true,
|
||||
},
|
||||
},
|
||||
output: "static",
|
||||
site: "https://blitzkit.app",
|
||||
outDir: "../../dist/website",
|
||||
prefetch: { defaultStrategy: "hover", prefetchAll: true },
|
||||
|
||||
build: { concurrency: 4 },
|
||||
adapter: undefined,
|
||||
|
||||
integrations: [
|
||||
favicons({
|
||||
name: strings.common.name,
|
||||
short_name: strings.common.name,
|
||||
|
||||
background: mauveDark.mauve1,
|
||||
themes: [mauve.mauve1, mauveDark.mauve1],
|
||||
appleStatusBarStyle: "black-translucent",
|
||||
|
||||
version: packageJSON.version,
|
||||
}),
|
||||
|
||||
react(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "@blitzkit/website-ue",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"lint": "astro check",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@astrojs/node": "^9.4.4",
|
||||
"@astrojs/react": "^4.4.0",
|
||||
"@astrolib/seo": "^1.0.0-beta.8",
|
||||
"@blitzkit/closed": "workspace:*",
|
||||
"@blitzkit/core": "workspace:*",
|
||||
"@blitzkit/game": "workspace:*",
|
||||
"@blitzkit/i18n": "workspace:*",
|
||||
"@bufbuild/protobuf": "^2.10.2",
|
||||
"@fontsource-variable/jetbrains-mono": "^5.2.8",
|
||||
"@fontsource-variable/roboto-flex": "^5.2.8",
|
||||
"@fontsource-variable/roboto-mono": "^5.2.8",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@react-three/drei": "^10.7.7",
|
||||
"@react-three/fiber": "^9.6.1",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@types/three": "^0.184.1",
|
||||
"@vite-pwa/astro": "^1.1.0",
|
||||
"@wooorm/starry-night": "^3.9.0",
|
||||
"astro": "^5.14.4",
|
||||
"astro-favicons": "^3.1.5",
|
||||
"autoprefixer": "^10.4.23",
|
||||
"cssnano": "^7.1.2",
|
||||
"fuzzysort": "^3.1.0",
|
||||
"googleapis": "^171.1.0",
|
||||
"hast-util-to-jsx-runtime": "^2.3.6",
|
||||
"immer": "^10.1.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-load-config": "^6.0.1",
|
||||
"postcss-nested": "^7.0.2",
|
||||
"postcss-preset-env": "^11.1.1",
|
||||
"quicklime": "^1.2.0",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
"react-error-boundary": "^6.1.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-promise-suspense": "^0.3.4",
|
||||
"sharp": "^0.34.5",
|
||||
"three": "^0.184.0",
|
||||
"three-stdlib": "^2.36.1",
|
||||
"typescript": "^5.9.3",
|
||||
"varuna": "workspace:*",
|
||||
"vite": "^7.1.9",
|
||||
"yaml": "^2.8.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import cssnano from "cssnano";
|
||||
import type { Config } from "postcss-load-config";
|
||||
import postcssPresetEnv from "postcss-preset-env";
|
||||
|
||||
export default {
|
||||
plugins: [
|
||||
require("autoprefixer"),
|
||||
require("postcss-nested"),
|
||||
|
||||
cssnano({
|
||||
preset: "default",
|
||||
}),
|
||||
|
||||
postcssPresetEnv({
|
||||
features: {},
|
||||
}),
|
||||
],
|
||||
} satisfies Config;
|
||||
@@ -0,0 +1,46 @@
|
||||
<svg width="426" height="426" viewBox="0 0 426 426" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M416.628 244.997L235.625 372.974C223.13 381.809 202.87 381.809 190.375 372.974L9.37173 244.997C3.1239 240.58 -3.95076e-06 234.79 0 229V196.6H426V229C426 234.79 422.876 240.58 416.628 244.997Z" fill="#0D2847"/>
|
||||
<path d="M416.628 180.603L235.625 52.6262C223.13 43.7913 202.87 43.7913 190.375 52.6262L9.37172 180.603C-3.12391 189.438 -3.12391 203.762 9.37173 212.597L190.375 340.574C202.87 349.409 223.13 349.409 235.625 340.574L416.628 212.597C429.124 203.762 429.124 189.438 416.628 180.603Z" fill="#D9D9D9"/>
|
||||
<path d="M416.628 180.603L235.625 52.6262C223.13 43.7913 202.87 43.7913 190.375 52.6262L9.37172 180.603C-3.12391 189.438 -3.12391 203.762 9.37173 212.597L190.375 340.574C202.87 349.409 223.13 349.409 235.625 340.574L416.628 212.597C429.124 203.762 429.124 189.438 416.628 180.603Z" fill="url(#paint0_linear_764_240)"/>
|
||||
<path d="M416.628 180.603L235.625 52.6262C223.13 43.7913 202.87 43.7913 190.375 52.6262L9.37172 180.603C-3.12391 189.438 -3.12391 203.762 9.37173 212.597L190.375 340.574C202.87 349.409 223.13 349.409 235.625 340.574L416.628 212.597C429.124 203.762 429.124 189.438 416.628 180.603Z" fill="url(#paint1_linear_764_240)"/>
|
||||
<g filter="url(#filter0_d_764_240)">
|
||||
<path d="M197.056 150.108L201.368 153.051L290.596 213.945L306.6 202.599L310.832 199.599L289.153 184.23L288.86 177.31L335.236 182.298L351.706 170.622L359.102 165.378L288.049 158.111L286.355 118L263.968 133.871L265.867 167.722L217.292 135.762L197.056 150.108Z" fill="#111927" fill-opacity="0.5" shape-rendering="crispEdges"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M192.905 289L74.2242 204.714L70 201.714L116.393 168.767C129.906 159.17 143.124 153.539 156.048 151.875C168.971 150.211 180.919 153.275 191.893 161.069C198.702 165.905 202.718 171.006 203.941 176.372C205.165 181.658 204.302 186.506 201.354 190.918L201.692 191.158C208.978 188.301 216.935 187.286 225.563 188.113C234.247 188.9 242.613 192.151 250.66 197.866C261.803 205.779 266.398 214.825 264.446 225.004C262.551 235.142 254.96 244.929 241.672 254.366L192.905 289ZM221.999 242.08L200.456 257.38L163.905 231.422L185.448 216.123C192.007 211.465 198.473 209.23 204.846 209.42C211.22 209.529 217.193 211.562 222.764 215.519C228.392 219.516 231.31 223.797 231.521 228.364C231.732 232.85 228.558 237.422 221.999 242.08ZM165.937 205.64L146.77 219.252L114.693 196.472L133.861 182.859C140.363 178.241 146.435 175.887 152.076 175.798C157.717 175.708 163.07 177.462 168.135 181.059C173.256 184.696 175.809 188.557 175.796 192.643C175.726 196.69 172.44 201.022 165.937 205.64Z" fill="#111927" fill-opacity="0.5" shape-rendering="crispEdges"/>
|
||||
<path d="M197.056 156.108L290.596 219.945L310.832 205.599L306.6 202.599L289.153 190.23L288.86 183.31L335.236 188.298L359.102 171.378L351.706 170.622L288.049 164.111L286.355 124L263.968 139.871L265.867 173.722L217.292 141.762L201.368 153.051L197.056 156.108Z" fill="#111927" fill-opacity="0.5" shape-rendering="crispEdges"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M192.905 295L70 207.714L74.2242 204.714L116.393 174.767C129.906 165.17 143.124 159.539 156.048 157.875C168.971 156.211 180.919 159.275 191.893 167.069C198.702 171.905 202.718 177.006 203.941 182.372C205.165 187.658 204.302 192.506 201.354 196.918L201.692 197.158C208.978 194.301 216.935 193.286 225.563 194.113C234.247 194.9 242.613 198.151 250.66 203.866C261.803 211.779 266.398 220.825 264.446 231.004C262.551 241.142 254.96 250.929 241.672 260.366L192.905 295ZM221.999 248.08L200.456 263.38L163.905 237.422L185.448 222.123C192.007 217.465 198.473 215.23 204.846 215.42C211.22 215.529 217.193 217.562 222.764 221.519C228.392 225.516 231.31 229.797 231.521 234.364C231.732 238.85 228.558 243.422 221.999 248.08ZM165.937 211.64L146.77 225.252L114.693 202.472L133.861 188.859C140.363 184.241 146.435 181.887 152.076 181.798C157.717 181.708 163.07 183.462 168.135 187.059C173.256 190.696 175.809 194.557 175.796 198.643C175.726 202.69 172.44 207.022 165.937 211.64Z" fill="#111927" fill-opacity="0.5" shape-rendering="crispEdges"/>
|
||||
<path d="M310.832 199.599L306.6 202.599L310.832 205.599V199.599Z" fill="#111927" fill-opacity="0.5" shape-rendering="crispEdges"/>
|
||||
<path d="M359.102 165.378L351.706 170.622L359.102 171.378V165.378Z" fill="#111927" fill-opacity="0.5" shape-rendering="crispEdges"/>
|
||||
<path d="M201.368 153.051L197.056 150.108V156.108L201.368 153.051Z" fill="#111927" fill-opacity="0.5" shape-rendering="crispEdges"/>
|
||||
<path d="M74.2242 204.714L70 201.714V207.714L74.2242 204.714Z" fill="#111927" fill-opacity="0.5" shape-rendering="crispEdges"/>
|
||||
</g>
|
||||
<path d="M197.056 150.108L290.596 213.945L310.832 199.599L289.153 184.23L288.86 177.31L335.236 182.298L359.102 165.378L288.049 158.111L286.355 118L263.968 133.871L265.867 167.722L217.292 135.762L197.056 150.108Z" fill="url(#paint2_linear_764_240)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M192.905 289L70 201.714L116.393 168.767C129.906 159.17 143.124 153.539 156.048 151.875C168.971 150.211 180.919 153.275 191.893 161.069C198.702 165.905 202.718 171.006 203.941 176.372C205.165 181.658 204.302 186.506 201.354 190.918L201.692 191.158C208.978 188.301 216.935 187.286 225.563 188.113C234.247 188.9 242.613 192.151 250.66 197.866C261.803 205.779 266.398 214.825 264.446 225.004C262.551 235.142 254.96 244.929 241.672 254.366L192.905 289ZM221.999 242.08L200.456 257.38L163.905 231.422L185.448 216.123C192.007 211.465 198.473 209.23 204.846 209.42C211.22 209.529 217.193 211.562 222.764 215.519C228.392 219.516 231.31 223.797 231.521 228.364C231.732 232.85 228.558 237.422 221.999 242.08ZM165.937 205.64L146.77 219.252L114.693 196.472L133.861 182.859C140.363 178.241 146.435 175.887 152.076 175.798C157.717 175.708 163.07 177.462 168.135 181.059C173.256 184.696 175.809 188.557 175.796 192.643C175.726 196.69 172.44 201.022 165.937 205.64Z" fill="url(#paint3_linear_764_240)"/>
|
||||
<defs>
|
||||
<filter id="filter0_d_764_240" x="66" y="116" width="297.103" height="185" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="2"/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_764_240"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_764_240" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_764_240" x1="213" y1="46" x2="213" y2="347.2" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#10243E"/>
|
||||
<stop offset="1" stop-color="#0F1B2D"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_764_240" x1="127.008" y1="96.51" x2="241.48" y2="334.974" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#262A65"/>
|
||||
<stop offset="1" stop-color="#104D87"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_764_240" x1="215" y1="98.5" x2="299.5" y2="210.5" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.379808" stop-color="#C2E6FF"/>
|
||||
<stop offset="0.706731" stop-color="#70B8FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_764_240" x1="215" y1="98.5" x2="299.5" y2="210.5" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.379808" stop-color="#C2E6FF"/>
|
||||
<stop offset="0.706731" stop-color="#70B8FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 481 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="21" height="54" viewBox="0 0 21 54" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 38L10 49H0L11 38L0 27H10L21 38ZM21 1V21L20 22H10L21 11L10 0H20L21 1ZM10 11L0 21V1L10 11Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 221 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="30" height="52" viewBox="0 0 30 52" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.8564 44V52H0V36L13.8564 44ZM30 52H16.1436V44L30 36V52ZM28.8564 18V34L15 42L1.14355 34V18L15 10L28.8564 18ZM13.8564 8L0 16V0H13.8564V8ZM30 16L16.1436 8V0H30V16Z" fill="#EEEEF0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 294 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="70" height="35" viewBox="0 0 70 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M70 32L64 35H0L70 0V32ZM0 32V0H64L0 32Z" fill="#EEEEF0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 170 B |
|
After Width: | Height: | Size: 453 KiB |
|
After Width: | Height: | Size: 676 KiB |
|
After Width: | Height: | Size: 405 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 443 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 715 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 367 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 346 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 406 KiB |
|
After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,125 @@
|
||||
import type { MetadataAccessor } from "@blitzkit/closed";
|
||||
import type { Strings } from "@blitzkit/i18n";
|
||||
import locales from "@blitzkit/i18n/locales.json";
|
||||
import type { Avatar } from "@protos/avatar";
|
||||
import type { AvatarList } from "@protos/avatar_list";
|
||||
import type { Avatars } from "@protos/avatars";
|
||||
import type { Background } from "@protos/background";
|
||||
import type { Backgrounds } from "@protos/backgrounds";
|
||||
import type { Consumables } from "@protos/consumables";
|
||||
import type { Equipment } from "@protos/equipment";
|
||||
import type { PopularTanks } from "@protos/popular_tanks";
|
||||
import type { Sets } from "@protos/sets";
|
||||
import type { Tank } from "@protos/tank";
|
||||
import type { TankList } from "@protos/tank_list";
|
||||
import type { TankUpgradePresets } from "@protos/tank_upgrade_presets";
|
||||
import type { Tanks } from "@protos/tanks";
|
||||
import type { TierPrices } from "@protos/tier_prices";
|
||||
import type { Tiers } from "@protos/tiers";
|
||||
|
||||
export abstract class AbstractAPI {
|
||||
abstract metadata: MetadataAccessor;
|
||||
|
||||
abstract mediaPrefix(path: string): string;
|
||||
|
||||
abstract tankList(): Promise<TankList>;
|
||||
abstract tank(id: string): Promise<Tank>;
|
||||
abstract tanks(): Promise<Tanks>;
|
||||
abstract popularTanks(): Promise<PopularTanks>;
|
||||
abstract tankUpgradePresets(): Promise<TankUpgradePresets>;
|
||||
|
||||
abstract tiers(): Promise<Tiers>;
|
||||
abstract equipment(): Promise<Equipment>;
|
||||
abstract consumables(): Promise<Consumables>;
|
||||
abstract tierPrices(): Promise<TierPrices>;
|
||||
abstract sets(): Promise<Sets>;
|
||||
|
||||
abstract strings(locale: string): Promise<Strings>;
|
||||
abstract gameStrings(locale: string): Promise<Record<string, string>>;
|
||||
protected abstract _groupedGameStrings(
|
||||
locale: string,
|
||||
group: string,
|
||||
): Promise<Record<string, string>>;
|
||||
abstract gameStringGroups(): Promise<string[]>;
|
||||
|
||||
abstract avatar(id: string): Promise<Avatar>;
|
||||
abstract avatars(): Promise<Avatars>;
|
||||
abstract avatarList(): Promise<AvatarList>;
|
||||
|
||||
abstract background(id: string): Promise<Background>;
|
||||
abstract backgrounds(): Promise<Backgrounds>;
|
||||
|
||||
abstract currencyIcon(id: string): Promise<string>;
|
||||
abstract stuffIcon(id: string): Promise<string>;
|
||||
|
||||
private assertLocale(locale: string) {
|
||||
const supported = locales.supported.find(
|
||||
(supported) => supported.locale === locale,
|
||||
);
|
||||
|
||||
if (supported === undefined) {
|
||||
throw new Error(`Unsupported locale: ${locale}`);
|
||||
}
|
||||
|
||||
return supported;
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async groupedGameStrings(locale: string, group: string, prefix: boolean) {
|
||||
this.assertLocale(locale);
|
||||
|
||||
const gameStrings = await this._groupedGameStrings(locale, group);
|
||||
|
||||
if (!prefix) return gameStrings;
|
||||
|
||||
const strings: Record<string, string> = {};
|
||||
|
||||
for (const key in gameStrings) {
|
||||
strings[`${group}__${key}`] = gameStrings[key];
|
||||
}
|
||||
|
||||
return strings;
|
||||
}
|
||||
}
|
||||
|
||||
export function Cache<Arguments extends unknown[]>(disableInDev = false) {
|
||||
const cache = new WeakMap<object, Map<string, Promise<unknown>>>();
|
||||
|
||||
return function (
|
||||
_target: unknown,
|
||||
_propertyKey: string,
|
||||
descriptor: PropertyDescriptor,
|
||||
) {
|
||||
const original = descriptor.value;
|
||||
|
||||
descriptor.value = function (...args: Arguments) {
|
||||
const key = args.join("-");
|
||||
|
||||
let thisCache = cache.get(this);
|
||||
|
||||
if (!thisCache) {
|
||||
thisCache = new Map();
|
||||
cache.set(this, thisCache);
|
||||
}
|
||||
|
||||
const disabled = disableInDev && import.meta.env.DEV;
|
||||
|
||||
if (thisCache.has(key) && !disabled) {
|
||||
return thisCache.get(key);
|
||||
}
|
||||
|
||||
const promise = (async () => {
|
||||
try {
|
||||
return await original.apply(this, args);
|
||||
} catch (error) {
|
||||
thisCache!.delete(key);
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
|
||||
thisCache.set(key, promise);
|
||||
|
||||
return promise;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import { fetchPB } from "@blitzkit/core";
|
||||
import type { Strings } from "@blitzkit/i18n";
|
||||
import { Avatars } from "@protos/avatars";
|
||||
import { Consumables } from "@protos/consumables";
|
||||
import { Equipment } from "@protos/equipment";
|
||||
import { PopularTanks } from "@protos/popular_tanks";
|
||||
import { Sets } from "@protos/sets";
|
||||
import { TankList } from "@protos/tank_list";
|
||||
import { TankUpgradePresets } from "@protos/tank_upgrade_presets";
|
||||
import { Tanks } from "@protos/tanks";
|
||||
import { TierPrices } from "@protos/tier_prices";
|
||||
import { Tiers } from "@protos/tiers";
|
||||
import { AbstractAPI, Cache } from "./abstract";
|
||||
|
||||
function rejected() {
|
||||
return Promise.reject("Not implemented on client");
|
||||
}
|
||||
|
||||
export class ClientAPI extends AbstractAPI {
|
||||
get metadata(): never {
|
||||
throw new Error("MetadataAccessor is inaccessible on the client");
|
||||
}
|
||||
|
||||
@UnimplementedOnClient
|
||||
mediaPrefix(): never {
|
||||
throw rejected();
|
||||
}
|
||||
|
||||
@Cache()
|
||||
tankList() {
|
||||
return fetchPB("/api/tanks/list.pb", TankList);
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async tank(id: string) {
|
||||
const tanks = await this.tanks();
|
||||
return tanks.tanks[id];
|
||||
}
|
||||
|
||||
@Cache()
|
||||
tanks() {
|
||||
return fetchPB("/api/tanks/all.pb", Tanks);
|
||||
}
|
||||
|
||||
@Cache()
|
||||
popularTanks() {
|
||||
return fetchPB("/api/tanks/popular.pb", PopularTanks);
|
||||
}
|
||||
|
||||
@Cache()
|
||||
tankUpgradePresets() {
|
||||
return fetchPB(`/api/tanks/upgrades.pb`, TankUpgradePresets);
|
||||
}
|
||||
|
||||
@Cache()
|
||||
tiers() {
|
||||
return fetchPB("/api/tiers.pb", Tiers);
|
||||
}
|
||||
|
||||
@Cache()
|
||||
equipment() {
|
||||
return fetchPB("/api/equipment.pb", Equipment);
|
||||
}
|
||||
|
||||
@Cache()
|
||||
consumables() {
|
||||
return fetchPB("/api/consumables.pb", Consumables);
|
||||
}
|
||||
|
||||
@Cache()
|
||||
tierPrices() {
|
||||
return fetchPB("/api/tier-prices.pb", TierPrices);
|
||||
}
|
||||
|
||||
@Cache()
|
||||
sets() {
|
||||
return fetchPB("/api/sets.pb", Sets);
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async strings(locale: string) {
|
||||
const response = await fetch(`/api/strings/${locale}.json`);
|
||||
const json = (await response.json()) as Strings;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@UnimplementedOnClient
|
||||
gameStrings() {
|
||||
return rejected();
|
||||
}
|
||||
|
||||
@Cache()
|
||||
protected async _groupedGameStrings(locale: string, group: string) {
|
||||
const response = await fetch(`/api/game-strings/${locale}/${group}.json`);
|
||||
const json = (await response.json()) as Record<string, string>;
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@UnimplementedOnClient
|
||||
gameStringGroups() {
|
||||
return rejected();
|
||||
}
|
||||
|
||||
@UnimplementedOnClient
|
||||
avatar() {
|
||||
return rejected();
|
||||
}
|
||||
|
||||
avatars() {
|
||||
return fetchPB("/api/avatars/all.pb", Avatars);
|
||||
}
|
||||
|
||||
@UnimplementedOnClient
|
||||
avatarList() {
|
||||
return rejected();
|
||||
}
|
||||
|
||||
@UnimplementedOnClient
|
||||
background() {
|
||||
return rejected();
|
||||
}
|
||||
|
||||
@UnimplementedOnClient
|
||||
backgrounds() {
|
||||
return rejected();
|
||||
}
|
||||
|
||||
@UnimplementedOnClient
|
||||
currencyIcon() {
|
||||
return rejected();
|
||||
}
|
||||
|
||||
@UnimplementedOnClient
|
||||
stuffIcon() {
|
||||
return rejected();
|
||||
}
|
||||
}
|
||||
|
||||
function UnimplementedOnClient(
|
||||
_target: unknown,
|
||||
propertyKey: string,
|
||||
descriptor: PropertyDescriptor,
|
||||
) {
|
||||
descriptor.value = function () {
|
||||
throw new Error(`${propertyKey} is not implemented on the client`);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { assertSecret } from "@blitzkit/core";
|
||||
import type { AbstractAPI } from "./abstract";
|
||||
|
||||
let dynamicAPI: AbstractAPI;
|
||||
|
||||
if (import.meta.env.SSR) {
|
||||
const { ProxyClient } = await import("@blitzkit/closed");
|
||||
const { ServerAPI } = await import("./server");
|
||||
|
||||
const proxyClient = await new ProxyClient(
|
||||
assertSecret(import.meta.env.WOTB_SERVER),
|
||||
).handshake(assertSecret(import.meta.env.WOTB_VERSION));
|
||||
const metadata = await proxyClient.metadata();
|
||||
|
||||
dynamicAPI = new ServerAPI(metadata);
|
||||
} else {
|
||||
const { ClientAPI } = await import("./client");
|
||||
|
||||
dynamicAPI = new ClientAPI();
|
||||
}
|
||||
|
||||
export const api = dynamicAPI;
|
||||
@@ -0,0 +1,23 @@
|
||||
import sharp from "sharp";
|
||||
|
||||
export async function imageProxy(url: string) {
|
||||
if (import.meta.env.DEV) {
|
||||
return Response.redirect(url);
|
||||
}
|
||||
|
||||
let response: Response;
|
||||
|
||||
try {
|
||||
response = await fetch(url);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to fetch image proxy ${url}:`, error);
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
|
||||
const buffer = await response.arrayBuffer();
|
||||
const image = sharp(buffer, { animated: true });
|
||||
const webp = await image.webp().toBuffer();
|
||||
const array = new Uint8Array(webp);
|
||||
|
||||
return new Response(array);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export const clientUnmountedResponse = new Response(null, { status: 503 });
|
||||
|
||||
export const notFoundResponse = new Response(null, { status: 404 });
|
||||
@@ -0,0 +1,453 @@
|
||||
import type { MetadataAccessor } from "@blitzkit/closed";
|
||||
import { assertSecret, sluggify } from "@blitzkit/core";
|
||||
import type { Strings } from "@blitzkit/i18n";
|
||||
import locales from "@blitzkit/i18n/locales.json";
|
||||
import type { Avatar, DeepPartial } from "@protos/avatar";
|
||||
import type { Background } from "@protos/background";
|
||||
import type { RemoteStorageComponent } from "@protos/blitz_static_remote_storage_component";
|
||||
import { Consumables } from "@protos/consumables";
|
||||
import { Equipment } from "@protos/equipment";
|
||||
import type { PopularTanks } from "@protos/popular_tanks";
|
||||
import { Sets } from "@protos/sets";
|
||||
import type { Tank } from "@protos/tank";
|
||||
import type { TankListEntry } from "@protos/tank_list";
|
||||
import { TankUpgradePresets } from "@protos/tank_upgrade_presets";
|
||||
import type { Tanks } from "@protos/tanks";
|
||||
import { TierPrices } from "@protos/tier_prices";
|
||||
import type { Tiers } from "@protos/tiers";
|
||||
import { existsSync } from "fs";
|
||||
import { mkdir, readFile, writeFile } from "fs/promises";
|
||||
import { google } from "googleapis";
|
||||
import { merge } from "lodash-es";
|
||||
import { parse } from "yaml";
|
||||
import { AbstractAPI, Cache } from "./abstract";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
throw new Error("ServerAPI is being evaluated in the browser");
|
||||
}
|
||||
|
||||
const globbedStrings = import.meta.glob("../../../i18n/strings/*.json", {
|
||||
import: "default",
|
||||
});
|
||||
const tankSlugPattern = /^(\/\w+)?\/tanks\/([a-z0-9-]+)\/$/;
|
||||
|
||||
interface LocalizationConfig {
|
||||
namespaces: string[];
|
||||
}
|
||||
|
||||
export class ServerAPI extends AbstractAPI {
|
||||
constructor(public metadata: MetadataAccessor) {
|
||||
super();
|
||||
}
|
||||
|
||||
mediaPrefix(path: string) {
|
||||
const config = this.resolveClientConfig();
|
||||
const metroResources = config.MetroResources();
|
||||
const remoteStorage = this.resolveRemoteUrl(
|
||||
metroResources.remote_storage!.remote_urls,
|
||||
);
|
||||
|
||||
return `${remoteStorage.url}/${remoteStorage.relative_path}${path}`;
|
||||
}
|
||||
|
||||
resolveRemoteUrl(remoteUrls: string[]) {
|
||||
let remoteStorage: RemoteStorageComponent | undefined = undefined;
|
||||
|
||||
for (const remoteUrl of remoteUrls) {
|
||||
const item = this.metadata.item(remoteUrl);
|
||||
const candidate = item.RemoteStorage();
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
return remoteStorage;
|
||||
}
|
||||
|
||||
resolveClientConfig() {
|
||||
const group = this.metadata.group("ClientConfigsEntity");
|
||||
|
||||
if (group.length !== 1) {
|
||||
throw new RangeError(
|
||||
`Don't know how to handle ${group.length} ClientConfigsEntities`,
|
||||
);
|
||||
}
|
||||
|
||||
return this.metadata.item(group[0].id);
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async tankList() {
|
||||
const group = this.metadata.group("TankEntity");
|
||||
const list: TankListEntry[] = [];
|
||||
const strings = await this.groupedGameStrings("en", "TankEntity", true);
|
||||
|
||||
for (const item of group) {
|
||||
const tankCatalog = item.TankCatalog();
|
||||
|
||||
if (!tankCatalog.name) continue;
|
||||
|
||||
const name = strings[tankCatalog.name.value];
|
||||
|
||||
if (!name) continue;
|
||||
|
||||
const slug = sluggify(name);
|
||||
|
||||
list.push({ id: item.id, slug });
|
||||
}
|
||||
|
||||
return { list };
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async tank(id: string) {
|
||||
const tankList = await this.tankList();
|
||||
const tankListEntry = tankList.list.find((tank) => tank.id === id);
|
||||
|
||||
if (!tankListEntry) {
|
||||
throw new Error(`Tank with id ${id} not found`);
|
||||
}
|
||||
|
||||
const { slug } = tankListEntry;
|
||||
const item = this.metadata.item(id);
|
||||
const tank = item.TankCatalog();
|
||||
const compensation = item.Compensation();
|
||||
|
||||
return { id, slug, tank, compensation } satisfies Tank;
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async tanks() {
|
||||
const tankList = await this.tankList();
|
||||
const data: Tanks = { tanks: {} };
|
||||
|
||||
for (const { id } of tankList.list) {
|
||||
data.tanks[id] = await this.tank(id);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async popularTanks() {
|
||||
if (!existsSync("../../temp/popular.json")) {
|
||||
const { tanks } = await this.tanks();
|
||||
const tanksArray = Object.values(tanks);
|
||||
|
||||
const auth = await google.auth.getClient({
|
||||
keyFile: assertSecret(import.meta.env.GOOGLE_APPLICATION_CREDENTIALS),
|
||||
scopes: ["https://www.googleapis.com/auth/analytics.readonly"],
|
||||
});
|
||||
const analytics = google.analyticsdata({ version: "v1beta", auth });
|
||||
const report = await analytics.properties.runReport({
|
||||
property: `properties/${assertSecret(
|
||||
import.meta.env.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID,
|
||||
)}`,
|
||||
requestBody: {
|
||||
dateRanges: [{ startDate: "7daysAgo", endDate: "today" }],
|
||||
dimensions: [{ name: "pagePath" }],
|
||||
metrics: [{ name: "screenPageViews" }],
|
||||
orderBys: [{ metric: { metricName: "screenPageViews" }, desc: true }],
|
||||
dimensionFilter: {
|
||||
filter: {
|
||||
fieldName: "pagePath",
|
||||
stringFilter: {
|
||||
matchType: "BEGINS_WITH",
|
||||
value: "/tanks/",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const views: Record<string, number> = {};
|
||||
|
||||
if (!report.data.rows) {
|
||||
throw new Error("No rows in report");
|
||||
}
|
||||
|
||||
for (const row of report.data.rows) {
|
||||
if (
|
||||
!row.dimensionValues ||
|
||||
!row.dimensionValues[0].value ||
|
||||
!row.metricValues ||
|
||||
!row.metricValues[0].value
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const matches = row.dimensionValues[0].value.match(tankSlugPattern);
|
||||
|
||||
if (!matches) continue;
|
||||
|
||||
const slug = matches[2];
|
||||
const tank = tanksArray.find((tank) => tank.slug === slug);
|
||||
|
||||
if (!tank) continue;
|
||||
|
||||
if (tank.id in views) {
|
||||
views[tank.id] += Number(row.metricValues[0].value);
|
||||
} else {
|
||||
views[tank.id] = Number(row.metricValues[0].value);
|
||||
}
|
||||
}
|
||||
|
||||
for (const id in tanks) {
|
||||
if (!(id in views)) {
|
||||
views[id] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const popularTanks = {
|
||||
tanks: Object.entries(views)
|
||||
.sort(([, a], [, b]) => b - a)
|
||||
.map(([id, views]) => ({ id, views })),
|
||||
} satisfies PopularTanks;
|
||||
|
||||
await mkdir("../../temp", { recursive: true });
|
||||
await writeFile("../../temp/popular.json", JSON.stringify(popularTanks));
|
||||
|
||||
return popularTanks;
|
||||
}
|
||||
|
||||
const data = await readFile("../../temp/popular.json", "utf-8");
|
||||
return JSON.parse(data) as PopularTanks;
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async tankUpgradePresets() {
|
||||
const group = this.metadata.group("TankUpgradePricePresetEntity");
|
||||
const presets = TankUpgradePresets.create();
|
||||
|
||||
for (const item of group) {
|
||||
presets.presets[item.id] = item.TankUpgradePricePreset();
|
||||
}
|
||||
|
||||
return presets;
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async tiers() {
|
||||
const group = this.metadata.group("TankTierEntity");
|
||||
const tiers: Tiers = { tiers: {} };
|
||||
|
||||
for (const item of group) {
|
||||
tiers.tiers[item.id] = item.TankTier();
|
||||
}
|
||||
|
||||
return tiers;
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async equipment() {
|
||||
const equipment = Equipment.create();
|
||||
|
||||
for (const item of this.metadata.group("EquipmentPresetEntity")) {
|
||||
equipment.presets[item.id] = item.EquipmentPreset();
|
||||
}
|
||||
|
||||
for (const item of this.metadata.group("EquipmentPricePresetEntity")) {
|
||||
equipment.prices[item.id] = item.EquipmentPricePreset();
|
||||
}
|
||||
|
||||
for (const item of this.metadata.group("EquipmentEntity")) {
|
||||
equipment.equipments[item.id] = item.BlitzStaticEquipment();
|
||||
}
|
||||
|
||||
return equipment;
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async consumables() {
|
||||
const consumables = Consumables.create();
|
||||
|
||||
for (const item of this.metadata.group("ConsumableEntity")) {
|
||||
const purchase = item.BlitzStaticPurchase("purchasable");
|
||||
const consumable = item.Consumable();
|
||||
const compatibility = item.Compatibility();
|
||||
|
||||
consumables.consumables[item.id] = {
|
||||
purchase,
|
||||
consumable,
|
||||
compatibility,
|
||||
};
|
||||
}
|
||||
|
||||
return consumables;
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async tierPrices() {
|
||||
const tierPrices = TierPrices.create();
|
||||
|
||||
for (const item of this.metadata.group("TierPricesEntity")) {
|
||||
tierPrices.prices[item.id] = item.TierPrices();
|
||||
}
|
||||
|
||||
return tierPrices;
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async sets() {
|
||||
const sets = Sets.create();
|
||||
|
||||
for (const item of this.metadata.group("TankSetEntity")) {
|
||||
sets.sets[item.id] = item.TankSetCatalog();
|
||||
}
|
||||
|
||||
return sets;
|
||||
}
|
||||
|
||||
@Cache(true)
|
||||
async strings(locale: string) {
|
||||
const localized = (await globbedStrings[
|
||||
`../../../i18n/strings/${locale}.json`
|
||||
]()) as DeepPartial<Strings>;
|
||||
const defaults = (await globbedStrings[
|
||||
`../../../i18n/strings/${locales.default}.json`
|
||||
]()) as Strings;
|
||||
const strings = merge({}, defaults, localized);
|
||||
|
||||
return strings;
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async gameStrings(locale: string) {
|
||||
const clientConfig = this.resolveClientConfig();
|
||||
const localizationResources = clientConfig.LocalizationResources();
|
||||
const strings: Record<string, string> = {};
|
||||
|
||||
for (const remoteStorageSettings of localizationResources.remote_storages) {
|
||||
const remoteStorage = this.resolveRemoteUrl(
|
||||
remoteStorageSettings.remote_urls,
|
||||
);
|
||||
const configPath = `${remoteStorage.url}${remoteStorage.relative_path}/config.yaml`;
|
||||
const config = await fetch(configPath)
|
||||
.then((response) => response.text())
|
||||
.then((text) => parse(text) as LocalizationConfig);
|
||||
|
||||
for (const namespace of config.namespaces) {
|
||||
const url = `${remoteStorage.url}${remoteStorage.relative_path}/${namespace}/${locale}.yaml`;
|
||||
const response = await fetch(url);
|
||||
const text = await response.text();
|
||||
const namespaceStrings: Record<string, string> = {};
|
||||
const parsed = parse(text) as Record<string, string>;
|
||||
|
||||
for (const key in parsed) {
|
||||
strings[key] = parsed[key].replaceAll('\\"', '"');
|
||||
}
|
||||
|
||||
Object.assign(strings, namespaceStrings);
|
||||
}
|
||||
}
|
||||
|
||||
return strings;
|
||||
}
|
||||
|
||||
@Cache()
|
||||
protected async _groupedGameStrings(locale: string, group: string) {
|
||||
const strings = await this.gameStrings(locale);
|
||||
const filtered: Record<string, string> = {};
|
||||
|
||||
for (const key in strings) {
|
||||
if (key.startsWith(`${group}__`)) {
|
||||
const trimmed = key.substring(group.length + 2);
|
||||
filtered[trimmed] = strings[key];
|
||||
}
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async gameStringGroups() {
|
||||
const defaultGameStrings = await this.gameStrings(locales.default);
|
||||
const groups = new Set<string>();
|
||||
|
||||
for (const key in defaultGameStrings) {
|
||||
const [group] = key.split("__");
|
||||
groups.add(group);
|
||||
}
|
||||
|
||||
return Array.from(groups.values());
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async avatar(id: string) {
|
||||
const avatar = this.metadata.item(id);
|
||||
const name = avatar.name;
|
||||
const stuff_ui = avatar.StuffUI("UIComponent");
|
||||
const profile_avatar = avatar.ProfileAvatar();
|
||||
const sellable = avatar.components.sellableComponent
|
||||
? avatar.Sellable()
|
||||
: undefined;
|
||||
|
||||
return { name, stuff_ui, profile_avatar, sellable };
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async avatars() {
|
||||
const avatars: Avatar[] = [];
|
||||
|
||||
for (const item of this.metadata.group("ProfileAvatarEntity")) {
|
||||
avatars.push(await this.avatar(item.id));
|
||||
}
|
||||
|
||||
return { avatars };
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async avatarList() {
|
||||
const avatars = await this.avatars();
|
||||
const list = avatars.avatars.map((avatar) => avatar.name);
|
||||
|
||||
return { list };
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async background(id: string) {
|
||||
const background = this.metadata.item(id);
|
||||
const name = background.name;
|
||||
const stuff_ui = background.StuffUI("UIComponent");
|
||||
const profile_background = background.ProfileBackground();
|
||||
const sellable = background.components.sellableComponent
|
||||
? background.Sellable()
|
||||
: undefined;
|
||||
|
||||
return { name, stuff_ui, profile_background, sellable };
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async backgrounds() {
|
||||
const backgrounds: Background[] = [];
|
||||
|
||||
for (const item of this.metadata.group("ProfileBackgroundEntity")) {
|
||||
backgrounds.push(await this.background(item.name));
|
||||
}
|
||||
|
||||
return { backgrounds };
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async currencyIcon(id: string) {
|
||||
const item = this.metadata.item(id);
|
||||
const stuffUI = item.StuffUI("UIComponent");
|
||||
return this.mediaPrefix(stuffUI.icon!.value);
|
||||
}
|
||||
|
||||
@Cache()
|
||||
async stuffIcon(id: string) {
|
||||
const item = this.metadata.item(id);
|
||||
console.log(id);
|
||||
const stuffUI = item.StuffUI("UIComponent");
|
||||
return this.mediaPrefix(stuffUI.icon!.value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { assertSecret } from "@blitzkit/core";
|
||||
|
||||
export function isChunkEnabled(chunk: string) {
|
||||
const buildChunk = assertSecret(import.meta.env.BUILD_CHUNK);
|
||||
return [chunk, "*"].includes(buildChunk);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import type {
|
||||
GetStaticPathsItem,
|
||||
GetStaticPathsOptions,
|
||||
GetStaticPathsResult,
|
||||
} from "astro";
|
||||
|
||||
type Promisable<T> = T | Promise<T>;
|
||||
|
||||
export function mixStaticPaths<
|
||||
TypeA extends GetStaticPathsItem,
|
||||
TypeB extends GetStaticPathsItem
|
||||
>(
|
||||
a: (options: GetStaticPathsOptions) => Promisable<TypeA[]>,
|
||||
b: (options: GetStaticPathsOptions) => Promisable<TypeB[]>
|
||||
) {
|
||||
return async function (options: GetStaticPathsOptions) {
|
||||
const pathsA = await a(options);
|
||||
const pathsB = await b(options);
|
||||
const paths: GetStaticPathsResult = [];
|
||||
|
||||
for (const pathA of pathsA) {
|
||||
for (const pathB of pathsB) {
|
||||
paths.push({
|
||||
params: { ...pathA.params, ...pathB.params },
|
||||
props: { ...pathA.props, ...pathB.props },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return paths as (TypeA & TypeB)[];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
---
|
||||
import ApiMacroSee from "./ApiMacroSee.astro";
|
||||
import ApiMacroType from "./ApiMacroType.astro";
|
||||
import { MarkdownCustom } from "./MarkdownCustom";
|
||||
|
||||
interface Props {
|
||||
body: string;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
const macroPattern = /@(\w+) (.+)/;
|
||||
const macros: Record<string, string[]> = {};
|
||||
|
||||
const macroComponents = {
|
||||
see: ApiMacroSee,
|
||||
type: ApiMacroType,
|
||||
};
|
||||
|
||||
const { body: bodyOriginal, locale } = Astro.props;
|
||||
let body = "";
|
||||
|
||||
for (const line of bodyOriginal.split("\n")) {
|
||||
const match = line.match(macroPattern);
|
||||
|
||||
if (match === null) {
|
||||
body += `${line}\n`;
|
||||
continue;
|
||||
}
|
||||
|
||||
const macro = match[1];
|
||||
const value = match[2];
|
||||
|
||||
if (macro in macros) {
|
||||
macros[macro].push(value);
|
||||
} else {
|
||||
macros[macro] = [value];
|
||||
}
|
||||
}
|
||||
---
|
||||
|
||||
<style>
|
||||
.api-description {
|
||||
display: flex;
|
||||
|
||||
.gutter {
|
||||
width: var(--space-2);
|
||||
background: linear-gradient(
|
||||
transparent,
|
||||
var(--gray-6) var(--space-4),
|
||||
var(--gray-6) calc(100% - var(--space-4)),
|
||||
transparent
|
||||
);
|
||||
|
||||
margin: 0 var(--space-4);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
|
||||
width: 100%;
|
||||
|
||||
padding-top: var(--space-4);
|
||||
padding-bottom: var(--space-3);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="api-description">
|
||||
<div class="gutter"></div>
|
||||
<div class="content">
|
||||
<MarkdownCustom {body} />
|
||||
|
||||
{
|
||||
Object.entries(macroComponents).map(([macro, Component]) => {
|
||||
if (!(macro in macros)) return null;
|
||||
|
||||
return macros[macro].map((value) => {
|
||||
return <Component {locale} value={value} />;
|
||||
});
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
import { api } from "../api/dynamic";
|
||||
import ApiDescription from "./ApiDescription.astro";
|
||||
import { ApiTester } from "./ApiTester";
|
||||
|
||||
interface Props {
|
||||
locale: string;
|
||||
api: CollectionEntry<"api">;
|
||||
}
|
||||
|
||||
const { locale, api: apiData } = Astro.props;
|
||||
const strings = await api.strings(locale);
|
||||
const body = apiData.data.docs ?? strings.api.no_docs;
|
||||
---
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container">
|
||||
<ApiTester client:idle {locale} path={apiData.id} />
|
||||
<ApiDescription {locale} {body} />
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
import type { CollectionEntry } from "astro:content";
|
||||
import ApiEntry from "./ApiEntry.astro";
|
||||
|
||||
interface Props {
|
||||
locale: string;
|
||||
apis: CollectionEntry<"api">[];
|
||||
}
|
||||
|
||||
const { apis, locale } = Astro.props;
|
||||
---
|
||||
|
||||
{apis.map((api) => <ApiEntry {locale} {api} />)}
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
import { sluggify } from "@blitzkit/core";
|
||||
import { Code } from "./Code";
|
||||
import { Link } from "./Link";
|
||||
import { Text } from "./Text";
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
const bodyPattern = /^([a-z0-9-\/\.\[\]]+) (.+)$/;
|
||||
|
||||
const { value, locale } = Astro.props;
|
||||
|
||||
const match = value.match(bodyPattern);
|
||||
|
||||
if (match === null) {
|
||||
throw new Error(`Invalid value for ApiMacroSee: ${value}`);
|
||||
}
|
||||
|
||||
const path = match[1];
|
||||
const message = match[2];
|
||||
---
|
||||
|
||||
<Text>
|
||||
<Text lowContrast weight="medium" color="tomato">
|
||||
<Code>@see</Code>
|
||||
</Text>
|
||||
|
||||
<Link underline="never" href={`#${sluggify(path)}`}>
|
||||
<Code>{path}</Code>
|
||||
</Link>
|
||||
|
||||
{message}
|
||||
</Text>
|
||||
@@ -0,0 +1,203 @@
|
||||
---
|
||||
import { ArrowBottomRightIcon } from "@radix-ui/react-icons";
|
||||
import { resolve } from "path";
|
||||
import {
|
||||
createProgram,
|
||||
forEachChild,
|
||||
isArrayTypeNode,
|
||||
isInterfaceDeclaration,
|
||||
isPropertySignature,
|
||||
isTypeNode,
|
||||
isTypeReferenceNode,
|
||||
type InterfaceDeclaration,
|
||||
type Node,
|
||||
type Symbol,
|
||||
type TypeNode,
|
||||
} from "typescript";
|
||||
import { api } from "../api/dynamic";
|
||||
import { Code } from "./Code";
|
||||
import HighlightedCodeBlock from "./HighlightedCodeBlock.astro";
|
||||
import { Link } from "./Link";
|
||||
import { Text } from "./Text";
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
const DEFAULT_EXPANDED = 8;
|
||||
|
||||
const { value, locale } = Astro.props;
|
||||
const strings = await api.strings(locale);
|
||||
|
||||
const lang = "ts";
|
||||
const root = "../../packages/closed/protos";
|
||||
|
||||
const [route, name] = value.split(".");
|
||||
const path = resolve(`${root}/${route}.ts`);
|
||||
|
||||
const program = createProgram([path], {});
|
||||
const source = program.getSourceFile(path);
|
||||
|
||||
if (source === undefined) {
|
||||
throw new Error(`Could not find ${path}`);
|
||||
}
|
||||
|
||||
const checker = program.getTypeChecker();
|
||||
const collected = new Map<string, string>();
|
||||
const visited = new Set<Symbol>();
|
||||
let declaration: InterfaceDeclaration | undefined;
|
||||
|
||||
forEachChild(source, (node) => {
|
||||
if (isInterfaceDeclaration(node) && node.name.text === name) {
|
||||
declaration = node;
|
||||
}
|
||||
});
|
||||
|
||||
if (declaration === undefined) {
|
||||
throw new Error(`Could not find ${name} in ${path}`);
|
||||
}
|
||||
|
||||
function collectNodes(node: Node) {
|
||||
if (isInterfaceDeclaration(node)) {
|
||||
const symbol = checker.getSymbolAtLocation(node.name);
|
||||
|
||||
if (symbol) {
|
||||
const text = node.getText();
|
||||
|
||||
collected.set(node.name.text, text);
|
||||
|
||||
node.members.forEach((member) => {
|
||||
if (isPropertySignature(member) && member.type) {
|
||||
collectTypes(member.type);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
forEachChild(node, collectNodes);
|
||||
}
|
||||
|
||||
function collectTypes(node: TypeNode) {
|
||||
if (isArrayTypeNode(node)) {
|
||||
collectTypes(node.elementType);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTypeReferenceNode(node)) {
|
||||
node.typeArguments?.forEach(collectTypes);
|
||||
}
|
||||
|
||||
const type = checker.getTypeAtLocation(node);
|
||||
const symbol = type.getSymbol();
|
||||
|
||||
if (!symbol || visited.has(symbol)) return;
|
||||
|
||||
visited.add(symbol);
|
||||
|
||||
const declarations = symbol.getDeclarations();
|
||||
|
||||
if (!declarations) return;
|
||||
|
||||
for (const declaration of declarations) {
|
||||
if (isInterfaceDeclaration(declaration)) {
|
||||
collected.set(declaration.name.text, declaration.getText());
|
||||
|
||||
declaration.members.forEach((member) => {
|
||||
if (isPropertySignature(member) && member.type) {
|
||||
collectTypes(member.type);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
forEachChild(declaration, (child) => {
|
||||
if (isTypeNode(child)) collectTypes(child);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collectNodes(declaration);
|
||||
|
||||
let content = "";
|
||||
|
||||
for (const [, definition] of collected) {
|
||||
content += `${definition}\n\n`;
|
||||
}
|
||||
|
||||
content = content.replaceAll(/^export\s*/gm, "");
|
||||
content = content.trim();
|
||||
---
|
||||
|
||||
<style>
|
||||
.macro {
|
||||
color: var(--gray-11);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.types {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
position: relative;
|
||||
|
||||
&:not([data-expanded="true"]) {
|
||||
max-height: 16rem;
|
||||
}
|
||||
|
||||
&[data-expanded="true"] {
|
||||
.overlay {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background: linear-gradient(transparent, var(--gray-1));
|
||||
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const viewers = document.querySelectorAll(".view-full");
|
||||
|
||||
for (const viewer of viewers) {
|
||||
viewer.addEventListener("click", () => {
|
||||
viewer.parentElement!.parentElement!.dataset.expanded = "true";
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="macro">
|
||||
<Text lowContrast weight="medium" color="tomato">
|
||||
<Code>@types</Code>
|
||||
</Text>
|
||||
|
||||
<ArrowBottomRightIcon />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="types"
|
||||
data-expanded={content.split("\n").length <= DEFAULT_EXPANDED}
|
||||
>
|
||||
<HighlightedCodeBlock {lang} content={content} />
|
||||
|
||||
<div class="overlay">
|
||||
<Link underline="always" href="#" className="view-full">
|
||||
{strings.api.view_full}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
.tester {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { sluggify } from "@blitzkit/core";
|
||||
import { PlayIcon } from "@radix-ui/react-icons";
|
||||
import { Fragment, useState } from "react";
|
||||
import {
|
||||
LocaleProvider,
|
||||
type LocaleAcceptorProps,
|
||||
} from "../../hooks/useLocale";
|
||||
import { useStrings } from "../../hooks/useStrings";
|
||||
import { Button } from "../Button";
|
||||
import { Code } from "../Code";
|
||||
import { Link } from "../Link";
|
||||
import { Text } from "../Text";
|
||||
import { TextField } from "../TextField";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
interface Props {
|
||||
path: string;
|
||||
}
|
||||
|
||||
const slugPattern = /\[\w+\]/g;
|
||||
|
||||
function Content({ path }: Props) {
|
||||
const strings = useStrings();
|
||||
|
||||
const slugs = (path.match(slugPattern) ?? []).map((slug) =>
|
||||
slug.slice(1, -1),
|
||||
);
|
||||
const [values, setValues] = useState(() => {
|
||||
const values: Record<string, string> = {};
|
||||
|
||||
for (const slug of slugs) {
|
||||
values[slug] = "";
|
||||
}
|
||||
|
||||
return values;
|
||||
});
|
||||
|
||||
const href = Object.entries(values).reduce(
|
||||
(href, [slug, value]) => href.replace(`[${slug}]`, value),
|
||||
`/api/${path}`,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.tester} id={sluggify(path)}>
|
||||
<Text weight="medium">
|
||||
<Code className={styles.title}>
|
||||
{path.split(slugPattern).map((text, index) => {
|
||||
const slug = slugs[index - 1];
|
||||
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
{index > 0 && (
|
||||
<TextField
|
||||
variant="form"
|
||||
className={styles.slug}
|
||||
placeholder={`[${slug}]`}
|
||||
align="center"
|
||||
autoWidth
|
||||
onChange={(event) => {
|
||||
setValues((state) => ({
|
||||
...state,
|
||||
[slug]: event.target.value,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<span key={index}>{text}</span>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</Code>
|
||||
</Text>
|
||||
|
||||
<Link underline="never" href={href} target="_blank">
|
||||
<Button>
|
||||
{strings.api.test} <PlayIcon />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ApiTester({ path, locale }: Props & LocaleAcceptorProps) {
|
||||
return (
|
||||
<LocaleProvider locale={locale}>
|
||||
<Content path={path} />
|
||||
</LocaleProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
|
||||
.series {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 7 / 8;
|
||||
|
||||
.entry {
|
||||
position: absolute;
|
||||
|
||||
border-radius: var(--space-3);
|
||||
box-shadow: var(--shadow-interactive), var(--shadow-aggressive);
|
||||
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
|
||||
&.skeleton {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.count {
|
||||
position: absolute;
|
||||
bottom: var(--space-3);
|
||||
right: var(--space-3);
|
||||
|
||||
box-shadow: var(--shadow-aggressive);
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
|
||||
.skeleton {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
display: flex;
|
||||
gap: var(--space-5);
|
||||
align-items: center;
|
||||
|
||||
.preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
padding: var(--space-2);
|
||||
|
||||
&[data-grade="1"] .image {
|
||||
outline: var(--space-2) solid var(--gray-9);
|
||||
}
|
||||
|
||||
&[data-grade="2"] .image {
|
||||
outline: var(--space-2) solid var(--blue-9);
|
||||
box-shadow: 0 0 var(--space-3) var(--blue-9);
|
||||
}
|
||||
|
||||
&[data-grade="3"] .image {
|
||||
outline: var(--space-2) solid var(--purple-9);
|
||||
box-shadow: 0 0 var(--space-4) var(--purple-9);
|
||||
}
|
||||
|
||||
&[data-grade="4"] .image {
|
||||
outline: var(--space-2) solid var(--amber-9);
|
||||
box-shadow: 0 0 var(--space-5) var(--amber-9);
|
||||
}
|
||||
|
||||
.image {
|
||||
aspect-ratio: 7 / 8;
|
||||
width: 8rem;
|
||||
border-radius: var(--space-3);
|
||||
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.grade {
|
||||
margin-top: calc(var(--space-3) * -1);
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
max-width: 20rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import { literals } from "@blitzkit/i18n";
|
||||
import type { Avatar } from "@protos/avatar";
|
||||
import { Grade } from "@protos/blitz_static_standard_grades_enum";
|
||||
import { DownloadIcon, StarFilledIcon } from "@radix-ui/react-icons";
|
||||
import { useGameStrings } from "../../hooks/useGameStrings";
|
||||
import { useStrings } from "../../hooks/useStrings";
|
||||
import type { PropsWithSkeleton } from "../../types/propsWithSkeleton";
|
||||
import type { Color } from "../../ui/color";
|
||||
import { Badge } from "../Badge";
|
||||
import { Button } from "../Button";
|
||||
import { Dialog } from "../Dialog";
|
||||
import { InlineSkeleton } from "../InlineSkeleton";
|
||||
import { Link } from "../Link";
|
||||
import { Skeleton } from "../Skeleton";
|
||||
import { Text } from "../Text";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
avatars: Avatar[];
|
||||
}
|
||||
|
||||
const MAX_CARDS = 3;
|
||||
const CARD_TRIM = "var(--space-3)";
|
||||
|
||||
export function AvatarCard(props: PropsWithSkeleton<Props>) {
|
||||
const card = (
|
||||
<div className={styles.card}>
|
||||
<Series skeleton={props.skeleton} {...props} />
|
||||
|
||||
<Text align="center" className={styles.label}>
|
||||
{props.skeleton ? <InlineSkeleton /> : props.name}
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (props.skeleton) return card;
|
||||
|
||||
return (
|
||||
<Dialog.Root>
|
||||
<Dialog.Trigger>{card}</Dialog.Trigger>
|
||||
<Dialog.Content>
|
||||
<Text size="major" weight="bold">
|
||||
{props.name}
|
||||
</Text>
|
||||
|
||||
<Details avatars={props.avatars} />
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
||||
|
||||
interface DetailsProps {
|
||||
avatars: Avatar[];
|
||||
}
|
||||
|
||||
const gradeColors: Record<Grade, Color> = {
|
||||
[Grade.GRADE_UNSPECIFIED]: "gray",
|
||||
[Grade.GRADE_COMMON]: "gray",
|
||||
[Grade.GRADE_RARE]: "blue",
|
||||
[Grade.GRADE_EPIC]: "purple",
|
||||
[Grade.GRADE_LEGENDARY]: "amber",
|
||||
};
|
||||
|
||||
function Details({ avatars }: DetailsProps) {
|
||||
const gameStrings = useGameStrings("ProfileAvatarEntity");
|
||||
const strings = useStrings();
|
||||
|
||||
return [...avatars].reverse().map((avatar) => {
|
||||
const description = gameStrings[avatar.stuff_ui!.description];
|
||||
const obtaining = gameStrings[avatar.stuff_ui!.obtaining_methods];
|
||||
const hasDescription = description || obtaining;
|
||||
|
||||
return (
|
||||
<div className={styles.details}>
|
||||
<div className={styles.preview} data-grade={avatar.stuff_ui!.grade}>
|
||||
<div
|
||||
className={styles.image}
|
||||
style={{
|
||||
backgroundImage: `url(/api/avatars/${avatar.name}.webp)`,
|
||||
}}
|
||||
/>
|
||||
<Badge
|
||||
color={gradeColors[avatar.stuff_ui!.grade]}
|
||||
className={styles.grade}
|
||||
>
|
||||
<StarFilledIcon />
|
||||
{strings.grades[avatar.stuff_ui!.grade]}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className={styles.description}>
|
||||
<Text>
|
||||
{hasDescription ? (
|
||||
<>
|
||||
{description} {obtaining}
|
||||
</>
|
||||
) : (
|
||||
strings.avatars.no_description
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<Link
|
||||
underline="never"
|
||||
href={`/api/avatars/${avatar.name}.webp`}
|
||||
download
|
||||
>
|
||||
<Button>
|
||||
<DownloadIcon /> {strings.avatars.download}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
interface SeriesProps {
|
||||
avatars: Avatar[];
|
||||
}
|
||||
|
||||
function Series(props: PropsWithSkeleton<SeriesProps>) {
|
||||
const count = Math.min(props.skeleton ? 0 : props.avatars.length, MAX_CARDS);
|
||||
const strings = useStrings();
|
||||
|
||||
return (
|
||||
<div className={styles.series}>
|
||||
{!props.skeleton &&
|
||||
props.avatars.slice(-MAX_CARDS).map((avatar, index) => (
|
||||
<div
|
||||
key={avatar.name}
|
||||
className={styles.entry}
|
||||
style={{
|
||||
filter: `brightness(${(index + 1) / count})`,
|
||||
top: `calc(${index} * ${CARD_TRIM})`,
|
||||
left: `calc(${index} * ${CARD_TRIM})`,
|
||||
width: `calc(100% - ${CARD_TRIM} * ${count - 1})`,
|
||||
height: `calc(100% - ${CARD_TRIM} * ${count - 1})`,
|
||||
backgroundImage: `url(/api/avatars/${avatar.name}.webp)`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{!props.skeleton && props.avatars.length > 1 && (
|
||||
<Badge color="gray" highContrast className={styles.count}>
|
||||
{literals(strings.units.x, { value: props.avatars.length })}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{props.skeleton && <Skeleton className={styles.entry} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.avatar-controls {
|
||||
display: flex;
|
||||
|
||||
.search {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||