Compare commits

..
171 Commits
Author SHA1 Message Date
TrèsAbhi 6a1b786dae fix: [] 2024-05-03 09:19:29 -05:00
TrèsAbhi ea1eeb4366 fix: first time tank deltas 2024-05-03 09:17:09 -05:00
TrèsAbhi 15bec37a33 feat: logs 2024-05-03 09:10:23 -05:00
TrèsAbhi 4bba794bfb feat: disable login 2024-05-03 09:04:35 -05:00
TrèsAbhi 82014845cb feat: add column 2024-05-03 09:02:06 -05:00
TrèsAbhi 74a83d17e7 feat: better layout 2024-05-02 18:27:27 -05:00
TrèsAbhi 26ea06b091 fix: undef window 2024-05-02 18:08:58 -05:00
TrèsAbhi 352cb70830 feat: totals 2024-05-01 22:11:12 -05:00
TrèsAbhi ff92b3dd45 feat: login auth 2024-05-01 21:04:31 -05:00
TrèsAbhi 99ed269723 fix: old code 2024-05-01 20:56:38 -05:00
TrèsAbhi c24639dd0c feat: deltas 2024-05-01 20:54:27 -05:00
TrèsAbhi cceebd1980 fix: redirect to tracker 2024-05-01 20:36:35 -05:00
TrèsAbhi 596018b3b2 feat: ghost login button 2024-05-01 20:27:10 -05:00
TrèsAbhi 8bc3e2c6b9 feat: stat generalzation 2024-05-01 17:55:13 -05:00
TrèsAbhi aafba7b6c8 feat: link 2024-05-01 17:29:40 -05:00
TrèsAbhi d56be26db2 feat: since timer 2024-05-01 17:23:33 -05:00
TrèsAbhi e740274e5a feat: table 2024-05-01 17:20:08 -05:00
TrèsAbhi 0634804914 Merge pull request #116 from tresabhi/main 2024-05-01 16:37:22 -05:00
TrèsAbhi 4d908493f1 feat: experimental 2024-05-01 16:36:34 -05:00
TrèsAbhi 23d43f131b fix: capitalize K 2024-05-01 16:35:28 -05:00
TrèsAbhi 8a1b8673dc feat: updated favicon 2024-05-01 16:34:46 -05:00
TrèsAbhi 8682b2ea78 feat: rearranged navbar 2024-05-01 16:33:22 -05:00
TrèsAbhi 6e32b41b15 feat: convinient login 2024-05-01 13:04:31 -05:00
TrèsAbhi 6223773c71 feat: move health to right 2024-04-30 22:20:31 -05:00
TrèsAbhi 7fd4c931e5 feat: remove flair 2024-04-30 10:01:23 -05:00
TrèsAbhi 1011c76cda feat: add n90 2024-04-30 09:57:37 -05:00
TrèsAbhi 38fa15ab97 feat: cc account tracking 2 2024-04-28 19:16:08 -05:00
TrèsAbhi d2b18360ed feat: cc account tracking 2024-04-28 18:43:12 -05:00
TrèsAbhi 6ae86a7a70 feat: some protobuf parsing 2024-04-28 17:06:31 -05:00
TrèsAbhi 6b175238b2 feat: subtitle 2024-04-27 11:07:58 -05:00
TrèsAbhi 2a2a7a067c feat: map definitions 2024-04-27 10:50:09 -05:00
TrèsAbhi 25788dedf4 feat: merge types 2024-04-26 12:51:35 -05:00
TrèsAbhi 391990676f feat: wn8 and labels 2024-04-26 12:34:59 -05:00
TrèsAbhi 8c16fdf9b5 fix 2024-04-25 14:00:13 -05:00
TrèsAbhi c30358248f fix: use production videos 2024-04-25 14:00:00 -05:00
TrèsAbhi 62b218a44d feat: some more functionality 2024-04-25 13:47:30 -05:00
TrèsAbhi 1f30d1d2c3 feat: mostly complete rating command 2024-04-24 16:22:48 -05:00
TrèsAbhi b1917c73f2 Update Party3.tsx 2024-04-24 08:13:18 -05:00
TrèsAbhi 2d3325f13a fix: dir 2024-04-23 16:08:13 -05:00
TrèsAbhi 983b96fd99 feat: some work 2024-04-23 16:07:59 -05:00
TrèsAbhi 2c105d7a89 feat: some unfinished ideas 2024-04-23 15:51:16 -05:00
TrèsAbhi 28348552f0 fix: watermark 2024-04-23 15:07:21 -05:00
TrèsAbhi 449f2b0677 fix: more updates to docs 2024-04-23 15:06:24 -05:00
TrèsAbhi 5346ab576f fix: realign 2024-04-23 10:33:24 -05:00
TrèsAbhi d041b353c9 feat: logs 2024-04-23 10:25:59 -05:00
TrèsAbhi 77575168a0 feat: better message 2024-04-23 10:24:00 -05:00
TrèsAbhi 051997d2d3 feat: kit 3 2024-04-23 10:19:32 -05:00
TrèsAbhi e53bdd8e19 fix: core 2024-04-23 09:57:59 -05:00
TrèsAbhi 4013192faa feat: kit 2 2024-04-23 09:51:32 -05:00
TrèsAbhi 3da009f859 feat: kit 2024-04-23 09:42:08 -05:00
TrèsAbhi 2a5888c033 feat: hero 2024-04-22 12:51:46 -05:00
TrèsAbhi ded98d78d4 fix: link 2024-04-22 12:48:07 -05:00
TrèsAbhi f4b231de10 fix: more paths 2024-04-22 12:46:47 -05:00
TrèsAbhi 9fda3c6861 fix: videos 2024-04-22 12:46:15 -05:00
TrèsAbhi d3f58d9cda Merge pull request #111 from tresabhi/dev-rinth 2024-04-22 12:43:26 -05:00
TrèsAbhi 3ef793a776 fix: rinth package 2024-04-22 12:43:23 -05:00
TrèsAbhi 13a901c839 Merge branch 'main' into dev-rinth 2024-04-22 12:43:22 -05:00
TrèsAbhi 5ede2324b3 Update Party3.tsx 2024-04-22 12:27:43 -05:00
TrèsAbhi 4e8f1a4a71 fix: ? 2024-04-22 10:27:46 -05:00
TrèsAbhi eae9cbf971 feat: stop excessive tank armor and model name spam 2024-04-22 10:07:23 -05:00
TrèsAbhi 0eac25f1c8 Merge pull request #108 from tresabhi/imgbot 2024-04-22 10:01:36 -05:00
TrèsAbhi c281e9b66f feat: cumulative video indexing 2024-04-22 09:38:00 -05:00
TrèsAbhi eb7fc62e00 Merge pull request #110 from tresabhi/main 2024-04-22 09:09:54 -05:00
TrèsAbhi d5f83c444e feat; gap 2024-04-21 19:45:47 -05:00
TrèsAbhi c890bfa8bd feat: move up 2024-04-21 19:41:42 -05:00
TrèsAbhi 9ead3eb627 feat: automatic videos 2024-04-21 19:40:38 -05:00
TrèsAbhi b8db78c328 fix: secrets 2024-04-21 18:26:15 -05:00
TrèsAbhi 8b65b94851 feat: force 2024-04-21 18:01:02 -05:00
TrèsAbhi 9da723ed31 feat: logs 2024-04-21 17:40:04 -05:00
TrèsAbhi 2f48dbc25c feat: more shell properties 2024-04-21 17:39:24 -05:00
TrèsAbhi 730d6f2bb8 feat: logs 2024-04-21 17:25:35 -05:00
TrèsAbhi 335ae4493f fix: render normal armor 2024-04-21 17:16:25 -05:00
TrèsAbhi eb83039967 feat: include lower part of barrel 2024-04-21 17:15:23 -05:00
TrèsAbhi 6e91810565 fix: select all rows 2024-04-21 17:05:35 -05:00
TrèsAbhi a55c259205 feat: videos automation 2024-04-21 17:05:07 -05:00
TrèsAbhi b5188331bd feat: remove textual reference 2024-04-21 12:17:33 -05:00
TrèsAbhi 2b354770c7 fix: fragment key 2024-04-21 11:16:46 -05:00
TrèsAbhi 11e4009303 fix: skill keys 2024-04-21 11:13:32 -05:00
TrèsAbhi 9bce5e5d75 fix: duplicate keys 2024-04-21 11:12:52 -05:00
TrèsAbhi 8e8d68a5b5 fix: antagonist bar keys 2024-04-21 11:10:44 -05:00
TrèsAbhi 2c6fcdf21f fix: navbar menu key 2024-04-21 11:09:53 -05:00
TrèsAbhi abb72fb674 fix: jsx tree node uuids 2024-04-21 11:09:24 -05:00
TrèsAbhi b805c2e3b4 fix: video key 2024-04-21 11:07:19 -05:00
TrèsAbhi 7e6aa41ebb fix: no undefined clipping planes 2024-04-21 11:06:40 -05:00
TrèsAbhi a2cfbda932 feat: start work of text 2024-04-21 11:05:42 -05:00
TrèsAbhi 0b222be6ef Merge pull request #109 from tresabhi/main 2024-04-21 10:50:25 -05:00
TrèsAbhi 1548813a21 feat: horizontal ad 2024-04-21 10:41:51 -05:00
TrèsAbhi c615284d87 feat: re-enable adsense 2024-04-21 10:37:44 -05:00
TrèsAbhi 6a48638171 fix: use client 2024-04-21 10:26:08 -05:00
TrèsAbhi 0b4829abd3 fix: use use effect 2024-04-21 10:25:43 -05:00
TrèsAbhi 57cd3aae50 feat: pub ads 2024-04-21 10:21:52 -05:00
TrèsAbhi 6298d6230f Update ads.txt 2024-04-21 09:13:56 -05:00
TrèsAbhi df7a5bb8a0 fix: fullscreen card 2024-04-20 21:40:41 -05:00
TrèsAbhi dd41cac3ac feat: videos 2024-04-20 21:01:28 -05:00
TrèsAbhi f17b275aa7 feat: add blitzkrieg name to older logs 2024-04-20 12:09:23 -05:00
TrèsAbhi 4a04f8a8f3 feat: generalized asset repo 2024-04-20 12:08:43 -05:00
TrèsAbhi 0f7d682782 feat: logos 2024-04-20 12:07:20 -05:00
TrèsAbhi 14cecdebe3 feat: reset asset name to krieg temporarily 2024-04-20 12:02:33 -05:00
TrèsAbhi c2b936c925 feat: rename to blitzrinth 2024-04-20 12:00:48 -05:00
TrèsAbhi 0fd17d9c96 feat: logs 2024-04-20 11:50:16 -05:00
TrèsAbhi b0645c39a0 fix: high end consumables increase 2024-04-20 11:45:41 -05:00
TrèsAbhi 1d947399ab fix: intra-clip for auto loaders 2024-04-20 11:43:15 -05:00
TrèsAbhi 659dddbe8e feat: aggressive donate button 2024-04-20 11:40:10 -05:00
TrèsAbhi a5c1223f7d feat: simpler footer 2024-04-20 11:20:52 -05:00
TrèsAbhi dc084f8d34 feat: compare button 2024-04-19 10:08:00 -05:00
TrèsAbhi 44b5907435 feat: tankopedia button 2024-04-19 09:59:21 -05:00
ImgBotApp 052fbec628 [ImgBot] Optimize images
*Total -- 6,696.66kb -> 4,836.70kb (27.77%)

/public/assets/banners/discord.png -- 1,632.36kb -> 921.18kb (43.57%)
/public/assets/banners/compare.png -- 2,571.28kb -> 1,799.75kb (30.01%)
/docs/public/assets/images/hero.png -- 183.03kb -> 147.76kb (19.27%)
/public/assets/banners/statistics.png -- 2,309.99kb -> 1,968.00kb (14.8%)

Signed-off-by: ImgBotApp <[email protected]>
2024-04-19 14:37:54 +00:00
TrèsAbhi 23d8c54366 feat: super duper secret map 2024-04-19 09:26:53 -05:00
TrèsAbhi dd88d42884 feat: logs 2024-04-19 09:17:22 -05:00
TrèsAbhi ed676f73b2 feat: og descs 2024-04-19 09:14:22 -05:00
TrèsAbhi adb0a9af64 fix: title 2024-04-19 09:11:36 -05:00
TrèsAbhi ccded1b2f8 feat: hide statistics 2024-04-19 09:10:33 -05:00
TrèsAbhi 89d43b4791 feat: better titles 2024-04-19 09:09:12 -05:00
TrèsAbhi 2226ac9852 feat: gutted some navbar features 2024-04-19 09:03:45 -05:00
TrèsAbhi 29bace875e fix: remove commit 2024-04-18 20:27:48 -05:00
TrèsAbhi 04107c10b7 feat: hamburger meny 2024-04-18 20:26:35 -05:00
TrèsAbhi 0a1e823ce4 feat: first developer mode enabling 2024-04-18 19:16:30 -05:00
TrèsAbhi c656d31618 feat: a few settings 2024-04-18 19:11:50 -05:00
TrèsAbhi 7eda4825ea feat: more navbar buttons 2024-04-18 18:42:57 -05:00
TrèsAbhi 60f0eb9a3c feat: tankopedia title 2024-04-18 18:29:29 -05:00
TrèsAbhi ce5c9f4afa feat: page exclusive titles 2024-04-18 18:28:46 -05:00
TrèsAbhi e834d31d92 feat: use lz4js types instead of home brewed 2024-04-18 18:23:23 -05:00
TrèsAbhi b679393b8a Merge branch 'main' into dev 2024-04-17 10:16:31 -05:00
TrèsAbhi eb5f79228b fix: bring back test tanks button 2024-04-17 09:52:15 -05:00
TrèsAbhi 6082576b60 feat: start of a meny 2024-04-17 09:50:46 -05:00
TrèsAbhi 92e9073939 things 2024-04-17 09:31:47 -05:00
TrèsAbhi d6669a84d6 Update index.ts 2024-04-17 08:34:20 -05:00
TrèsAbhi 0c4debe327 Update index.ts 2024-04-17 08:18:56 -05:00
TrèsAbhi 9f41bab28a feat: disabled stats 2024-04-16 22:13:21 -05:00
TrèsAbhi b906b42998 feat: search bar 2024-04-16 22:12:51 -05:00
TrèsAbhi 728a11efa4 feat: reduced gap 2024-04-16 22:06:24 -05:00
TrèsAbhi c35916c291 feat: colors 2024-04-16 22:05:36 -05:00
TrèsAbhi 2fd2e93f55 feat: shadows 2024-04-16 21:58:08 -05:00
TrèsAbhi b566d5982c feat: better icons 2024-04-16 21:49:30 -05:00
TrèsAbhi bef43e9ae6 feat: tank icons 2024-04-16 19:36:49 -05:00
TrèsAbhi e6404d5c77 feat: better spacing 2024-04-16 19:07:06 -05:00
TrèsAbhi f4d58015f5 feat: somethings 2024-04-16 19:03:37 -05:00
TrèsAbhi cd77d24dc6 feat: statistics 2024-04-16 18:51:20 -05:00
TrèsAbhi e25a3cd868 feat: title 2024-04-16 13:53:01 -05:00
TrèsAbhi 68923fe800 feat: icons 2024-04-16 13:49:33 -05:00
TrèsAbhi ccc6ddd5c9 Merge branch 'main' of https://github.com/tresabhi/blitzkrieg 2024-04-16 13:45:33 -05:00
TrèsAbhi 1c28b33da3 Merge branch 'dev' of https://github.com/tresabhi/blitzkrieg into dev 2024-04-16 13:45:18 -05:00
TrèsAbhi 900337f3ce feat: log 2024-04-16 13:45:17 -05:00
TrèsAbhi fae9f8bded feat: time in manifest 2024-04-16 13:45:03 -05:00
TrèsAbhi a0d78f89ff fix: remove logo on small screens 2024-04-16 13:45:03 -05:00
TrèsAbhi 6853d63bbb feat: change to utc 2024-04-16 13:45:03 -05:00
TrèsAbhi 2b27954a3b fix: max dpm on double shot auto reloaders 2024-04-16 13:45:03 -05:00
TrèsAbhi 7aa0b65d1d feat: null handling 2024-04-16 13:45:03 -05:00
TrèsAbhi 3e7a73c528 feat: cleaner fetch blitz func 2024-04-16 13:45:02 -05:00
TrèsAbhi 076785a9ad feat: more efficient 2024-04-16 13:45:02 -05:00
TrèsAbhi 74828e3c45 feat: rts 2024-04-16 13:45:02 -05:00
TrèsAbhi 310640c1ec feat: base work 2024-04-16 13:45:02 -05:00
TrèsAbhi c49c56fba3 Merge pull request #107 from tresabhi/dev 2024-04-16 13:44:16 -05:00
TrèsAbhi cc744f6154 Merge branch 'main' into dev 2024-04-16 13:44:09 -05:00
TrèsAbhi 93bf4a4742 Dev (#106) 2024-04-16 13:43:44 -05:00
TrèsAbhi badeb588dd feat: time in manifest 2024-04-16 09:42:48 -05:00
TrèsAbhi 5876bf35b4 fix: remove logo on small screens 2024-04-16 09:35:10 -05:00
TrèsAbhi 73903114c9 feat: change to utc 2024-04-16 09:28:01 -05:00
TrèsAbhi b29f7ca7f5 fix: max dpm on double shot auto reloaders 2024-04-16 09:24:28 -05:00
TrèsAbhi 9d715c23e5 redacted 2024-04-16 09:18:12 -05:00
TrèsAbhi 4ff5f7cdeb feat: null handling 2024-04-15 13:25:35 -05:00
TrèsAbhi dccfe3d652 feat: cleaner fetch blitz func 2024-04-15 13:19:25 -05:00
TrèsAbhi d780d32291 feat: more efficient 2024-04-15 09:27:59 -05:00
TrèsAbhi ebbdbc53a4 feat: rts 2024-04-13 22:13:57 -05:00
TrèsAbhi 9bc1635680 feat: base work 2024-04-13 18:20:49 -05:00
TrèsAbhi d7d19a0b50 Merge branch 'main' of https://github.com/tresabhi/blitzkrieg 2024-04-13 10:43:20 -05:00
TrèsAbhi 1b6845abf8 fix: grammar 2024-04-13 10:43:09 -05:00
TrèsAbhi 545ee70cd0 feat: logs 2 2024-04-13 10:42:31 -05:00
TrèsAbhi c92729d0e0 feat: logs 2024-04-13 10:42:17 -05:00
TrèsAbhi 71c3b5d2ef feat: undefined mask handling 2024-04-13 10:38:13 -05:00
TrèsAbhi 7372075ba3 fix: remove duplicate mantlets 2024-04-13 10:36:13 -05:00
299 changed files with 4742 additions and 2671 deletions

No files matched your search

+2
View File
@@ -62,4 +62,6 @@ jobs:
$env:GH_TOKEN = "${{ secrets.GH_TOKEN }}" $env:GH_TOKEN = "${{ secrets.GH_TOKEN }}"
$env:DISCORD_TOKEN = "${{ secrets.DISCORD_TOKEN }}" $env:DISCORD_TOKEN = "${{ secrets.DISCORD_TOKEN }}"
$env:DATABASE_URL = "${{ secrets.DATABASE_URL }}" $env:DATABASE_URL = "${{ secrets.DATABASE_URL }}"
$env:VIDEOS_SHEET = "${{ secrets.VIDEOS_SHEET }}"
$env:GOOGLE_APPLICATION_CREDENTIALS = "${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}"
yarn build:assets --all-targets --depot ${{ github.ref == 'refs/heads/main' && '--production' || '' }} yarn build:assets --all-targets --depot ${{ github.ref == 'refs/heads/main' && '--production' || '' }}
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
- name: Download release assets - name: Download release assets
run: | run: |
DOWNLOAD_URL=$(curl -s https://api.github.com/repos/tresabhi/blitzkrieg/releases/latest \ DOWNLOAD_URL=$(curl -s https://api.github.com/repos/tresabhi/blitzkit/releases/latest \
| grep "browser_download_url.*bot.zip" \ | grep "browser_download_url.*bot.zip" \
| cut -d : -f 2,3 \ | cut -d : -f 2,3 \
| tr -d \") | tr -d \")
+1
View File
@@ -19,3 +19,4 @@ temp/
/.yarn /.yarn
depots depots
public/sitemap* public/sitemap*
/firebase.json
+3 -3
View File
@@ -1,3 +1,3 @@
[submodule "submodules/closedkrieg"] [submodule "submodules/blitzkit-closed"]
path = submodules/closedkrieg path = submodules/blitzkit-closed
url = https://github.com/tresabhi/closedkrieg url = https://github.com/tresabhi/blitzkit-closed
+19
View File
@@ -11,14 +11,20 @@
"applypatches", "applypatches",
"artefacts", "artefacts",
"atgm", "atgm",
"Avearges",
"AYUV", "AYUV",
"battleresults",
"bbox", "bbox",
"bepaint", "bepaint",
"betakrieg", "betakrieg",
"BINORMAL", "BINORMAL",
"Binormals", "Binormals",
"BKRL", "BKRL",
"BKRTS",
"BKRTSMANIFEST",
"bkwd", "bkwd",
"blitzkit",
"blitzrinth",
"blitzstars", "blitzstars",
"Camo", "Camo",
"CDON", "CDON",
@@ -33,6 +39,7 @@
"CUBETEXCOORD", "CUBETEXCOORD",
"customperiod", "customperiod",
"datasource", "datasource",
"dbid",
"Ddpf", "Ddpf",
"Ddsd", "Ddsd",
"debugflags", "debugflags",
@@ -76,10 +83,13 @@
"loadouts", "loadouts",
"loddist", "loddist",
"lods", "lods",
"lossrate",
"mantlets",
"metallicness", "metallicness",
"minitelrose", "minitelrose",
"Minotauro", "Minotauro",
"MIPMAPCOUNT", "MIPMAPCOUNT",
"mišek",
"modeltransform", "modeltransform",
"MURICA", "MURICA",
"NEGATIVEX", "NEGATIVEX",
@@ -101,12 +111,17 @@
"POSITIVEY", "POSITIVEY",
"POSITIVEZ", "POSITIVEZ",
"Promisable", "Promisable",
"protobuf",
"protos",
"Pyogenics", "Pyogenics",
"ratingleaderboard", "ratingleaderboard",
"raycaster", "raycaster",
"researchable", "researchable",
"Resvg", "Resvg",
"rizz",
"roboto", "roboto",
"rtsc",
"RTSM",
"SCPG", "SCPG",
"scroller", "scroller",
"searchclans", "searchclans",
@@ -141,6 +156,7 @@
"Unicum", "Unicum",
"UNLOCALIZED", "UNLOCALIZED",
"UNORM", "UNORM",
"unpickled",
"Unregistering", "Unregistering",
"vickers", "vickers",
"vitepress", "vitepress",
@@ -153,10 +169,13 @@
"Woah", "Woah",
"WOTB", "WOTB",
"wotblitz", "wotblitz",
"wotbreplay",
"WOTINSPECTOR",
"xffn", "xffn",
"xkdkw", "xkdkw",
"XMLDVPL", "XMLDVPL",
"YAMLDVPL", "YAMLDVPL",
"youtubers",
"zustand" "zustand"
], ],
"deno.enable": false, "deno.enable": false,
+7 -7
View File
@@ -1,4 +1,4 @@
# Blitzkrieg # BlitzKit
[![built with Codeium](https://codeium.com/badges/main)](https://codeium.com) [![built with Codeium](https://codeium.com/badges/main)](https://codeium.com)
@@ -6,18 +6,18 @@ Tools for everything World of Tanks Blitz
## Invite the bot ## Invite the bot
Go to [application directory for Blitzkrieg](https://discord.com/application-directory/1097673957865443370) and click "Add to Server." Voila! You're ready to go. Go to [application directory for BlitzKit](https://discord.com/application-directory/1097673957865443370) and click "Add to Server." Voila! You're ready to go.
![Blitzkrieg application directory](https://i.imgur.com/JlEA0J1.png) ![BlitzKit application directory](https://i.imgur.com/YWCtOuJ.png)
## Visit the website ## Visit the website
### https://blitz-krieg.vercel.app/ ### https://blitzkit.vercel.app/
![Blitzkrieg website](https://i.imgur.com/eRIZe5P.png) ![BlitzKit website](https://i.imgur.com/bdYMUCL.png)
## Visit the documentation website ## Visit the documentation website
### https://tresabhi.github.io/blitzkrieg/ ### https://tresabhi.github.io/blitzkit/
![Blitzkrieg documentation website](https://i.imgur.com/TOt5fMx.png) ![BlitzKit documentation website](https://i.imgur.com/hm3KTsF.png)
+3 -3
View File
@@ -5,9 +5,9 @@ import { defineConfig } from 'vitepress';
import { getSidebar } from 'vitepress-plugin-auto-sidebar'; import { getSidebar } from 'vitepress-plugin-auto-sidebar';
export default defineConfig({ export default defineConfig({
title: 'blitzkrieg', title: 'BlitzKit',
description: '🎉 All-in-one Discord bot for everything World of Tanks Blitz', description: '🎉 All-in-one Discord bot for everything World of Tanks Blitz',
base: '/blitzkrieg/', base: '/blitzkit/',
outDir: '../dist/docs', outDir: '../dist/docs',
themeConfig: { themeConfig: {
@@ -33,7 +33,7 @@ export default defineConfig({
], ],
socialLinks: [ socialLinks: [
{ icon: 'github', link: 'https://github.com/tresabhi/blitzkrieg' }, { icon: 'github', link: 'https://github.com/tresabhi/blitzkit' },
], ],
}, },
}); });
+1 -1
View File
@@ -1,4 +1,4 @@
# 1.19.0 # Blitzkrieg 1.19.0
Introducing the Blitzkrieg Compare tool! Introducing the Blitzkrieg Compare tool!
+11
View File
@@ -0,0 +1,11 @@
# Blitzkrieg 1.19.1
A minor-major fix to the armor sandbox. This update only affects the website.
## Fixes
- Guns are now clipped properly allowing some tanks like the 252U and T34's mantlets to be penetrable (usually with premium shells) as they are in-game
| Before | After |
| ------------------------------------------ | ----------------------------------------- |
| ![before](https://i.imgur.com/wB0kuk0.png) | ![after](https://i.imgur.com/vxp8wB0.png) |
+6
View File
@@ -0,0 +1,6 @@
# Blitzkrieg 1.19.2
## Fixes
- Maximum DPM for double shot auto-reloaders is now calculate correctly with intra-clip in mind
- Blitzkrieg logo is now removed on lower resolutions to allow for other buttons to fit in the title bar of the Tankopedia
+13
View File
@@ -0,0 +1,13 @@
# Blitzkrieg 1.19.3
Google rizz. This version only affects the website.
## Features
- The titles and descriptions of the Tankopedia tank pages are now more informative
- Added a settings page
- Added a hamburger menu to the navigation bar
## Fixes
- Tank icons in URL previews are now presented properly
+18
View File
@@ -0,0 +1,18 @@
# Blitzkrieg 1.19.4
I genuinely have no idea how these bugs sneaked in here. This update only affects the website.
## Features
- Added a button in Compare which links to the corresponding Tankopedia page
- Added a button in Tankopedia that links to the corresponding Compare page
- If the two tanks (the shooter and the one of which armor is displayed) in the Tankopedia are different, they will be both added by default in the Compare page
- Simplified the footer and added a link to my website and the credits page
- Added an aggressive donate button right at the top of the home page
## Fixes
Thanks to mišek for these bug reports!
- The intra-clip time is now displayed for auto-loaders
- High End Consumables now increase the duration of consumables correctly instead of decreasing it
+19
View File
@@ -0,0 +1,19 @@
# Blitzkrieg 1.19.5
The tankopedia, now more helpful than ever! This update only affects the website.
## Features
- The Tankopedia pages for tanks now include videos from YouTubers
- The selection of tanks that have videos is very small currently but you can help expand it by dropping suggestions on [the official Discord server](https://discord.gg/nDt7AjGJQH)
- Added shell normalization and ricochet angles to the Tankopedia and Compare
## Fixes
- Removed undefined clipping planes
- Added proper keying to all JSX tree nodes, navbar menu items, antagonist bar shells, skill rows and fragments of tank stats in the Tankopedia
- The second half of barrels for tanks like the T-100 LT are now properly included
## Technical Features
- The list of videos is fetched transpiled from a Google Sheets spreadsheet into a CSV file in the asset repository
+3
View File
@@ -0,0 +1,3 @@
# BlitzKit 1.19.6
Renamed Blitzkrieg to BlitzKit.
+18
View File
@@ -0,0 +1,18 @@
# BlitzKit 1.20.0
**IMPORTANT FOR STREAMERS:** This version removed the session tracker embed. Embeds will be reintroduced in the future.
## Features
- Reworked session tracker
- Played tanks are now displayed as a clean table that fits the theme of the website
- Tank images are now displayed along with them being linked to their respective tankopedia pages
- The UI has been simplified
- You can also change what columns are shown in the table but this is a work in progress feature
- Added the `/replay` command
- This command uploads your replays to WOTINSPECTOR and displays the battle results
## Fixes
- Fixed website logo to be "BK"
- Fixed the logo of the website to have a capital "K"
+2 -2
View File
@@ -1,9 +1,9 @@
# Blitzkrieg Rating Leaderboard # Blitzkit Rating Leaderboard
A straightforward way of storing the entire World of Tanks Blitz rating leaderboard to disk. A straightforward way of storing the entire World of Tanks Blitz rating leaderboard to disk.
```cpp ```cpp
primary BlitzkriegRatingLeaderboard { primary BlitzkitRatingLeaderboard {
utf8"BKRL" magic; utf8"BKRL" magic;
Header header; Header header;
primary Body<header> body; primary Body<header> body;
@@ -1,15 +1,16 @@
# Team # Team
- TrèsAbhi: creator & programmer - TrèsAbhi: creator & programmer
- Prince_NA: game advisor & tester
# Contributors # Contributors
- S0AP: bot verification process & initial advertizement platform - Prince_NA: general game advisor and tester
- S0AP: bot verification and initial advertizement platform
- Vovko: discord bot rendering pipeline - Vovko: discord bot rendering pipeline
- Maddox: game file decompression - Maddox: game file decompression
- Pyogenics: tank model decompression - Pyogenics: tank model parsing
- Aeson: game files layout - Aeson: game files layout
- Minitelrose, ChickenMan7777: 3-caliber rule edge cases - Minitelrose, ChickenMan7777: 3-caliber rule edge cases
- \_34, HisRoyalFatness, Synx, xkdkw: embed UX - \_34, HisRoyalFatness, Synx, xkdkw: embed UX
- [OPTML], Aeson, \_34, Droodles: beta testing tankopedia - [OPTML], Aeson, \_34, Droodles: beta testing Tankopedia
- [OPTML], Droodles: beta testing Compare
+2 -2
View File
@@ -4,7 +4,7 @@
## Bot ## Bot
Blitzkrieg is available as a Discord bot and can be [installed very easily](/guide/invite). BlitzKit is available as a Discord bot and can be [installed very easily](/guide/invite).
- Track statistics - Track statistics
- Find inactive players in a clan - Find inactive players in a clan
@@ -15,7 +15,7 @@ Blitzkrieg is available as a Discord bot and can be [installed very easily](/gui
## Website ## Website
Provides a bunch of very helpful tools; https://blitz-krieg.vercel.app/ Provides a bunch of very helpful tools; https://blitzkit.vercel.app/
- Session tracker - Session tracker
- Rating leaderboard - Rating leaderboard
+1 -1
View File
@@ -1,3 +1,3 @@
# Inviting the Bot # Inviting the Bot
Go to [application directory for Blitzkrieg](https://discord.com/application-directory/1097673957865443370) and click "Add to Server." Voila! You're ready to go. Go to [application directory for BlitzKit](https://discord.com/application-directory/1097673957865443370) and click "Add to Server." Voila! You're ready to go.
+71
View File
@@ -0,0 +1,71 @@
# BlitzKit Regular Tank Stats
A straightforward way of storing the entire World of Tanks Blitz tank statistics for players to disk.
## Manifest `manifest.rtsm`
```cpp
primary RegularTankStatisticsManifest {
utf8"RTSM" magic;
Header header;
primary Body body;
}
Header {
uint16 version; // always 1
}
Body {
uint32 time;
uint8 base;
}
```
## Chunk `*.rtsc`
```cpp
primary RegularTankStatisticsChunk {
utf8"RTSC" magic;
Header header;
primary Body body<header.playerCount>;
}
Header {
uint16 version; // always 1
uint32 playerCount;
}
Body<uint32 playerCount> {
Player[playerCount] players;
}
Player {
uint32 id;
uint16 tankCount;
Tank[tankCount] tanks;
}
Tank {
uint32 id;
uint32 battleLifeTime;
uint32 lastBattleTime;
uint8 markOfMastery;
uint32 battles;
uint32 capturePoints;
uint32 damageDealt;
uint32 damageReceived;
uint32 droppedCapturePoints;
uint32 frags;
uint32 frags8p;
uint32 hits;
uint32 losses;
uint8 maxFrags;
uint16 maxXp;
uint32 shots;
uint32 spotted;
uint32 survivedBattles;
uint32 winAndSurvived;
uint32 wins;
uint32 xp;
}
```
+4 -4
View File
@@ -3,12 +3,12 @@
layout: home layout: home
hero: hero:
name: 'blitzkrieg' name: 'BlitzKit'
text: Everything Blitz, right in Discord text: Everything Blitz, right in Discord
tagline: All-in-one Discord bot for everything World of Tanks Blitz tagline: All-in-one Discord bot for everything World of Tanks Blitz
image: image:
src: '/assets/images/hero.png' src: '/assets/images/hero.png'
alt: 'blitzkrieg' alt: 'blitzkit'
actions: actions:
- theme: brand - theme: brand
text: Add to Discord Server text: Add to Discord Server
@@ -20,11 +20,11 @@ hero:
features: features:
- title: Clean and Efficient UI - title: Clean and Efficient UI
icon: 🚀 icon: 🚀
details: Blitzkrieg is where concise meets efficient. Get the information you need in the way you want. details: BlitzKit is where concise meets efficient. Get the information you need in the way you want.
- title: Endless Possibilities - title: Endless Possibilities
icon: 🛠 icon: 🛠
details: With endless ways to customize the output, it's guaranteed that you'll always get the information you need. details: With endless ways to customize the output, it's guaranteed that you'll always get the information you need.
- title: Easy to Use - title: Easy to Use
icon: 💻 icon: 💻
details: Leveraging Discord's, Blitz Stars', and Wargaming's powerful APIS, Blitzkrieg does all the hard work for you. details: Leveraging Discord's, Blitz Stars', and Wargaming's powerful APIS, Blitzkit does all the hard work for you.
--- ---
+4 -8
View File
@@ -1,22 +1,18 @@
# Blitzkrieg Privacy Policy # BlitzKit Privacy Policy
Blitzkrieg collects little to no data. Regardless, here is an exhaustive list of Blitzkrieg's data collection practices. BlitzKit collects little to no data. Regardless, here is an exhaustive list of BlitzKit's data collection practices.
## Logs ## Logs
All Blitzkrieg logs are available to the public as an effort for open source transparency. A history of all logs [can be found on GitHub](https://github.com/tresabhi/blitzkrieg/actions/workflows/host.yml). All logs expire automatically and are permanently deleted after 30 days. However, anyone may chose to save a local copy of the logs which is not tracked by GitHub. All BlitzKit logs are available to the public as an effort for open source transparency. A history of all logs [can be found on GitHub](https://github.com/tresabhi/blitzkit/actions/workflows/host.yml). All logs expire automatically and are permanently deleted after 30 days. However, anyone may chose to save a local copy of the logs which is not tracked by GitHub.
## Commands ## Commands
Everytime a command a command is used, the command name and parameters provided is logged. Any identifying information like manually provided in-game usernames are also recorded. Everytime a command a command is used, the command name and parameters provided is logged. Any identifying information like manually provided in-game usernames are also recorded.
## Errors
Everytime an error occurs, a detailed stack trace, scope of the error, and the error message is logged both in the GitHub logs and in the public channel [#🔲︱blitzkrieg-logs](https://discord.com/channels/734786591205359697/880650462540476416) in [the official Discord server](https://discord.gg/nDt7AjGJQH).
## Databases ## Databases
No databases are used by Blitzkrieg meaning that no data is collected outside of what has already been mentioned above. This is no permanent state. No databases are used by BlitzKit meaning that no data is collected outside of what has already been mentioned above. This is no permanent state.
## 3rd Party ## 3rd Party
+3 -3
View File
@@ -1,12 +1,12 @@
# Blitzkrieg Terms of Service # BlitzKit Terms of Service
## About ## About
Blitzkrieg is a multi-function Discord bot that serves users with needs specific to the game World of Tanks Blitz. BlitzKit is a multi-function Discord bot that serves users with needs specific to the game World of Tanks Blitz.
## License ## License
Blitzkrieg is licensed under the MIT License. BlitzKit is licensed under the MIT License.
## 3rd Party ## 3rd Party
Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 35 KiB

+11 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "blitzkrieg", "name": "blitzkit",
"version": "1.19.0", "version": "1.20.0",
"description": "🎉 Tools for everything World of Tanks Blitz", "description": "🎉 Tools for everything World of Tanks Blitz",
"main": "src/server.ts", "main": "src/server.ts",
"type": "module", "type": "module",
@@ -12,7 +12,8 @@
"build:docs": "vitepress build docs", "build:docs": "vitepress build docs",
"build:ratingleaderboard": "vite-node --script scripts/buildRatingLeaderboard", "build:ratingleaderboard": "vite-node --script scripts/buildRatingLeaderboard",
"build:assets": "vite-node --max_old_space_size=16384 --script scripts/buildAssets/index", "build:assets": "vite-node --max_old_space_size=16384 --script scripts/buildAssets/index",
"build:test": "vite-node --script scripts/buildTest", "test": "vite-node --script scripts/test",
"test2": "vite-node --script scripts/test2",
"dev:bot": "vite-node --script scripts/build && yarn start:bot", "dev:bot": "vite-node --script scripts/build && yarn start:bot",
"dev:docs": "vitepress dev docs", "dev:docs": "vitepress dev docs",
"dev:website": "next dev", "dev:website": "next dev",
@@ -44,6 +45,7 @@
"@resvg/resvg-js": "^2.4.1", "@resvg/resvg-js": "^2.4.1",
"@types/three": "^0.160.0", "@types/three": "^0.160.0",
"@vanilla-extract/css": "^1.13.0", "@vanilla-extract/css": "^1.13.0",
"adm-zip": "^0.5.12",
"bepaint": "^0.1.0-beta.3", "bepaint": "^0.1.0-beta.3",
"decompress": "^4.2.1", "decompress": "^4.2.1",
"discord.js": "^14.13.0", "discord.js": "^14.13.0",
@@ -52,6 +54,7 @@
"express": "^4.18.2", "express": "^4.18.2",
"false": "^0.0.4", "false": "^0.0.4",
"fuzzysort": "^2.0.4", "fuzzysort": "^2.0.4",
"googleapis": "^134.0.0",
"immer": "^10.0.2", "immer": "^10.0.2",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
@@ -59,14 +62,16 @@
"markdown-escape": "^2.0.0", "markdown-escape": "^2.0.0",
"next": "^14.1.0", "next": "^14.1.0",
"parse-dds": "^1.2.1", "parse-dds": "^1.2.1",
"pickleparser": "^0.2.1",
"postprocessing": "^6.34.1", "postprocessing": "^6.34.1",
"protobufjs": "^7.2.6",
"react": "^18.2.0", "react": "^18.2.0",
"react-device-detect": "^2.2.3", "react-device-detect": "^2.2.3",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-fuzzysort": "^1.0.1", "react-fuzzysort": "^1.0.1",
"react-promise-suspense": "^0.3.4", "react-promise-suspense": "^0.3.4",
"satori": "^0.10.13", "satori": "^0.10.13",
"sharp": "0.33.1", "sharp": "^0.33.3",
"three": "0.159.0", "three": "0.159.0",
"three-stdlib": "^2.29.4", "three-stdlib": "^2.29.4",
"vite-node": "^0.34.5", "vite-node": "^0.34.5",
@@ -77,12 +82,14 @@
}, },
"devDependencies": { "devDependencies": {
"@total-typescript/ts-reset": "^0.5.1", "@total-typescript/ts-reset": "^0.5.1",
"@types/adm-zip": "^0.5.5",
"@types/decompress": "^4.2.7", "@types/decompress": "^4.2.7",
"@types/escape-html": "^1.0.2", "@types/escape-html": "^1.0.2",
"@types/express": "^4.17.18", "@types/express": "^4.17.18",
"@types/js-yaml": "^4.0.8", "@types/js-yaml": "^4.0.8",
"@types/lodash": "^4.14.199", "@types/lodash": "^4.14.199",
"@types/lz4": "^0.6.4", "@types/lz4": "^0.6.4",
"@types/lz4js": "^0.2.1",
"@types/markdown-escape": "^1.1.0", "@types/markdown-escape": "^1.1.0",
"@types/node": "^20.6.5", "@types/node": "^20.6.5",
"@types/parse-dds": "^1.0.3", "@types/parse-dds": "^1.0.3",
+2
View File
@@ -1 +1,3 @@
google.com, pub-1289406790225052, DIRECT, f08c47fec0942fa0 google.com, pub-1289406790225052, DIRECT, f08c47fec0942fa0
appnexus.com, 14163, RESELLER
pubcenter.microsoft.com, 253617120, DIRECT
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 921 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

+3 -3
View File
@@ -1,7 +1,7 @@
User-agent: * User-agent: *
Allow: / Allow: /
Host: https://blitz-krieg.vercel.app Host: https://blitzkit.vercel.app
Sitemap: https://blitz-krieg.vercel.app/sitemap.txt Sitemap: https://blitzkit.vercel.app/sitemap.txt
Sitemap: https://blitz-krieg.vercel.app/sitemap.xml Sitemap: https://blitzkit.vercel.app/sitemap.xml
+2 -2
View File
@@ -3,8 +3,8 @@ import { parse as parsePath } from 'path';
import { parse as parseYaml } from 'yaml'; import { parse as parseYaml } from 'yaml';
import { readStringDVPL } from '../src/core/blitz/readStringDVPL'; import { readStringDVPL } from '../src/core/blitz/readStringDVPL';
import { writeDVPL } from '../src/core/blitz/writeDVPL'; import { writeDVPL } from '../src/core/blitz/writeDVPL';
import { secrets } from '../src/core/blitzkrieg/secrets'; import { secrets } from '../src/core/blitzkit/secrets';
import { dvp } from '../submodules/closedkrieg/src/dvp'; import { dvp } from '../submodules/blitzkit-closed/src/dvp';
import { DATA } from './buildAssets/constants'; import { DATA } from './buildAssets/constants';
const versionTextFile = await readStringDVPL(`${DATA}/version.txt.dvpl`); const versionTextFile = await readStringDVPL(`${DATA}/version.txt.dvpl`);
+2 -2
View File
@@ -2,8 +2,8 @@ import { readdir } from 'fs/promises';
import sharp from 'sharp'; import sharp from 'sharp';
import { readDVPLFile } from '../../src/core/blitz/readDVPLFile'; import { readDVPLFile } from '../../src/core/blitz/readDVPLFile';
import { readStringDVPL } from '../../src/core/blitz/readStringDVPL'; import { readStringDVPL } from '../../src/core/blitz/readStringDVPL';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkit/commitMultipleFiles';
import { DATA, POI } from './constants'; import { DATA, POI } from './constants';
export async function boosterIcons(production: boolean) { export async function boosterIcons(production: boolean) {
+2 -2
View File
@@ -1,6 +1,6 @@
import { readDVPLFile } from '../../src/core/blitz/readDVPLFile'; import { readDVPLFile } from '../../src/core/blitz/readDVPLFile';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkit/commitMultipleFiles';
import { DATA, POI } from './constants'; import { DATA, POI } from './constants';
export async function camouflageIcons(production: boolean) { export async function camouflageIcons(production: boolean) {
+2 -2
View File
@@ -1,7 +1,7 @@
import { readdir } from 'fs/promises'; import { readdir } from 'fs/promises';
import { readBase64DVPL } from '../../src/core/blitz/readBase64DVPL'; import { readBase64DVPL } from '../../src/core/blitz/readBase64DVPL';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkit/commitMultipleFiles';
import { DATA, POI } from './constants'; import { DATA, POI } from './constants';
export async function circleFlags(production: boolean) { export async function circleFlags(production: boolean) {
@@ -3,8 +3,8 @@ import { readDVPLFile } from '../../src/core/blitz/readDVPLFile';
import { readStringDVPL } from '../../src/core/blitz/readStringDVPL'; import { readStringDVPL } from '../../src/core/blitz/readStringDVPL';
import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL'; import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL'; import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkit/commitMultipleFiles';
import { DATA, POI } from './constants'; import { DATA, POI } from './constants';
import { ConsumablesCommon, ProvisionsCommon } from './definitions'; import { ConsumablesCommon, ProvisionsCommon } from './definitions';
+2 -2
View File
@@ -1,7 +1,7 @@
import sharp from 'sharp'; import sharp from 'sharp';
import { readDVPLFile } from '../../src/core/blitz/readDVPLFile'; import { readDVPLFile } from '../../src/core/blitz/readDVPLFile';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkit/commitMultipleFiles';
import { DATA } from './constants'; import { DATA } from './constants';
const ICONS = [ const ICONS = [
+67 -16
View File
@@ -7,24 +7,25 @@ import { readStringDVPL } from '../../src/core/blitz/readStringDVPL';
import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL'; import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL'; import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
import { toUniqueId } from '../../src/core/blitz/toUniqueId'; import { toUniqueId } from '../../src/core/blitz/toUniqueId';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { import {
ConsumableDefinitions, ConsumableDefinitions,
TankFilterDefinitionCategory, TankFilterDefinitionCategory,
} from '../../src/core/blitzkrieg/consumableDefinitions'; } from '../../src/core/blitzkit/consumableDefinitions';
import { import {
EquipmentDefinitions, EquipmentDefinitions,
EquipmentPreset, EquipmentPreset,
EquipmentRow, EquipmentRow,
} from '../../src/core/blitzkrieg/equipmentDefinitions'; } from '../../src/core/blitzkit/equipmentDefinitions';
import { GameDefinitions } from '../../src/core/blitzkrieg/gameDefinitions'; import { GameDefinitions } from '../../src/core/blitzkit/gameDefinitions';
import { MapDefinitions } from '../../src/core/blitzkit/mapDefinitions';
import { import {
ModelArmor, ModelArmor,
ModelDefinitions, ModelDefinitions,
} from '../../src/core/blitzkrieg/modelDefinitions'; } from '../../src/core/blitzkit/modelDefinitions';
import { ProvisionDefinitions } from '../../src/core/blitzkrieg/provisionDefinitions'; import { ProvisionDefinitions } from '../../src/core/blitzkit/provisionDefinitions';
import { SkillDefinitions } from '../../src/core/blitzkrieg/skillDefinitions'; import { SkillDefinitions } from '../../src/core/blitzkit/skillDefinitions';
import { superCompress } from '../../src/core/blitzkrieg/superCompress'; import { superCompress } from '../../src/core/blitzkit/superCompress';
import { import {
Crew, Crew,
CrewMember, CrewMember,
@@ -33,12 +34,12 @@ import {
TankDefinitionPrice, TankDefinitionPrice,
TankDefinitions, TankDefinitions,
Tier, Tier,
} from '../../src/core/blitzkrieg/tankDefinitions'; } from '../../src/core/blitzkit/tankDefinitions';
import { DATA, POI } from './constants'; import { DATA, POI } from './constants';
import { Avatar } from './skillIcons'; import { Avatar } from './skillIcons';
import { TankParameters } from './tankIcons'; import { TankParameters } from './tankIcons';
interface Strings { export interface BlitzStrings {
[key: string]: string; [key: string]: string;
} }
type ShellKind = type ShellKind =
@@ -325,7 +326,32 @@ type ConsumablesVehicleFilter =
| { name: string } | { name: string }
| { extendedTags: string }; | { extendedTags: string };
const blitzShellKindToBLitzkrieg: Record<ShellKind, ShellType> = { export interface Maps {
maps: {
[key: string]: {
id: number;
tags?: string;
localName: string;
avaliableInTrainingRoom: boolean; // lol wg typo
spriteFrame: number;
supremacyPointsThreshold?: number;
availableModes: number[];
shadowMapsAvailable?: boolean;
assaultRespawnPoints: {
allies: MapAlly[];
enemies: MapAlly[];
};
levels?: number[];
};
};
}
export interface MapAlly {
respawnNumber: number;
points: Array<number[]>;
}
const blitzShellKindToBlitzkit: Record<ShellKind, ShellType> = {
ARMOR_PIERCING: 'ap', ARMOR_PIERCING: 'ap',
ARMOR_PIERCING_CR: 'ap_cr', ARMOR_PIERCING_CR: 'ap_cr',
HIGH_EXPLOSIVE: 'he', HIGH_EXPLOSIVE: 'he',
@@ -334,7 +360,7 @@ const blitzShellKindToBLitzkrieg: Record<ShellKind, ShellType> = {
const missingStrings: Record<string, string> = { const missingStrings: Record<string, string> = {
'#artefacts:tungstentip/name': 'Tungsten Shells', '#artefacts:tungstentip/name': 'Tungsten Shells',
}; };
const botPattern = /^.+((tutorial_bot(\d+)?)|(TU(R?)))$/; export const botPattern = /^.+((tutorial_bot(\d+)?)|(TU(R?)))$/;
export async function definitions(production: boolean) { export async function definitions(production: boolean) {
console.log('Building definitions...'); console.log('Building definitions...');
@@ -348,6 +374,7 @@ export async function definitions(production: boolean) {
}; };
const tankDefinitions: TankDefinitions = {}; const tankDefinitions: TankDefinitions = {};
const modelDefinitions: ModelDefinitions = {}; const modelDefinitions: ModelDefinitions = {};
const mapDefinitions: MapDefinitions = {};
const equipmentDefinitions: EquipmentDefinitions = { const equipmentDefinitions: EquipmentDefinitions = {
presets: {}, presets: {},
equipments: {}, equipments: {},
@@ -361,12 +388,12 @@ export async function definitions(production: boolean) {
(nations) => nations.filter((nation) => nation !== 'common'), (nations) => nations.filter((nation) => nation !== 'common'),
); );
const tankStringIdMap: Record<string, number> = {}; const tankStringIdMap: Record<string, number> = {};
const stringsPreInstalled = await readYAMLDVPL<Strings>( const stringsPreInstalled = await readYAMLDVPL<BlitzStrings>(
`${DATA}/${POI.strings}/en.yaml.dvpl`, `${DATA}/${POI.strings}/en.yaml.dvpl`,
); );
const stringsCache = await fetch(POI.cachedStrings) const stringsCache = await fetch(POI.cachedStrings)
.then((response) => response.text()) .then((response) => response.text())
.then((string) => parseYaml(string) as Strings); .then((string) => parseYaml(string) as BlitzStrings);
const optionalDevices = await readXMLDVPL<{ root: OptionalDevices }>( const optionalDevices = await readXMLDVPL<{ root: OptionalDevices }>(
`${DATA}/${POI.optionalDevices}.dvpl`, `${DATA}/${POI.optionalDevices}.dvpl`,
); );
@@ -386,7 +413,7 @@ export async function definitions(production: boolean) {
const avatar = await readXMLDVPL<{ root: Avatar }>( const avatar = await readXMLDVPL<{ root: Avatar }>(
`${DATA}/XML/item_defs/tankmen/avatar.xml.dvpl`, `${DATA}/XML/item_defs/tankmen/avatar.xml.dvpl`,
); );
const maps = await readYAMLDVPL<Maps>(`${DATA}/maps.yaml.dvpl`);
const tankXps = new Map<number, number>(); const tankXps = new Map<number, number>();
await Promise.all( await Promise.all(
@@ -739,6 +766,17 @@ export async function definitions(production: boolean) {
const gunArmor: ModelArmor = { thickness: {} }; const gunArmor: ModelArmor = { thickness: {} };
const shotDispersionFactors = const shotDispersionFactors =
gun.shotDispersionFactors ?? gunListEntry.shotDispersionFactors; gun.shotDispersionFactors ?? gunListEntry.shotDispersionFactors;
const maskName = `gun_${gunModel.toString().padStart(2, '0')}`;
const maskEnabled =
tankParameters.maskSlice?.[maskName]?.enabled ?? false;
let mask: number | undefined;
if (maskEnabled) {
const maskRaw = tankParameters.maskSlice![maskName]!;
mask = maskRaw.planePosition[1];
} else {
mask = undefined;
}
totalUnlocks.push(gun.unlocks); totalUnlocks.push(gun.unlocks);
Object.keys(gun.armor) Object.keys(gun.armor)
@@ -788,6 +826,7 @@ export async function definitions(production: boolean) {
modelDefinitions[tankId].turrets[turretId].guns[gunId] = { modelDefinitions[tankId].turrets[turretId].guns[gunId] = {
armor: gunArmor, armor: gunArmor,
model: gunModel, model: gunModel,
mask,
thickness: thickness:
gun.armor.gun === undefined gun.armor.gun === undefined
? 0 ? 0
@@ -840,7 +879,7 @@ export async function definitions(production: boolean) {
caliber: shell.caliber, caliber: shell.caliber,
normalization: shell.normalizationAngle, normalization: shell.normalizationAngle,
ricochet: shell.ricochetAngle, ricochet: shell.ricochetAngle,
type: blitzShellKindToBLitzkrieg[shell.kind], type: blitzShellKindToBlitzkit[shell.kind],
explosionRadius: explosionRadius:
shell.kind === 'HIGH_EXPLOSIVE' shell.kind === 'HIGH_EXPLOSIVE'
? shell.explosionRadius ?? 0 ? shell.explosionRadius ?? 0
@@ -1110,6 +1149,13 @@ export async function definitions(production: boolean) {
skillDefinitions.classes[tankClass as TankClass] = skills.split(' '); skillDefinitions.classes[tankClass as TankClass] = skills.split(' ');
}); });
Object.entries(maps.maps).forEach(([key, map]) => {
mapDefinitions[map.id] = {
id: map.id,
name: strings[`#maps:${key}:${map.localName}`],
};
});
await commitAssets( await commitAssets(
'definitions', 'definitions',
[ [
@@ -1148,6 +1194,11 @@ export async function definitions(production: boolean) {
encoding: 'base64', encoding: 'base64',
path: 'definitions/skills.cdon.lz4', path: 'definitions/skills.cdon.lz4',
}, },
{
content: superCompress(mapDefinitions),
encoding: 'base64',
path: 'definitions/maps.cdon.lz4',
},
], ],
production, production,
); );
+2 -2
View File
@@ -3,8 +3,8 @@ import { readDVPLFile } from '../../src/core/blitz/readDVPLFile';
import { readStringDVPL } from '../../src/core/blitz/readStringDVPL'; import { readStringDVPL } from '../../src/core/blitz/readStringDVPL';
import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL'; import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL'; import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkit/commitMultipleFiles';
import { DATA, POI } from './constants'; import { DATA, POI } from './constants';
import { OptionalDevices } from './definitions'; import { OptionalDevices } from './definitions';
+2
View File
@@ -24,11 +24,13 @@ import { skillIcons } from './skillIcons';
import { tankArmors } from './tankArmors'; import { tankArmors } from './tankArmors';
import { tankIcons } from './tankIcons'; import { tankIcons } from './tankIcons';
import { tankModels } from './tankModels'; import { tankModels } from './tankModels';
import { videos } from './videos';
if (!targets && !allTargets) throw new Error('No target(s) specified'); if (!targets && !allTargets) throw new Error('No target(s) specified');
const methods = [ const methods = [
definitions, definitions,
videos,
scratchedFlags, scratchedFlags,
currencies, currencies,
skillIcons, skillIcons,
+2 -2
View File
@@ -1,8 +1,8 @@
import { readdir } from 'fs/promises'; import { readdir } from 'fs/promises';
import sharp from 'sharp'; import sharp from 'sharp';
import { readDVPLFile } from '../../src/core/blitz/readDVPLFile'; import { readDVPLFile } from '../../src/core/blitz/readDVPLFile';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkit/commitMultipleFiles';
import { DATA, POI } from './constants'; import { DATA, POI } from './constants';
export async function moduleIcons(production: boolean) { export async function moduleIcons(production: boolean) {
+2 -2
View File
@@ -1,7 +1,7 @@
import { readdir } from 'fs/promises'; import { readdir } from 'fs/promises';
import { readBase64DVPL } from '../../src/core/blitz/readBase64DVPL'; import { readBase64DVPL } from '../../src/core/blitz/readBase64DVPL';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkit/commitMultipleFiles';
import { DATA, POI } from './constants'; import { DATA, POI } from './constants';
export async function scratchedFlags(production: boolean) { export async function scratchedFlags(production: boolean) {
+2 -2
View File
@@ -2,8 +2,8 @@ import { readdir } from 'fs/promises';
import sharp from 'sharp'; import sharp from 'sharp';
import { readDVPLFile } from '../../src/core/blitz/readDVPLFile'; import { readDVPLFile } from '../../src/core/blitz/readDVPLFile';
import { readStringDVPL } from '../../src/core/blitz/readStringDVPL'; import { readStringDVPL } from '../../src/core/blitz/readStringDVPL';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkit/commitMultipleFiles';
import { DATA, POI } from './constants'; import { DATA, POI } from './constants';
export async function shellIcons(production: boolean) { export async function shellIcons(production: boolean) {
+2 -2
View File
@@ -2,8 +2,8 @@ import sharp from 'sharp';
import { TankClass } from '../../src/components/Tanks'; import { TankClass } from '../../src/components/Tanks';
import { readDVPLFile } from '../../src/core/blitz/readDVPLFile'; import { readDVPLFile } from '../../src/core/blitz/readDVPLFile';
import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL'; import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkit/commitMultipleFiles';
import { DATA } from './constants'; import { DATA } from './constants';
interface SkillIcon { interface SkillIcon {
+2 -5
View File
@@ -3,8 +3,8 @@ import { readdir } from 'fs/promises';
import { extractArmor } from '../../src/core/blitz/extractArmor'; import { extractArmor } from '../../src/core/blitz/extractArmor';
import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL'; import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
import { toUniqueId } from '../../src/core/blitz/toUniqueId'; import { toUniqueId } from '../../src/core/blitz/toUniqueId';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkit/commitMultipleFiles';
import { DATA, POI } from './constants'; import { DATA, POI } from './constants';
import { VehicleDefinitionList } from './definitions'; import { VehicleDefinitionList } from './definitions';
@@ -28,9 +28,6 @@ export async function tankArmors(production: boolean) {
if (tankKey.includes('tutorial_bot')) return; if (tankKey.includes('tutorial_bot')) return;
const id = toUniqueId(nation, tank.id); const id = toUniqueId(nation, tank.id);
console.log(`Building armor ${id} @ ${nation}/${tankKey}`);
const model = await extractArmor(DATA, `${nation}-${tankKey}`); const model = await extractArmor(DATA, `${nation}-${tankKey}`);
changes.push({ changes.push({
+16 -2
View File
@@ -5,8 +5,8 @@ import { NATION_IDS } from '../../src/constants/nations';
import { readDVPLFile } from '../../src/core/blitz/readDVPLFile'; import { readDVPLFile } from '../../src/core/blitz/readDVPLFile';
import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL'; import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL'; import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkit/commitMultipleFiles';
import { DATA, POI } from './constants'; import { DATA, POI } from './constants';
import { VehicleDefinitionList } from './definitions'; import { VehicleDefinitionList } from './definitions';
@@ -26,6 +26,20 @@ export interface TankParameters {
averageThickness?: Record<string, number>; averageThickness?: Record<string, number>;
}; };
}; };
maskSlice?: {
[key: string]:
| {
enabled: boolean;
planePosition: Vector3Tuple;
planeNormal: Vector3Tuple;
planeAxis: Vector3Tuple;
frustumConeBaseRadius: number;
frustumConeAngleDegrees: number;
planeSliceMode: number;
frustumConeSliceMode: number;
}
| undefined;
};
} }
export async function tankIcons(production: boolean) { export async function tankIcons(production: boolean) {
+2 -5
View File
@@ -4,8 +4,8 @@ import { extractModel } from '../../src/core/blitz/extractModel';
import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL'; import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL'; import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
import { toUniqueId } from '../../src/core/blitz/toUniqueId'; import { toUniqueId } from '../../src/core/blitz/toUniqueId';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { FileChange } from '../../src/core/blitzkrieg/commitMultipleFiles'; import { FileChange } from '../../src/core/blitzkit/commitMultipleFiles';
import { DATA, POI } from './constants'; import { DATA, POI } from './constants';
import { VehicleDefinitionList } from './definitions'; import { VehicleDefinitionList } from './definitions';
import { TankParameters } from './tankIcons'; import { TankParameters } from './tankIcons';
@@ -32,9 +32,6 @@ export async function tankModels(production: boolean) {
if (tankKey.includes('tutorial_bot')) return; if (tankKey.includes('tutorial_bot')) return;
const id = toUniqueId(nation, tank.id); const id = toUniqueId(nation, tank.id);
console.log(`Building model ${id} @ ${nation}/${tankKey}`);
const parameters = await readYAMLDVPL<TankParameters>( const parameters = await readYAMLDVPL<TankParameters>(
`${DATA}/${POI.tankParameters}/${nation}/${tankKey}.yaml.dvpl`, `${DATA}/${POI.tankParameters}/${nation}/${tankKey}.yaml.dvpl`,
); );
+142
View File
@@ -0,0 +1,142 @@
import { readdir } from 'fs/promises';
import { google } from 'googleapis';
import { cloneDeep, uniqBy } from 'lodash';
import { parse as parseYaml } from 'yaml';
import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
import { toUniqueId } from '../../src/core/blitz/toUniqueId';
import { commitAssets } from '../../src/core/blitzkit/commitAssets';
import { VideoDefinitions } from '../../src/core/blitzkit/videos';
import { DATA, POI } from './constants';
import { BlitzStrings, botPattern, VehicleDefinitionList } from './definitions';
const youtubers = [
'UCKBYXp4Xn2I2tL1UL4fpbhw', // droodles
'UCrQ-Dy8lVsm11u8pCJ8W7Tw', // fatness
'UCxRpZqMElssTdPYDK3x7wyg', // n90
];
export async function videos(production: boolean) {
console.log('Building videos...');
const currentVideos = await fetch(
'https://raw.githubusercontent.com/tresabhi/blitzkit-assets/main/definitions/videos.csv',
)
.then((response) => response.text())
.then((lines) => {
return lines
.split('\n')
.map((line) => {
const [idString, lastUpdatedString, ...videos] = line.split(',');
const id = Number(idString);
const lastUpdated = Number(lastUpdatedString);
return { id, lastUpdated, videos };
})
.reduce<VideoDefinitions>(
(accumulator, { id, lastUpdated, videos }) => {
accumulator[id] = { lastUpdated, videos };
return accumulator;
},
{},
);
});
const auth = await google.auth.getClient({
scopes: ['https://www.googleapis.com/auth/youtube.readonly'],
});
const youtube = google.youtube({ version: 'v3', auth });
const nations = await readdir(`${DATA}/${POI.vehicleDefinitions}`).then(
(nations) => nations.filter((nation) => nation !== 'common'),
);
const stringsPreInstalled = await readYAMLDVPL<BlitzStrings>(
`${DATA}/${POI.strings}/en.yaml.dvpl`,
);
const stringsCache = await fetch(POI.cachedStrings)
.then((response) => response.text())
.then((string) => parseYaml(string) as BlitzStrings);
const strings = {
...stringsPreInstalled,
...stringsCache,
};
const tanks: { id: number; name: string; tier: number }[] = [];
await Promise.all(
nations.map(async (nation) => {
const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>(
`${DATA}/${POI.vehicleDefinitions}/${nation}/list.xml.dvpl`,
);
await Promise.all(
Object.entries(tankList.root).map(async ([tankKey, tank]) => {
if (botPattern.test(tankKey)) return null;
const id = toUniqueId(nation, tank.id);
const name =
(tank.shortUserString
? strings[tank.shortUserString]
: undefined) ?? strings[tank.userString];
const tier = tank.level;
tanks.push({ id, name, tier });
}),
);
}),
);
const tanksSanitized = tanks
.sort((a, b) => b.tier - a.tier)
.sort((a, b) => {
const lastUpdatedA = currentVideos[a.id]?.lastUpdated ?? 0;
const lastUpdatedB = currentVideos[b.id]?.lastUpdated ?? 0;
return lastUpdatedA - lastUpdatedB;
});
const videos: VideoDefinitions = cloneDeep(currentVideos);
let done = 0;
for (const tank of tanksSanitized) {
try {
const results = await youtube.search.list({
part: ['snippet'],
q: `World of Tanks Blitz ${tank.name}`,
});
if (!results.data.items || results.data.items.length === 0) continue;
const items = uniqBy(
results.data.items.filter(
(item) =>
item.snippet?.channelId &&
item.id?.videoId &&
youtubers.includes(item.snippet.channelId),
),
(item) => item.snippet!.channelId,
);
videos[tank.id] = {
lastUpdated: Math.round(Date.now() / 1000),
videos: items.map((item) => item.id!.videoId!),
};
console.log(
`${++done} / ${tanks.length} (${Math.round(100 * (done / tanks.length))}%) done; found ${items.length} for ${tank.name}`,
);
await new Promise((resolve) => setTimeout(resolve, 1000 / 5));
} catch (error) {
console.warn('An error occurred, safely exiting prematurely...', error);
break;
}
}
const content = Object.entries(videos)
.map(
([id, { videos, lastUpdated }]) =>
`${id},${lastUpdated},${videos.join(',')}`,
)
.join('\n');
await commitAssets(
'videos',
[{ content, encoding: 'utf-8', path: 'definitions/videos.csv' }],
production,
);
}
+4 -4
View File
@@ -8,10 +8,10 @@ import {
import { RegionSubdomain } from '../src/constants/regions'; import { RegionSubdomain } from '../src/constants/regions';
import { getAccountInfo } from '../src/core/blitz/getAccountInfo'; import { getAccountInfo } from '../src/core/blitz/getAccountInfo';
import regionSubdomainToRegion from '../src/core/blitz/regionSubdomainToRegion'; import regionSubdomainToRegion from '../src/core/blitz/regionSubdomainToRegion';
import { commitAssets } from '../src/core/blitzkrieg/commitAssets'; import { commitAssets } from '../src/core/blitzkit/commitAssets';
import { FileChange } from '../src/core/blitzkrieg/commitMultipleFiles.js'; import { FileChange } from '../src/core/blitzkit/commitMultipleFiles.js';
import { patientFetchJSON } from '../src/core/blitzkrieg/patientFetchJSON'; import { patientFetchJSON } from '../src/core/blitzkit/patientFetchJSON';
import { superCompress } from '../src/core/blitzkrieg/superCompress'; import { superCompress } from '../src/core/blitzkit/superCompress';
import { import {
BkrlBaseEntry, BkrlBaseEntry,
BkrlFormat, BkrlFormat,
+3 -3
View File
@@ -1,4 +1,4 @@
import { tankDefinitions } from '../src/core/blitzkrieg/tankDefinitions'; import { tankDefinitions } from '../src/core/blitzkit/tankDefinitions';
// random vercel bug forces require import // random vercel bug forces require import
const { writeFileSync } = require('fs'); const { writeFileSync } = require('fs');
@@ -8,14 +8,14 @@ console.log('Building sitemap...');
const awaitedTankDefinitions = await tankDefinitions; const awaitedTankDefinitions = await tankDefinitions;
const values = Object.values(awaitedTankDefinitions); const values = Object.values(awaitedTankDefinitions);
const txt = values const txt = values
.map((tank) => `https://blitz-krieg.vercel.app/tools/tankopedia/${tank.id}/`) .map((tank) => `https://blitzkit.vercel.app/tools/tankopedia/${tank.id}/`)
.join('\n'); .join('\n');
const xml = `<?xml version="1.0" encoding="UTF-8"?> const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
${values ${values
.map( .map(
(tank) => (tank) =>
`<url><loc>https://blitz-krieg.vercel.app/tools/tankopedia/${tank.id}/</loc></url>`, `<url><loc>https://blitzkit.vercel.app/tools/tankopedia/${tank.id}/</loc></url>`,
) )
.join('\n')} .join('\n')}
</urlset>`; </urlset>`;
+96
View File
@@ -0,0 +1,96 @@
import { chunk, times } from 'lodash';
import { compress } from 'lz4js';
import { argv } from 'process';
import { Region, REGIONS } from '../src/constants/regions';
import getTankStats from '../src/core/blitz/getTankStats';
import { idToRegion } from '../src/core/blitz/idToRegion';
import { commitAssets } from '../src/core/blitzkit/commitAssets';
import { FileChange } from '../src/core/blitzkit/commitMultipleFiles';
import {
RtscWritePlayer,
RtscWriteStream,
RtsmWriteStream,
} from '../src/core/streams/rts';
import usersRaw from '../test.users.json';
const PLAYERS_PER_CHUNK = 256;
const CHUNKS_PER_BATCH = 128;
const production = argv.includes('--production');
const region = argv
.find((argument) => argument.startsWith('--region'))
?.split('=')[1] as Region | undefined;
if (!region || !REGIONS.includes(region)) {
throw new Error('No valid region specified');
}
const today = new Date();
const todayPath = `${today.getUTCFullYear()}/${today.getUTCMonth() + 1}/${today.getUTCDate()}`;
const users = usersRaw.filter(({ blitz }) => idToRegion(blitz) === region);
const base = Math.ceil(users.length / PLAYERS_PER_CHUNK);
const chunks = times(base, (offset) => ({
offset,
users: users.filter((user) => user.blitz % base === offset),
}));
const batches = chunk(chunks, CHUNKS_PER_BATCH);
const manifestWriteStream = new RtsmWriteStream();
console.log(`Found ${users.length} players in ${region}`);
await commitAssets(
`regular tank stats manifests ${todayPath}`,
[
{
encoding: 'base64',
path: `regions/${region}/regular/tanks/${todayPath}/manifest.rtsm`,
content: Buffer.from(
compress(manifestWriteStream.rtsm(base).uint8Array),
).toString('base64'),
},
],
production,
);
let batchesDone = 0;
for (const batch of batches) {
console.log(`Batch ${++batchesDone} of ${batches.length}`);
const changes: FileChange[] = [];
let chunksDone = 0;
for (const chunk of batch) {
console.log(`Chunk ${++chunksDone} of ${chunks.length}`);
let playersDone = 0;
const players = await Promise.all(
chunk.users.map(async (user) => {
const tanks = await getTankStats(region, user.blitz);
console.log(`Player ${++playersDone} of ${chunk.users.length}`);
if (tanks === null) return null;
return {
id: user.blitz,
tanks,
} satisfies RtscWritePlayer;
}),
).then((players) => players.filter((player) => player !== null));
const writeStream = new RtscWriteStream();
writeStream.rtsc(players);
changes.push({
encoding: 'base64',
content: Buffer.from(compress(writeStream.uint8Array)).toString('base64'),
path: `regions/${region}/regular/tanks/${todayPath}/${chunk.offset}.rtsc.lz4`,
});
}
// do not await; commit in background
commitAssets(
`regular tank stats ${todayPath} batch ${batches.indexOf(batch) + 1}`,
changes,
production,
);
}
+36
View File
@@ -0,0 +1,36 @@
import AdmZip from 'adm-zip';
import { readFile } from 'fs/promises';
import { BattleResultsReadStream } from '../src/core/streams/battleResults';
interface WoTBReplayMeta {
version: string;
title: string;
dbid: string;
playerName: string;
battleStartTime: string;
playerVehicleName: string;
mapName: string;
arenaUniqueId: string;
battleDuration: number;
vehicleCompDescriptor: number;
camouflageId: number;
mapId: number;
arenaBonusType: number;
}
const file = await readFile('temp/test.wotbreplay');
const zip = new AdmZip(file);
const entries = zip.getEntries();
const metaRaw = entries.find((entry) => entry.name === 'meta.json');
const battleResultsRaw = entries.find(
(entry) => entry.name === 'battle_results.dat',
);
if (!metaRaw) throw new Error('No meta.json found');
if (!battleResultsRaw) throw new Error('No battle_results.dat found');
const meta = JSON.parse(metaRaw.getData().toString()) as WoTBReplayMeta;
const battleResults = new BattleResultsReadStream(
battleResultsRaw.getData().buffer,
).battleResults();
+62
View File
@@ -0,0 +1,62 @@
'use client';
import { Flex, Heading, Text } from '@radix-ui/themes';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
import PageWrapper from '../../../components/PageWrapper';
import { mutateApp } from '../../../stores/app';
export default function Page({
params,
searchParams,
}: {
params: { provider: string };
searchParams: { [key: string]: string };
}) {
const router = useRouter();
useEffect(() => {
switch (params.provider) {
case 'wargaming': {
if (
!searchParams.expires_at ||
!searchParams.access_token ||
!searchParams.account_id
) {
break;
}
mutateApp((draft) => {
draft.login = {
expiresAt: Number(searchParams.expires_at),
id: Number(searchParams.account_id),
token: searchParams.access_token,
};
});
break;
}
default: {
console.error(`Unknown provider: ${params.provider}`);
}
}
router.push(searchParams.return ? searchParams.return : '/');
});
return (
<PageWrapper>
<Flex
style={{ flex: 1 }}
align="center"
justify="center"
direction="column"
gap="2"
>
<Heading>Verifying you...</Heading>
<Text color="gray">You'll be on your way shortly.</Text>
</Flex>
</PageWrapper>
);
}
-339
View File
@@ -1,339 +0,0 @@
'use client';
import { ContextMenu } from '@radix-ui/themes';
import { useEffect, useState } from 'react';
import * as Breakdown from '../../../components/Breakdown';
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
import fetchBlitz from '../../../core/blitz/fetchBlitz';
import { resetSession } from '../../../core/blitzkrieg/resetSession';
import {
TankDefinition,
tankDefinitions,
} from '../../../core/blitzkrieg/tankDefinitions';
import { tankAverages } from '../../../core/blitzstars/tankAverages';
import calculateWN8 from '../../../core/statistics/calculateWN8';
import { deltaTankStats } from '../../../core/statistics/deltaTankStats';
import getWN8Percentile from '../../../core/statistics/getWN8Percentile';
import mutateSession, { useSession } from '../../../stores/session';
import { IndividualTankStats, TanksStats } from '../../../types/tanksStats';
import { CustomColumnDisplay } from '../../tools/session/components/CustomColumn';
import { Menu } from '../../tools/session/components/Menu';
const REFRESH_RATE = 1 / 5;
interface SessionPageProps {
naked?: boolean;
}
export default function SessionPage({ naked = true }: SessionPageProps) {
const session = useSession();
const since = session.isTracking ? new Date(session.time) : undefined;
const [diff, setDiff] = useState<
| {
careerWN8: number;
list: {
stats: IndividualTankStats;
tankDefinitions?: TankDefinition;
career: IndividualTankStats;
currentWN8?: number;
careerWN8?: number;
}[];
}
| undefined
>(undefined);
const sessionBattles = diff?.list.reduce(
(accumulator, { stats }) => accumulator + stats.all.battles,
0,
);
const tankStatsArray = session.isTracking
? (Object.values(session.tankStats) as IndividualTankStats[])
: [];
const sessionWinrate = diff
? diff.list.reduce(
(accumulator, { stats }) => accumulator + stats.all.wins,
0,
) / sessionBattles!
: 0;
const careerBattles = tankStatsArray.reduce(
(accumulator, stats) => accumulator + stats.all.battles,
0,
);
const careerWinrate =
tankStatsArray.reduce(
(accumulator, stats) => accumulator + stats.all.wins,
0,
) / careerBattles;
const currentDamage = diff
? diff.list.reduce(
(accumulator, { stats }) => accumulator + stats.all.damage_dealt,
0,
) / sessionBattles!
: 0;
const careerDamage =
tankStatsArray.reduce(
(accumulator, stats) => accumulator + stats.all.damage_dealt,
0,
) / careerBattles;
useEffect(() => {
if (session.title === 'Untitled session') {
mutateSession((draft) => {
if (draft.isTracking) draft.title = draft.nickname;
});
}
}, [session.title]);
useEffect(() => {
async function recalculateDiff() {
if (session.isTracking) {
const { id, region } = session;
const careerRaw = await fetchBlitz<TanksStats>(
`https://api.wotblitz.${region}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
const career = careerRaw[id].reduce<
Record<number, IndividualTankStats>
>(
(accumulator, curr) => ({
...accumulator,
[curr.tank_id]: curr,
}),
{},
);
const awaitedTankDefinitions = await tankDefinitions;
const awaitedTankAverages = await tankAverages;
const careerWN8 =
careerRaw[id].reduce(
(accumulator, { tank_id, all }) =>
accumulator +
(awaitedTankAverages[tank_id] && all.battles > 0
? all.battles *
calculateWN8(awaitedTankAverages[tank_id].all, all)
: 0),
0,
) /
careerRaw[id].reduce(
(accumulator, { tank_id, all }) =>
accumulator +
(awaitedTankAverages[tank_id] && all.battles > 0
? all.battles
: 0),
0,
);
setDiff({
careerWN8,
list: (
Object.values(
deltaTankStats(session.tankStats, career, session.time / 1000),
) as IndividualTankStats[]
).map((stats) => {
return {
stats,
tankDefinitions: awaitedTankDefinitions[stats.tank_id],
career: career[stats.tank_id],
currentWN8: awaitedTankAverages[stats.tank_id]
? calculateWN8(
awaitedTankAverages[stats.tank_id].all,
stats.all,
)
: undefined,
careerWN8: awaitedTankAverages[stats.tank_id]
? calculateWN8(
awaitedTankAverages[stats.tank_id].all,
career[stats.tank_id].all,
)
: undefined,
};
}),
});
}
}
recalculateDiff();
const interval = setInterval(recalculateDiff, 1000 / REFRESH_RATE);
return () => clearInterval(interval);
}, [session]);
const totalRowStats: Record<
CustomColumnDisplay,
Breakdown.RowStatItem | undefined
> = {
battles: {
title: 'Battles',
current: sessionBattles?.toLocaleString(),
career: tankStatsArray
.reduce((accumulator, stats) => accumulator + stats.all.battles, 0)
.toLocaleString(),
},
winrate: {
title: 'Winrate',
current: sessionBattles ? `${(100 * sessionWinrate).toFixed(2)}%` : '--',
career: `${(100 * careerWinrate).toFixed(2)}%`,
delta: sessionBattles === 0 ? 0 : sessionWinrate - careerWinrate,
},
wn8: (() => {
if (!diff) return undefined;
const battlesWithWN8 = diff.list.reduce(
(accumulator, { currentWN8, stats }) =>
(currentWN8 === undefined ? 0 : stats.all.battles) + accumulator,
0,
);
const currentWN8 =
diff.list.reduce(
(accumulator, { currentWN8, stats }) =>
stats.all.battles * (currentWN8 ?? 0) + accumulator,
0,
) / battlesWithWN8!;
return {
title: 'WN8',
current: battlesWithWN8
? Math.round(currentWN8!).toLocaleString()
: '--',
percentile: battlesWithWN8 ? getWN8Percentile(currentWN8!) : undefined,
career: Math.round(diff.careerWN8).toLocaleString(),
};
})(),
damage: {
title: 'Damage',
delta: sessionBattles === 0 ? 0 : currentDamage - careerDamage,
current: sessionBattles
? Math.round(currentDamage).toLocaleString()
: '--',
career: Math.round(careerDamage).toLocaleString(),
},
none: undefined,
};
return (
<ContextMenu.Root>
<ContextMenu.Trigger>
<div>
<Breakdown.Root>
{session.showTotal &&
session.isTracking &&
since !== undefined &&
diff !== undefined && (
<Breakdown.Row
naked={naked}
color={session.color}
minimized={!session.showCareer}
title={session.title}
stats={session.customColumns.map((customColumn) => {
const rowStat = totalRowStats[customColumn.display];
if (!rowStat) return undefined;
return {
...rowStat,
delta:
customColumn.showDelta &&
rowStat.percentile === undefined
? rowStat.delta
: undefined,
};
})}
/>
)}
{diff?.list
.sort(
(a, b) => b.stats.last_battle_time - a.stats.last_battle_time,
)
.map(
({ stats, tankDefinitions, career, careerWN8, currentWN8 }) => {
const rowStats: Record<
CustomColumnDisplay,
Breakdown.RowStatItem | undefined
> = {
battles: {
title: 'Battles',
current: stats.all.battles.toLocaleString(),
career: career.all.battles.toLocaleString(),
},
winrate: {
title: 'Winrate',
current: `${(
100 *
(stats.all.wins / stats.all.battles)
).toFixed(2)}%`,
career: `${(
100 *
(career.all.wins / career.all.battles)
).toFixed(2)}%`,
delta:
stats.all.wins / stats.all.battles -
career.all.wins / career.all.battles,
},
wn8: {
title: 'WN8',
current:
currentWN8 === undefined
? undefined
: Math.round(currentWN8).toLocaleString(),
career:
careerWN8 === undefined
? undefined
: Math.round(careerWN8).toLocaleString(),
percentile:
currentWN8 === undefined
? undefined
: getWN8Percentile(currentWN8),
},
damage: {
title: 'Damage',
current: Math.round(
stats.all.damage_dealt / stats.all.battles,
).toLocaleString(),
career: Math.round(
career.all.damage_dealt / career.all.battles,
).toLocaleString(),
delta:
stats.all.damage_dealt / stats.all.battles -
career.all.damage_dealt / career.all.battles,
},
none: undefined,
};
return (
<Breakdown.Row
naked={naked}
color={session.color}
key={stats.tank_id}
minimized={!session.showCareer}
title={
tankDefinitions?.name ?? `Unknown tank ${stats.tank_id}`
}
type="tank"
tankType={tankDefinitions?.class}
treeType={tankDefinitions?.treeType}
stats={session.customColumns.map((customColumn) => {
const rowStat = rowStats[customColumn.display];
if (!rowStat) return undefined;
return {
...rowStat,
delta:
customColumn.showDelta &&
rowStat.percentile === undefined
? rowStat.delta
: undefined,
};
})}
/>
);
},
)}
</Breakdown.Root>
</div>
</ContextMenu.Trigger>
<Menu reset={resetSession} Builder={ContextMenu} />
</ContextMenu.Root>
);
}
+31 -14
View File
@@ -5,12 +5,13 @@ import '@radix-ui/themes/styles.css';
import { config } from 'dotenv'; import { config } from 'dotenv';
import { Roboto_Flex } from 'next/font/google'; import { Roboto_Flex } from 'next/font/google';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { ReactNode, useEffect, useState } from 'react'; import { ReactNode, Suspense, useEffect, useState } from 'react';
import { Google } from '../components/Google';
import { Footer } from '../components/Footer'; import { Footer } from '../components/Footer';
import { Loader } from '../components/Loader';
import Navbar from '../components/Navbar'; import Navbar from '../components/Navbar';
import isDev from '../core/blitzkrieg/isDev'; import { Party3 } from '../components/Party3';
import { isLocalhost } from '../core/blitzkrieg/isLocalhost'; import isDev from '../core/blitzkit/isDev';
import { isLocalhost } from '../core/blitzkit/isLocalhost';
import { useFullScreen } from '../hooks/useFullScreen'; import { useFullScreen } from '../hooks/useFullScreen';
import { useApp } from '../stores/app'; import { useApp } from '../stores/app';
@@ -30,6 +31,8 @@ const robotoFlex = Roboto_Flex({
export default function RootLayout({ children }: RootLayoutProps) { export default function RootLayout({ children }: RootLayoutProps) {
const pathname = usePathname(); const pathname = usePathname();
const isEmbed = pathname.split('/')[1] === 'embeds'; const isEmbed = pathname.split('/')[1] === 'embeds';
const isRoot = pathname === '/';
const darkMode = useApp((state) => state.darkMode);
const [showDevBuildAlert, setShowDevBuildAlert] = useState(false); const [showDevBuildAlert, setShowDevBuildAlert] = useState(false);
const isFullScreen = useFullScreen(); const isFullScreen = useFullScreen();
@@ -45,13 +48,19 @@ export default function RootLayout({ children }: RootLayoutProps) {
return ( return (
<html lang="en" className={robotoFlex.className}> <html lang="en" className={robotoFlex.className}>
<head> <head>
<title>Blitzkrieg</title> {isRoot && (
<meta <>
name="description" <title>BlitzKit</title>
content="🎉 Tools for everything World of Tanks Blitz" <meta
/> name="description"
content="🎉 Tools for everything World of Tanks Blitz"
/>
</>
)}
<meta property="og:site_name" content="BlitzKit" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<Google /> <Party3 />
</head> </head>
<body <body
@@ -61,7 +70,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
}} }}
> >
<Theme <Theme
appearance="dark" appearance={darkMode ? 'dark' : 'light'}
panelBackground="solid" panelBackground="solid"
radius="full" radius="full"
suppressHydrationWarning suppressHydrationWarning
@@ -69,7 +78,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
> >
<Flex <Flex
direction="column" direction="column"
style={{ minHeight: '100vh', paddingTop: isEmbed ? undefined : 52 }} style={{ minHeight: '100vh', paddingTop: isEmbed ? undefined : 64 }}
> >
{!isEmbed && <Navbar />} {!isEmbed && <Navbar />}
<AlertDialog.Root open={showDevBuildAlert}> <AlertDialog.Root open={showDevBuildAlert}>
@@ -81,7 +90,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
the official Discord server the official Discord server
</a> </a>
. Also consider using{' '} . Also consider using{' '}
<a href="https://blitz-krieg.vercel.app/"> <a href="https://blitzkit.vercel.app/">
the more stable release version the more stable release version
</a> </a>
. You will be asked again in 8 days. . You will be asked again in 8 days.
@@ -101,7 +110,15 @@ export default function RootLayout({ children }: RootLayoutProps) {
</AlertDialog.Content> </AlertDialog.Content>
</AlertDialog.Root> </AlertDialog.Root>
{children} <Suspense
fallback={
<div style={{ flex: 1 }}>
<Loader />
</div>
}
>
{children}
</Suspense>
{!isEmbed && !isFullScreen && <Footer />} {!isEmbed && !isFullScreen && <Footer />}
</Flex> </Flex>
+20 -8
View File
@@ -1,22 +1,35 @@
'use client'; 'use client';
import { CaretRightIcon } from '@radix-ui/react-icons'; import { CaretRightIcon } from '@radix-ui/react-icons';
import { Flex } from '@radix-ui/themes'; import { Button, Flex, Link as LinkRadix } from '@radix-ui/themes';
import Link from 'next/link'; import Link from 'next/link';
import PageWrapper from '../components/PageWrapper'; import PageWrapper from '../components/PageWrapper';
import { TOOLS } from '../constants/tools'; import { TOOLS } from '../constants/tools';
import { PatreonIcon } from '../icons/Patreon';
import { theme } from '../stitches.config'; import { theme } from '../stitches.config';
import * as styles from './page.css'; import * as styles from './page.css';
export default function Page() { export default function Page() {
return ( return (
<PageWrapper> <PageWrapper>
<Flex direction="column" gap="4" justify="center" style={{ flex: 1 }}> <Flex
{/* <TextField.Root placeholder="Search anything..."> direction="column"
<TextField.Slot> gap="5"
<MagnifyingGlassIcon height="16" width="16" /> justify="center"
</TextField.Slot> align="center"
</TextField.Root> */} style={{ flex: 1 }}
>
<LinkRadix href="https://www.patreon.com/tresabhi" target="_blank">
<Button
color="ruby"
style={{
boxShadow: `0 0 8px ${theme.colors.solidBackground_ruby}f0`,
}}
>
<PatreonIcon style={{ width: '1em', height: '1em' }} /> Consider
donating on Patreon
</Button>
</LinkRadix>
<Flex gap="3" wrap="wrap" align="center" justify="center"> <Flex gap="3" wrap="wrap" align="center" justify="center">
{TOOLS.map((tool) => ( {TOOLS.map((tool) => (
@@ -43,7 +56,6 @@ export default function Page() {
alignItems: 'flex-start', alignItems: 'flex-start',
justifyContent: 'flex-end', justifyContent: 'flex-end',
padding: 16, padding: 16,
gap: 4,
transition: `box-shadow ${theme.durations.regular}`, transition: `box-shadow ${theme.durations.regular}`,
}} }}
onClick={(event) => { onClick={(event) => {
+44
View File
@@ -0,0 +1,44 @@
'use client';
import { Flex, Heading, Switch, Text } from '@radix-ui/themes';
import PageWrapper from '../../components/PageWrapper';
import { useApp } from '../../stores/app';
export default function Page() {
const developerMode = useApp((state) => state.developerMode);
const darkMode = useApp((state) => state.darkMode);
return (
<PageWrapper justify="center" align="center">
<Flex direction="column" gap="4">
<Flex gap="3" direction="column">
<Heading size="5">Appearance</Heading>
<Flex align="center" gap="2">
<Text>Dark mode (experimental)</Text>
<Switch
checked={darkMode}
onCheckedChange={(checked) =>
useApp.setState({ darkMode: checked })
}
/>
</Flex>
</Flex>
<Flex gap="3" direction="column">
<Heading size="5">Advanced</Heading>
<Flex align="center" gap="2">
<Text>Developer mode</Text>
<Switch
checked={developerMode}
onCheckedChange={(checked) =>
useApp.setState({ developerMode: checked })
}
/>
</Flex>
</Flex>
</Flex>
</PageWrapper>
);
}
@@ -1,16 +1,18 @@
import { LoopIcon, TrashIcon } from '@radix-ui/react-icons'; import { ExternalLinkIcon, LoopIcon, TrashIcon } from '@radix-ui/react-icons';
import { Dialog, Flex, IconButton } from '@radix-ui/themes'; import { Dialog, Flex, IconButton } from '@radix-ui/themes';
import Link from 'next/link';
import { use, useState } from 'react'; import { use, useState } from 'react';
import { provisionDefinitions } from '../../../../core/blitzkrieg/provisionDefinitions'; import { provisionDefinitions } from '../../../../core/blitzkit/provisionDefinitions';
import { tankToCompareMember } from '../../../../core/blitzkrieg/tankToCompareMember'; import { tankToCompareMember } from '../../../../core/blitzkit/tankToCompareMember';
import { mutateCompareTemporary } from '../../../../stores/compare'; import { mutateCompareTemporary } from '../../../../stores/compare';
import { TankSearch } from '../../tankopedia/components/TankSearch'; import { TankSearch } from '../../tankopedia/components/TankSearch';
interface TankControlProps { interface TankControlProps {
index: number; index: number;
id: number;
} }
export function TankControl({ index }: TankControlProps) { export function TankControl({ index, id }: TankControlProps) {
const awaitedProvisionDefinitions = use(provisionDefinitions); const awaitedProvisionDefinitions = use(provisionDefinitions);
const [switchTankDialogOpen, setSwitchTankDialogOpen] = useState(false); const [switchTankDialogOpen, setSwitchTankDialogOpen] = useState(false);
@@ -27,6 +29,7 @@ export function TankControl({ index }: TankControlProps) {
> >
<TrashIcon /> <TrashIcon />
</IconButton> </IconButton>
<Dialog.Root <Dialog.Root
open={switchTankDialogOpen} open={switchTankDialogOpen}
onOpenChange={setSwitchTankDialogOpen} onOpenChange={setSwitchTankDialogOpen}
@@ -62,6 +65,12 @@ export function TankControl({ index }: TankControlProps) {
</Flex> </Flex>
</Dialog.Content> </Dialog.Content>
</Dialog.Root> </Dialog.Root>
<Link href={`/tools/tankopedia/${id}`}>
<IconButton variant="ghost">
<ExternalLinkIcon />
</IconButton>
</Link>
</Flex> </Flex>
); );
} }
+28 -17
View File
@@ -31,23 +31,23 @@ import { EquipmentManager } from '../../../components/EquipmentManager';
import { ModuleManager } from '../../../components/ModuleManager'; import { ModuleManager } from '../../../components/ModuleManager';
import PageWrapper from '../../../components/PageWrapper'; import PageWrapper from '../../../components/PageWrapper';
import { ProvisionsManager } from '../../../components/ProvisionsManager'; import { ProvisionsManager } from '../../../components/ProvisionsManager';
import { asset } from '../../../core/blitzkrieg/asset'; import { asset } from '../../../core/blitzkit/asset';
import { availableProvisions } from '../../../core/blitzkrieg/availableProvisions'; import { availableProvisions } from '../../../core/blitzkit/availableProvisions';
import { checkConsumableProvisionInclusivity } from '../../../core/blitzkrieg/checkConsumableProvisionInclusivity'; import { checkConsumableProvisionInclusivity } from '../../../core/blitzkit/checkConsumableProvisionInclusivity';
import { consumableDefinitions } from '../../../core/blitzkrieg/consumableDefinitions'; import { consumableDefinitions } from '../../../core/blitzkit/consumableDefinitions';
import { createDefaultSkills } from '../../../core/blitzkrieg/createDefaultSkills'; import { createDefaultSkills } from '../../../core/blitzkit/createDefaultSkills';
import { equipmentDefinitions } from '../../../core/blitzkrieg/equipmentDefinitions'; import { equipmentDefinitions } from '../../../core/blitzkit/equipmentDefinitions';
import { modelDefinitions } from '../../../core/blitzkrieg/modelDefinitions'; import { modelDefinitions } from '../../../core/blitzkit/modelDefinitions';
import { provisionDefinitions } from '../../../core/blitzkrieg/provisionDefinitions'; import { provisionDefinitions } from '../../../core/blitzkit/provisionDefinitions';
import { skillDefinitions } from '../../../core/blitzkrieg/skillDefinitions'; import { skillDefinitions } from '../../../core/blitzkit/skillDefinitions';
import { tankCharacteristics } from '../../../core/blitzkrieg/tankCharacteristics'; import { tankCharacteristics } from '../../../core/blitzkit/tankCharacteristics';
import { import {
TankDefinition, TankDefinition,
tankDefinitions, tankDefinitions,
} from '../../../core/blitzkrieg/tankDefinitions'; } from '../../../core/blitzkit/tankDefinitions';
import { tankIcon } from '../../../core/blitzkrieg/tankIcon'; import { tankIcon } from '../../../core/blitzkit/tankIcon';
import { tankToCompareMember } from '../../../core/blitzkrieg/tankToCompareMember'; import { tankToCompareMember } from '../../../core/blitzkit/tankToCompareMember';
import { BlitzkriegButtonWatermark } from '../../../icons/BlitzkriegButtonWatermark'; import { BlitzkitButtonWatermark } from '../../../icons/BlitzkitButtonWatermark';
import { theme } from '../../../stitches.config'; import { theme } from '../../../stitches.config';
import { import {
DeltaMode, DeltaMode,
@@ -447,7 +447,7 @@ export default function Page() {
gap="2" gap="2"
style={{ height: '100%' }} style={{ height: '100%' }}
> >
<TankControl index={index} key={tank.id} /> <TankControl index={index} key={tank.id} id={tank.id} />
<Flex <Flex
ref={draggable} ref={draggable}
@@ -648,7 +648,7 @@ export default function Page() {
align="center" align="center"
justify="center" justify="center"
> >
<BlitzkriegButtonWatermark width={64} height={64} /> <BlitzkitButtonWatermark width={64} height={64} />
<InsertionMarker index={0} /> <InsertionMarker index={0} />
</Flex> </Flex>
</Table.ColumnHeaderCell> </Table.ColumnHeaderCell>
@@ -1233,10 +1233,21 @@ export default function Page() {
<Row name="Shells" value="shells" decimals={0} /> <Row name="Shells" value="shells" decimals={0} />
</> </>
)} )}
<Row name="Caliber" value="caliber" decimals={0} />
<Row name="Penetration" value="penetration" decimals={0} /> <Row name="Penetration" value="penetration" decimals={0} />
<Row name="Damage" value="damage" decimals={0} /> <Row name="Damage" value="damage" decimals={0} />
<Row name="Module damage" value="moduleDamage" decimals={0} /> <Row name="Module damage" value="moduleDamage" decimals={0} />
<Row name="Caliber" value="caliber" decimals={0} />
<Row
name="Normalization"
value="shellNormalization"
decimals={0}
/>
<Row
name="Ricochet"
value="shellRicochet"
deltaType="lowerIsBetter"
decimals={0}
/>
<Row name="Shell velocity" value="shellVelocity" decimals={0} /> <Row name="Shell velocity" value="shellVelocity" decimals={0} />
<Row <Row
name="Aim time" name="Aim time"
+21 -19
View File
@@ -2,10 +2,9 @@
import Link from 'next/link'; import Link from 'next/link';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { ReactNode, Suspense } from 'react'; import { ReactNode } from 'react';
import { TOOLS } from '../../constants/tools'; import { TOOLS } from '../../constants/tools';
import { theme } from '../../stitches.config'; import { theme } from '../../stitches.config';
import { Loader } from './components/Loader';
interface ToolsLayoutProps { interface ToolsLayoutProps {
children: ReactNode; children: ReactNode;
@@ -15,21 +14,32 @@ export default function ToolsLayout({ children }: ToolsLayoutProps) {
const pathname = usePathname(); const pathname = usePathname();
const pathSplit = pathname.split('/'); const pathSplit = pathname.split('/');
const toolId = pathSplit.at(2); const toolId = pathSplit.at(2);
const isToolRoot = pathname === `/tools/${toolId}`;
const tool = TOOLS.find((tool) => tool.id === toolId); const tool = TOOLS.find((tool) => tool.id === toolId);
return ( return (
<> <>
{tool && ( {tool && (
<> <>
<title>{tool.title}</title> {isToolRoot && (
{tool.pageDescription !== undefined && ( <>
<meta name="description" content={tool.pageDescription} /> <title>{tool.title}</title>
)} <meta
property="og:image"
content={`/assets/banners/${toolId}.webp`}
/>
<meta {tool.pageDescription !== undefined && (
property="og:image" <>
content={`/assets/banners/${toolId}.webp`} <meta name="description" content={tool.pageDescription} />
/> <meta
property="og:description"
content={tool.pageDescription}
/>
</>
)}
</>
)}
</> </>
)} )}
@@ -85,15 +95,7 @@ export default function ToolsLayout({ children }: ToolsLayoutProps) {
</div> </div>
)} )}
<Suspense {children}
fallback={
<div style={{ flex: 1 }}>
<Loader />
</div>
}
>
{children}
</Suspense>
</> </>
); );
} }
+6 -5
View File
@@ -25,7 +25,6 @@ import {
Region, Region,
UNLOCALIZED_REGION_NAMES, UNLOCALIZED_REGION_NAMES,
} from '../../../constants/regions'; } from '../../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
import fetchBlitz from '../../../core/blitz/fetchBlitz'; import fetchBlitz from '../../../core/blitz/fetchBlitz';
import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo'; import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo';
import getRatingInfo from '../../../core/blitz/getRatingInfo'; import getRatingInfo from '../../../core/blitz/getRatingInfo';
@@ -36,9 +35,9 @@ import {
AccountList, AccountList,
AccountListItem, AccountListItem,
} from '../../../core/blitz/searchPlayersAcrossRegions'; } from '../../../core/blitz/searchPlayersAcrossRegions';
import { getArchivedLatestSeasonNumber } from '../../../core/blitzkrieg/getArchivedLatestSeasonNumber'; import { getArchivedLatestSeasonNumber } from '../../../core/blitzkit/getArchivedLatestSeasonNumber';
import getArchivedRatingInfo from '../../../core/blitzkrieg/getArchivedRatingInfo'; import getArchivedRatingInfo from '../../../core/blitzkit/getArchivedRatingInfo';
import { getArchivedRatingLeaderboard } from '../../../core/blitzkrieg/getArchivedRatingLeaderboard'; import { getArchivedRatingLeaderboard } from '../../../core/blitzkit/getArchivedRatingLeaderboard';
import { theme } from '../../../stitches.config'; import { theme } from '../../../stitches.config';
import { PageTurner } from './components/PageTurner'; import { PageTurner } from './components/PageTurner';
@@ -818,7 +817,9 @@ export default function Page() {
} else { } else {
setSearchLoading(true); setSearchLoading(true);
const accountList = await fetchBlitz<AccountList>( const accountList = await fetchBlitz<AccountList>(
`https://api.wotblitz.${region}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}`, region,
'account/list',
{ search: encodedSearch },
); );
const playerKeys = Object.keys( const playerKeys = Object.keys(
leaderboard[region][season], leaderboard[region][season],
@@ -1,61 +0,0 @@
import { ContextMenu, DropdownMenu } from '@radix-ui/themes';
import mutateSession, { useSession } from '../../../../stores/session';
export type CustomColumnDisplay =
| 'battles'
| 'winrate'
| 'wn8'
| 'damage'
| 'none';
const NO_DELTA: CustomColumnDisplay[] = ['battles', 'wn8', 'none'];
interface CustomColumn {
Builder: typeof DropdownMenu | typeof ContextMenu;
column: number;
}
export function CustomColumn({ column, Builder }: CustomColumn) {
const customColumn = useSession((state) => state.customColumns[column]);
return (
<Builder.Sub>
<Builder.SubTrigger>Column {column + 1}</Builder.SubTrigger>
<Builder.SubContent>
<Builder.RadioGroup
value={customColumn.display}
onValueChange={(event) =>
mutateSession((draft) => {
draft.customColumns[column].display =
event as CustomColumnDisplay;
})
}
>
<Builder.RadioItem value="battles">Battles</Builder.RadioItem>
<Builder.RadioItem value="winrate">Winrate</Builder.RadioItem>
<Builder.RadioItem value="wn8">WN8</Builder.RadioItem>
<Builder.RadioItem value="damage">Damage</Builder.RadioItem>
<Builder.RadioItem value="none">None</Builder.RadioItem>
</Builder.RadioGroup>
{!NO_DELTA.includes(customColumn.display) && (
<>
<Builder.Separator />
<Builder.CheckboxItem
checked={customColumn.showDelta}
onCheckedChange={(checked) =>
mutateSession((draft) => {
draft.customColumns[column].showDelta = checked;
})
}
>
Show delta
</Builder.CheckboxItem>
</>
)}
</Builder.SubContent>
</Builder.Sub>
);
}
-135
View File
@@ -1,135 +0,0 @@
import {
Button,
ContextMenu,
Dialog,
DropdownMenu,
Flex,
Heading,
TextField,
} from '@radix-ui/themes';
import { createColors } from 'bepaint';
import { useRef, useState } from 'react';
import {
ACCENT_COLORS,
AccentColor,
GRAY_COLORS,
GrayColor,
PALETTES,
} from '../../../../constants/radixColors';
import { useSession } from '../../../../stores/session';
import { CustomColumn } from './CustomColumn';
interface ContextMenuProps {
Builder: typeof DropdownMenu | typeof ContextMenu;
reset: () => void;
}
export function Menu({ Builder, reset }: ContextMenuProps) {
const session = useSession();
const [showTitleEditor, setShowTitleEditor] = useState(false);
const titleInput = useRef<HTMLInputElement>(null);
return (
<>
<Dialog.Root open={showTitleEditor} onOpenChange={setShowTitleEditor}>
<Dialog.Content>
<Flex direction="column" gap="4">
<Heading size="4">Edit title</Heading>
<Flex gap="2" style={{ width: '100%' }}>
<TextField.Root
style={{ flex: 1 }}
ref={titleInput}
defaultValue={session.title}
placeholder="Enter title..."
onKeyDown={(event) => {
if (event.key === 'Enter' && titleInput.current) {
setShowTitleEditor(false);
useSession.setState({ title: titleInput.current!.value });
}
}}
/>
<Dialog.Close>
<Button
onClick={() =>
useSession.setState({ title: titleInput.current!.value })
}
>
Set
</Button>
</Dialog.Close>
</Flex>
</Flex>
</Dialog.Content>
</Dialog.Root>
<Builder.Content>
<Builder.Sub>
<Builder.SubTrigger>Accent color</Builder.SubTrigger>
<Builder.SubContent>
<Builder.RadioGroup
value={session.color}
onValueChange={(value) =>
useSession.setState({ color: value as AccentColor | GrayColor })
}
>
{[...ACCENT_COLORS, ...GRAY_COLORS].map((color) => (
<Builder.RadioItem value={color} key={color}>
<Flex gap="2" align="center">
<div
style={{
backgroundColor: createColors(PALETTES[color])
.solidBackground,
width: 16,
height: 16,
borderRadius: '100%',
}}
/>
{`${color.charAt(0).toUpperCase()}${color.slice(1)}`}
</Flex>
</Builder.RadioItem>
))}
</Builder.RadioGroup>
</Builder.SubContent>
</Builder.Sub>
<Builder.Separator />
<Builder.Item onClick={() => setShowTitleEditor(true)}>
Edit title...
</Builder.Item>
<CustomColumn Builder={Builder} column={0} />
<CustomColumn Builder={Builder} column={1} />
<CustomColumn Builder={Builder} column={2} />
<CustomColumn Builder={Builder} column={3} />
<Builder.Separator />
<Builder.CheckboxItem
checked={session.showTotal}
onCheckedChange={(checked) =>
useSession.setState({ showTotal: checked })
}
>
Show total
</Builder.CheckboxItem>
<Builder.CheckboxItem
checked={session.showCareer}
onCheckedChange={(checked) =>
useSession.setState({ showCareer: checked })
}
>
Show career
</Builder.CheckboxItem>
<Builder.Separator />
<Builder.Item color="red" onClick={reset}>
Reset
</Builder.Item>
</Builder.Content>
</>
);
}
-40
View File
@@ -1,40 +0,0 @@
import { style } from '@vanilla-extract/css';
import { theme } from '../../../stitches.config';
export const toolBar = style({
'@media': {
'screen and (min-width: 640px)': {
flexDirection: 'row',
},
},
flexDirection: 'column',
display: 'flex',
gap: 8,
});
export const toolbarButton = style({
'@media': {
'screen and (min-width: 640px)': {
flex: 0,
},
},
flex: 1,
});
export const searchButton = style({
border: 'none',
background: 'none',
fontSize: 16,
display: 'flex',
padding: 8,
alignItems: 'center',
justifyContent: 'space-between',
cursor: 'pointer',
':hover': {
backgroundColor: theme.colors.componentInteractiveHover,
borderRadius: 4,
},
});
+443 -210
View File
@@ -1,267 +1,500 @@
'use client'; 'use client';
import { import {
CaretDownIcon, ArrowDownIcon,
CopyIcon, MagnifyingGlassIcon,
PersonIcon, PlusIcon,
ReloadIcon,
} from '@radix-ui/react-icons'; } from '@radix-ui/react-icons';
import { import {
AlertDialog, AlertDialog,
Button, Button,
Checkbox, Card,
DropdownMenu, DropdownMenu,
Flex, Flex,
Heading, Heading,
IconButton,
Link,
Select,
Table,
Text, Text,
TextField, TextField,
} from '@radix-ui/themes'; } from '@radix-ui/themes';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { ChangeEvent, useEffect, useRef, useState } from 'react'; import { use, useEffect, useMemo, useRef, useState } from 'react';
import PageWrapper from '../../../components/PageWrapper'; import PageWrapper from '../../../components/PageWrapper';
import { UNLOCALIZED_REGION_NAMES } from '../../../constants/regions'; import { UNLOCALIZED_REGION_NAMES_SHORT } from '../../../constants/regions';
import {
generateStats,
prettifyStats,
Stat,
STAT_KEYS,
STAT_NAMES,
sumStats,
} from '../../../core/blitz/generateStats';
import {
getAccountInfo,
IndividualAccountInfo,
} from '../../../core/blitz/getAccountInfo';
import getTankStats from '../../../core/blitz/getTankStats';
import { idToRegion } from '../../../core/blitz/idToRegion';
import searchPlayersAcrossRegions, { import searchPlayersAcrossRegions, {
AccountListWithServer, AccountListWithServer,
} from '../../../core/blitz/searchPlayersAcrossRegions'; } from '../../../core/blitz/searchPlayersAcrossRegions';
import { resetSession } from '../../../core/blitzkrieg/resetSession'; import { tankDefinitions } from '../../../core/blitzkit/tankDefinitions';
import { setSession } from '../../../core/blitzkrieg/setSession'; import { tankIcon } from '../../../core/blitzkit/tankIcon';
import { useSession } from '../../../stores/session'; import { tankAverages } from '../../../core/blitzstars/tankAverages';
import SessionPage from '../../embeds/session/page'; import { deltaTankStats } from '../../../core/statistics/deltaTankStats';
import { Menu } from './components/Menu'; import { useWideFormat } from '../../../hooks/useWideFormat';
import * as styles from './page.css'; import { theme } from '../../../stitches.config';
import mutateSession, {
SessionTracking,
useSession,
} from '../../../stores/session';
import { IndividualTankStats } from '../../../types/tanksStats';
export default function Page() { export default function Page() {
const wideFormat = useWideFormat(640);
const awaitedTankDefinitions = use(tankDefinitions);
const awaitedTankAvearges = use(tankAverages);
const [showSearch, setShowSearch] = useState(false);
const [searching, setSearching] = useState(false);
const [searchResults, setSearchResults] = useState<AccountListWithServer>([]);
const [showCCInaccessibilityPrompt, setShowCCInaccessibilityPrompt] =
useState(false);
const input = useRef<HTMLInputElement>(null); const input = useRef<HTMLInputElement>(null);
const [searchResults, setSearchResults] = useState<
AccountListWithServer | undefined
>(undefined);
const [showSearchResults, setShowSearchResults] = useState(false);
const handleChange = debounce(
async (event: ChangeEvent<HTMLInputElement>) => {
if (event.target.value) {
setSearchResults(await searchPlayersAcrossRegions(event.target.value));
} else {
setSearchResults(undefined);
setShowSearchResults(false);
}
},
500,
);
const session = useSession(); const session = useSession();
const [tankStatsB, setTankStatsB] = useState<IndividualTankStats[] | null>(
null,
);
const accountInfoPromise = useMemo(
() =>
new Promise<IndividualAccountInfo | null>(async (resolve) => {
resolve(
session.tracking
? await getAccountInfo(session.player.region, session.player.id)
: null,
);
}),
[session.tracking && session.player.id],
);
const accountInfo = use(accountInfoPromise);
const delta = useMemo(
() =>
session.tracking && tankStatsB
? deltaTankStats(session.player.stats, tankStatsB)
.sort((a, b) => b.last_battle_time - a.last_battle_time)
.map((entry) => {
const tank = awaitedTankDefinitions[entry.tank_id];
const average = awaitedTankAvearges[tank.id];
const stats = generateStats(entry.all, average?.all);
const statsPretty = prettifyStats(stats);
return {
tank,
stats,
statsPretty,
};
})
: undefined,
[session.tracking && session.player, tankStatsB],
);
const total = useMemo(
() =>
delta
? prettifyStats(sumStats(delta.map(({ stats }) => stats)))
: undefined,
[delta],
);
const search = debounce(async () => {
if (!input.current) return;
setSearchResults(await searchPlayersAcrossRegions(input.current.value, 9));
setSearching(false);
}, 500);
useEffect(() => { useEffect(() => {
if (session.isTracking) input.current!.value = session.nickname; async function update() {
}, [session]); if (!session.tracking) return;
const tankStats = await getTankStats(
idToRegion(session.player.id),
session.player.id,
);
setTankStatsB(tankStats);
}
update();
const interval = setInterval(update, 5 * 1000);
return () => clearInterval(interval);
}, [session.tracking && session.player.id]);
return ( return (
<PageWrapper color="blue"> <PageWrapper>
<div className={styles.toolBar}> <AlertDialog.Root
<div style={{ flex: 1, boxSizing: 'border-box', position: 'relative' }}> open={showCCInaccessibilityPrompt}
onOpenChange={setShowCCInaccessibilityPrompt}
>
<AlertDialog.Content>
<AlertDialog.Title>
You're attempting to track a CC account
</AlertDialog.Title>
<AlertDialog.Description>
CC (community contributor) accounts do not have public tank
statistics. We are in the process of adding embeds for CC accounts,
stay tuned.
</AlertDialog.Description>
<Flex mt="4" justify="end">
<AlertDialog.Action>
<Button variant="solid">Alright</Button>
</AlertDialog.Action>
</Flex>
</AlertDialog.Content>
</AlertDialog.Root>
{!session.tracking && (
<Flex
style={{ flex: 1 }}
align="center"
justify="center"
direction="column"
gap="3"
>
<Flex gap="2" align="center">
<Text color="gray">Look up a player to get started</Text>
<ArrowDownIcon />
</Flex>
<TextField.Root <TextField.Root
defaultValue={session.isTracking ? session.nickname : undefined} size="3"
style={{
position: 'relative',
width: '75vw',
maxWidth: 480,
}}
ref={input} ref={input}
onChange={(event) => { placeholder="Search players..."
event.stopPropagation(); onChange={() => {
if (!input.current) return;
if (event.target.value) { const sanitized = input.current.value.trim();
setShowSearchResults(true);
setSearchResults(undefined); if (sanitized.length > 0) {
setShowSearch(true);
setSearching(true);
search();
} else { } else {
setShowSearchResults(false); setShowSearch(false);
}
handleChange(event);
}}
onBlur={(event) => {
// TODO: remove this hack when https://github.com/radix-ui/primitives/issues/2193 (https://github.com/radix-ui/primitives/issues/1969) is fixed
if (showSearchResults) event.target.focus();
}}
onKeyDown={(event) => {
if (event.key === 'Escape') {
setShowSearchResults(false);
input.current?.blur();
} }
}} }}
placeholder="Search for a player..."
> >
<TextField.Slot> <TextField.Slot>
<PersonIcon height="16" width="16" /> <MagnifyingGlassIcon />
</TextField.Slot> </TextField.Slot>
{showSearch && (
<Card
mt="2"
style={{
position: 'absolute',
top: '100%',
left: 0,
width: '100%',
}}
>
{searching && (
<Flex justify="center">
<Text color="gray">Searching...</Text>
</Flex>
)}
{!searching && searchResults.length === 0 && (
<Flex justify="center">
<Text color="gray">No players found</Text>
</Flex>
)}
{!searching && searchResults.length > 0 && (
<Flex direction="column" gap="2">
{searchResults.map((player) => (
<Button
key={player.account_id}
variant="ghost"
onClick={async () => {
const stats = await getTankStats(
player.region,
player.account_id,
);
if (stats === null) {
setShowCCInaccessibilityPrompt(true);
return;
}
setShowSearch(false);
mutateSession((draft) => {
if (!input.current) return;
draft.tracking = true;
(draft as SessionTracking).player = {
id: player.account_id,
region: player.region,
since: Date.now(),
stats,
};
input.current.value = player.nickname;
});
}}
>
{player.nickname} (
{UNLOCALIZED_REGION_NAMES_SHORT[player.region]})
</Button>
))}
</Flex>
)}
</Card>
)}
</TextField.Root> </TextField.Root>
</Flex>
)}
<DropdownMenu.Root open={showSearchResults} modal={false}> {session.tracking && (
<DropdownMenu.Trigger> <Flex
<div /> mt="2"
</DropdownMenu.Trigger> mb="2"
direction={wideFormat ? 'row' : 'column'}
align="center"
justify="between"
gap="2"
>
<Flex mt="2" mb="2" direction="column" gap="2">
<Heading size="5">Tracking {accountInfo?.nickname}</Heading>
<Text color="gray">
Since {new Date(session.player.since).toLocaleString()}
</Text>
</Flex>
<DropdownMenu.Content> <Flex gap="2" align="center" justify="center">
{searchResults === undefined ? ( <Button
<DropdownMenu.Item disabled>Searching...</DropdownMenu.Item> color="red"
) : searchResults.length === 0 ? ( onClick={async () => {
<DropdownMenu.Item disabled>No results</DropdownMenu.Item> const stats = await getTankStats(
) : ( session.player.region,
searchResults?.map( session.player.id,
({ account_id: id, nickname, region }, index) => ( );
<DropdownMenu.Item
key={id}
onClick={() => {
setShowSearchResults(false);
setSession(region, id, nickname);
}}
shortcut={UNLOCALIZED_REGION_NAMES[region]}
>
{nickname}
</DropdownMenu.Item>
),
)
)}
</DropdownMenu.Content>
</DropdownMenu.Root>
</div>
<div style={{ display: 'flex', gap: 8 }}> if (stats === null) {
<AlertDialog.Root> setShowCCInaccessibilityPrompt(true);
<AlertDialog.Trigger> return;
<Button }
variant="soft"
onClick={(event) => {
if (!session.resetPrompt) {
event.preventDefault();
resetSession();
}
}}
className={styles.toolbarButton}
color="red"
>
<ReloadIcon width="16" height="16" /> Reset
</Button>
</AlertDialog.Trigger>
<AlertDialog.Content> mutateSession((draft) => {
<Flex direction="column" gap="4"> (draft as SessionTracking).player.stats = stats;
<Flex direction="column" gap="0"> });
<AlertDialog.Title> }}
Are you sure you want to reset? >
</AlertDialog.Title> Reset
<AlertDialog.Description> </Button>
This'll discard your current session and start tracking from <Button
scratch again. onClick={() => {
</AlertDialog.Description> mutateSession((draft) => {
</Flex> draft.tracking = false;
});
}}
>
Change
</Button>
</Flex>
</Flex>
)}
<Text size="2"> {session.tracking && delta && delta.length > 0 && (
<Checkbox <Table.Root variant="surface">
mr="1" <Table.Header>
onCheckedChange={(checked) => <Table.Row>
useSession.setState({ <Table.ColumnHeaderCell>Tank</Table.ColumnHeaderCell>
resetPrompt: !checked, {session.columns.map((column, index) => (
}) <Table.ColumnHeaderCell
} width="0"
/> key={column}
Don't ask me again
</Text>
</Flex>
<Flex gap="3" mt="4">
<AlertDialog.Cancel>
<Button variant="soft" color="gray">
Cancel
</Button>
</AlertDialog.Cancel>
<AlertDialog.Action>
<Button variant="solid" color="red" onClick={resetSession}>
Reset
</Button>
</AlertDialog.Action>
</Flex>
</AlertDialog.Content>
</AlertDialog.Root>
<AlertDialog.Root>
<AlertDialog.Trigger>
<Button
variant="soft"
className={styles.toolbarButton}
onClick={(event) => {
if (!session.embedPrompt) {
event.preventDefault();
resetSession();
}
navigator.clipboard.writeText(
`${location.origin}/embeds/session`,
);
}}
>
<CopyIcon width="16" height="16" /> Embed
</Button>
</AlertDialog.Trigger>
<AlertDialog.Content>
<AlertDialog.Title>
Copied link to your clipboard
</AlertDialog.Title>
<AlertDialog.Description>
Instructions for streamers:
<iframe
style={{ style={{
width: '100%', position: 'relative',
aspectRatio: '16 / 9',
border: 'none',
}} }}
src="https://www.youtube-nocookie.com/embed/e--GwjyrJAE?si=ftEiCmpbMI2FUY5N" >
title="YouTube video player" {index === 0 && session.columns.length < 4 && (
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" <DropdownMenu.Root>
></iframe> <DropdownMenu.Trigger>
</AlertDialog.Description> <IconButton
size="1"
variant="surface"
style={{
position: 'absolute',
left: 0,
top: '50%',
transform: 'translate(-100%, -50%)',
zIndex: 1,
}}
>
<PlusIcon />
</IconButton>
</DropdownMenu.Trigger>
<Text size="2"> <DropdownMenu.Content>
<Checkbox {STAT_KEYS.filter(
mr="1" (item) => !session.columns.includes(item),
onCheckedChange={(checked) => ).map((key) => (
useSession.setState({ <DropdownMenu.Item
embedPrompt: !checked, key={key}
}) onClick={() => {
} mutateSession((draft) => {
/> (draft as SessionTracking).columns.unshift(
Don't show this again key as Stat,
</Text> );
});
}}
>
{STAT_NAMES[key]}
</DropdownMenu.Item>
))}
</DropdownMenu.Content>
</DropdownMenu.Root>
)}
<Flex gap="3" mt="4"> <Select.Root
<AlertDialog.Cancel> value={column}
<Button variant="soft">Got it!</Button> onValueChange={(value) => {
</AlertDialog.Cancel> mutateSession((draft) => {
</Flex> if (value === 'remove') {
</AlertDialog.Content> (draft as SessionTracking).columns.splice(index, 1);
</AlertDialog.Root> } else {
(draft as SessionTracking).columns[index] =
value as Stat;
}
});
}}
>
<Select.Trigger variant="ghost" />
<DropdownMenu.Root> <Select.Content>
<DropdownMenu.Trigger> {STAT_KEYS.filter(
<Button variant="soft" className={styles.toolbarButton}> (item) =>
Options !session.columns.includes(item) || item === column,
<CaretDownIcon /> ).map((key) => (
</Button> <Select.Item key={key} value={key}>
</DropdownMenu.Trigger> {STAT_NAMES[key]}
</Select.Item>
))}
<Menu Builder={DropdownMenu} reset={resetSession} /> <Select.Separator />
</DropdownMenu.Root>
</div>
</div>
<SessionPage naked={false} /> <Select.Item value="remove">Remove</Select.Item>
</Select.Content>
</Select.Root>
</Table.ColumnHeaderCell>
))}
</Table.Row>
</Table.Header>
{!session.isTracking && ( <Table.Body>
<Table.Row
style={{
overflow: 'hidden',
}}
>
<Table.RowHeaderCell
style={{
paddingLeft: 32,
position: 'relative',
overflowY: 'hidden',
}}
>
Total
</Table.RowHeaderCell>
{session.columns.map((column) => (
<Table.Cell key={column}>{total![column]}</Table.Cell>
))}
</Table.Row>
{delta.map(({ statsPretty, tank }) => {
return (
<Table.Row
key={tank.id}
style={{
overflow: 'hidden',
}}
>
<Table.RowHeaderCell
style={{
paddingLeft: 32,
position: 'relative',
overflowY: 'hidden',
}}
>
<Link href={`/tools/tankopedia/${tank.id}`}>
<img
draggable={false}
src={tankIcon(tank.id)}
style={{
position: 'absolute',
width: 128 + 32,
height: '200%',
top: '-50%',
left: 0,
objectFit: 'contain',
objectPosition: '50% 50%',
overflow: 'hidden',
}}
/>
<div
style={{
backgroundColor: 'red',
position: 'absolute',
top: 0,
left: 0,
height: '100%',
width: 128,
background:
'linear-gradient(90deg, #00000080, #00000000)',
}}
/>
<Text
style={{
color: theme.colors.textHighContrast,
position: 'relative',
textWrap: 'nowrap',
textShadow: 'black 0 0 4px',
}}
>
{tank.name}
</Text>
</Link>
</Table.RowHeaderCell>
{session.columns.map((column) => (
<Table.Cell key={column}>{statsPretty[column]}</Table.Cell>
))}
</Table.Row>
);
})}
</Table.Body>
</Table.Root>
)}
{session.tracking && delta && delta.length === 0 && (
<Flex <Flex
align="center" align="center"
justify="center" justify="center"
direction="column" direction="column"
style={{ flex: 1 }} style={{
flex: 1,
}}
> >
<Heading color="gray">No player selected</Heading> <Heading color="gray">No battles</Heading>
<Text color="gray"> <Text color="gray">Go ahead and play a game</Text>
Search for a <PersonIcon /> Player to get started
</Text>
</Flex> </Flex>
)} )}
</PageWrapper> </PageWrapper>
+107
View File
@@ -0,0 +1,107 @@
import { CaretDownIcon, CaretRightIcon } from '@radix-ui/react-icons';
import { Button, Flex, Table, Text } from '@radix-ui/themes';
import { random } from 'lodash';
import { use, useMemo, useState } from 'react';
import { tankDefinitions } from '../../../../core/blitzkit/tankDefinitions';
import { tankIcon } from '../../../../core/blitzkit/tankIcon';
interface DayProps {
offset: number;
}
export function Day({ offset }: DayProps) {
const timeTo = new Date(Date.now() - offset * 24 * 60 * 60 * 1000);
const timeFrom = new Date(Date.now() - (offset + 1) * 24 * 60 * 60 * 1000);
const awaitedTankDefinitions = use(tankDefinitions);
const tanksList = useMemo(
() => Object.values(awaitedTankDefinitions),
[awaitedTankDefinitions],
);
const [expanded, setExpanded] = useState(offset === 0);
const tanks = tanksList
.filter((tank) => tank.tier === 10)
.sort(() => Math.random() - 0.5)
.slice(0, random(2, 5));
return (
<>
<Table.Row>
<Table.RowHeaderCell>
<Button
style={{
width: '100%',
}}
variant="ghost"
onClick={() => setExpanded((state) => !state)}
>
<Flex
align="center"
gap="1"
style={{
width: '100%',
}}
>
{expanded ? <CaretDownIcon /> : <CaretRightIcon />}
{timeTo.toLocaleString('default', {
month: 'short',
day: 'numeric',
})}
</Flex>
</Button>
</Table.RowHeaderCell>
<Table.Cell align="right">{random(1, 10)}</Table.Cell>
<Table.Cell align="right">
{random(60, 80, true).toFixed(0)}%
</Table.Cell>
</Table.Row>
{expanded &&
tanks.map((tank) => (
<Table.Row
key={tank.id}
style={{
overflow: 'hidden',
}}
>
<Table.RowHeaderCell
style={{
paddingLeft: 32,
position: 'relative',
overflowY: 'hidden',
}}
>
<img
draggable={false}
src={tankIcon(tank.id)}
style={{
position: 'absolute',
width: 128 + 32,
height: '200%',
top: '-50%',
left: 0,
boxShadow: 'inset 128px 0 64px -64px #00000040',
objectFit: 'contain',
objectPosition: '50% 50%',
overflow: 'hidden',
}}
/>
<Text
style={{
position: 'relative',
textWrap: 'nowrap',
textShadow: 'black 0 0 4px',
}}
>
{tank.name}
</Text>
</Table.RowHeaderCell>
<Table.Cell align="right">{random(1, 10)}</Table.Cell>
<Table.Cell align="right">
{random(60, 80, true).toFixed(0)}%
</Table.Cell>
</Table.Row>
))}
</>
);
}
+40
View File
@@ -0,0 +1,40 @@
'use client';
import { Flex, Heading, Table } from '@radix-ui/themes';
import { random, times } from 'lodash';
import { HeroStat } from '../../../components/AllStatsOverview/components/HeroStat';
import PageWrapper from '../../../components/PageWrapper';
import { Day } from './components/Day';
export default function Page() {
return (
<PageWrapper color="iris">
<Flex gap="2">
<Heading>TresAbhi</Heading>
<Heading color="gray">[OPTML]</Heading>
</Flex>
<HeroStat color="#8E4EC6" subtitle="Super Unicum" value={3142} />
<Table.Root variant="surface">
<Table.Header>
<Table.Row>
<Table.ColumnHeaderCell>Day</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" align="right">
Battles
</Table.ColumnHeaderCell>
<Table.ColumnHeaderCell width="0" align="right">
Winrate
</Table.ColumnHeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{times(random(5, 10), (index) => (
<Day offset={index} key={index} />
))}
</Table.Body>
</Table.Root>
</PageWrapper>
);
}
@@ -5,11 +5,11 @@ import { ModuleButton } from '../../../../../components/ModuleButtons/ModuleButt
import { ShellButton } from '../../../../../components/ModuleButtons/ShellButton'; import { ShellButton } from '../../../../../components/ModuleButtons/ShellButton';
import { SmallTankIcon } from '../../../../../components/SmallTankIcon'; import { SmallTankIcon } from '../../../../../components/SmallTankIcon';
import { resolveNearPenetration } from '../../../../../core/blitz/resolveNearPenetration'; import { resolveNearPenetration } from '../../../../../core/blitz/resolveNearPenetration';
import { pushTankopediaPath } from '../../../../../core/blitzkrieg/pushTankopediaPath'; import { pushTankopediaPath } from '../../../../../core/blitzkit/pushTankopediaPath';
import { import {
SHELL_NAMES, SHELL_NAMES,
TIER_ROMAN_NUMERALS, TIER_ROMAN_NUMERALS,
} from '../../../../../core/blitzkrieg/tankDefinitions/constants'; } from '../../../../../core/blitzkit/tankDefinitions/constants';
import { mutateDuel, useDuel } from '../../../../../stores/duel'; import { mutateDuel, useDuel } from '../../../../../stores/duel';
import { import {
mutateTankopediaTemporary, mutateTankopediaTemporary,
@@ -191,6 +191,7 @@ export function AntagonistBar({ floating }: AntagonistBarProps) {
<Flex> <Flex>
{antagonist.turret.guns.map((gun, index) => ( {antagonist.turret.guns.map((gun, index) => (
<ModuleButton <ModuleButton
key={gun.id}
rowChild rowChild
first={index === 0} first={index === 0}
last={ last={
@@ -9,8 +9,8 @@ import {
} from '@radix-ui/themes'; } from '@radix-ui/themes';
import { use } from 'react'; import { use } from 'react';
import { ConsumablesManager } from '../../../../../../../components/ConsumablesManager'; import { ConsumablesManager } from '../../../../../../../components/ConsumablesManager';
import { checkConsumableProvisionInclusivity } from '../../../../../../../core/blitzkrieg/checkConsumableProvisionInclusivity'; import { checkConsumableProvisionInclusivity } from '../../../../../../../core/blitzkit/checkConsumableProvisionInclusivity';
import { consumableDefinitions } from '../../../../../../../core/blitzkrieg/consumableDefinitions'; import { consumableDefinitions } from '../../../../../../../core/blitzkit/consumableDefinitions';
import { useEquipment } from '../../../../../../../hooks/useEquipment'; import { useEquipment } from '../../../../../../../hooks/useEquipment';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel'; import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper'; import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
@@ -1,7 +1,7 @@
import { Button, Flex, Heading } from '@radix-ui/themes'; import { Button, Flex, Heading } from '@radix-ui/themes';
import { use } from 'react'; import { use } from 'react';
import { EquipmentManager } from '../../../../../../../components/EquipmentManager'; import { EquipmentManager } from '../../../../../../../components/EquipmentManager';
import { equipmentDefinitions } from '../../../../../../../core/blitzkrieg/equipmentDefinitions'; import { equipmentDefinitions } from '../../../../../../../core/blitzkit/equipmentDefinitions';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel'; import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper'; import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
@@ -2,7 +2,7 @@ import { Flex, Heading, Slider, TextField } from '@radix-ui/themes';
import { clamp, debounce } from 'lodash'; import { clamp, debounce } from 'lodash';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { GenericTankComponentButton } from '../../../../../../../components/ModuleButtons/GenericTankComponentButton'; import { GenericTankComponentButton } from '../../../../../../../components/ModuleButtons/GenericTankComponentButton';
import { asset } from '../../../../../../../core/blitzkrieg/asset'; import { asset } from '../../../../../../../core/blitzkit/asset';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel'; import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper'; import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
@@ -1,8 +1,8 @@
import { Button, Flex, Heading } from '@radix-ui/themes'; import { Button, Flex, Heading } from '@radix-ui/themes';
import { use } from 'react'; import { use } from 'react';
import { ProvisionsManager } from '../../../../../../../components/ProvisionsManager'; import { ProvisionsManager } from '../../../../../../../components/ProvisionsManager';
import { availableProvisions } from '../../../../../../../core/blitzkrieg/availableProvisions'; import { availableProvisions } from '../../../../../../../core/blitzkit/availableProvisions';
import { provisionDefinitions } from '../../../../../../../core/blitzkrieg/provisionDefinitions'; import { provisionDefinitions } from '../../../../../../../core/blitzkit/provisionDefinitions';
import { mutateDuel, useDuel } from '../../../../../../../stores/duel'; import { mutateDuel, useDuel } from '../../../../../../../stores/duel';
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper'; import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
@@ -10,8 +10,8 @@ import {
} from '@radix-ui/themes'; } from '@radix-ui/themes';
import { use } from 'react'; import { use } from 'react';
import { CrewSkillManager } from '../../../../../../../../components/CrewSkillManager'; import { CrewSkillManager } from '../../../../../../../../components/CrewSkillManager';
import { createDefaultSkills } from '../../../../../../../../core/blitzkrieg/createDefaultSkills'; import { createDefaultSkills } from '../../../../../../../../core/blitzkit/createDefaultSkills';
import { skillDefinitions } from '../../../../../../../../core/blitzkrieg/skillDefinitions'; import { skillDefinitions } from '../../../../../../../../core/blitzkit/skillDefinitions';
import { import {
mutateTankopediaTemporary, mutateTankopediaTemporary,
useTankopediaTemporary, useTankopediaTemporary,
@@ -9,21 +9,21 @@ import {
TextField, TextField,
} from '@radix-ui/themes'; } from '@radix-ui/themes';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { use, useEffect, useRef, useState } from 'react'; import { Fragment, use, useEffect, useRef, useState } from 'react';
import { lerp } from 'three/src/math/MathUtils'; import { lerp } from 'three/src/math/MathUtils';
import { ShellButton } from '../../../../../../components/ModuleButtons/ShellButton'; import { ShellButton } from '../../../../../../components/ModuleButtons/ShellButton';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits'; import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
import { isExplosive } from '../../../../../../core/blitz/isExplosive'; import { isExplosive } from '../../../../../../core/blitz/isExplosive';
import { coefficient } from '../../../../../../core/blitzkrieg/coefficient'; import { coefficient } from '../../../../../../core/blitzkit/coefficient';
import { equipmentDefinitions } from '../../../../../../core/blitzkrieg/equipmentDefinitions'; import { equipmentDefinitions } from '../../../../../../core/blitzkit/equipmentDefinitions';
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions'; import { modelDefinitions } from '../../../../../../core/blitzkit/modelDefinitions';
import { provisionDefinitions } from '../../../../../../core/blitzkrieg/provisionDefinitions'; import { provisionDefinitions } from '../../../../../../core/blitzkit/provisionDefinitions';
import { tankCharacteristics } from '../../../../../../core/blitzkrieg/tankCharacteristics'; import { tankCharacteristics } from '../../../../../../core/blitzkit/tankCharacteristics';
import { import {
CREW_MEMBER_NAMES, CREW_MEMBER_NAMES,
GUN_TYPE_NAMES, GUN_TYPE_NAMES,
SHELL_NAMES, SHELL_NAMES,
} from '../../../../../../core/blitzkrieg/tankDefinitions/constants'; } from '../../../../../../core/blitzkit/tankDefinitions/constants';
import { useEquipment } from '../../../../../../hooks/useEquipment'; import { useEquipment } from '../../../../../../hooks/useEquipment';
import { useFullScreen } from '../../../../../../hooks/useFullScreen'; import { useFullScreen } from '../../../../../../hooks/useFullScreen';
import { mutateDuel, useDuel } from '../../../../../../stores/duel'; import { mutateDuel, useDuel } from '../../../../../../stores/duel';
@@ -155,7 +155,6 @@ export function Characteristics() {
})} })}
</Flex> </Flex>
</Flex> </Flex>
<Info name="Gun type">{GUN_TYPE_NAMES[gun.type]}</Info> <Info name="Gun type">{GUN_TYPE_NAMES[gun.type]}</Info>
<InfoWithDelta name="DPM" decimals={0} unit="hp / min"> <InfoWithDelta name="DPM" decimals={0} unit="hp / min">
{stats.dpm} {stats.dpm}
@@ -178,7 +177,7 @@ export function Characteristics() {
name={ name={
<a <a
target="_blank" target="_blank"
href="https://tresabhi.github.io/blitzkrieg/guide/dpm.html" href="https://tresabhi.github.io/blitzkit/guide/dpm.html"
> >
What's the difference? What's the difference?
</a> </a>
@@ -189,7 +188,6 @@ export function Characteristics() {
{gun.type !== 'regular' && ( {gun.type !== 'regular' && (
<InfoWithDelta name="Shells">{stats.shells}</InfoWithDelta> <InfoWithDelta name="Shells">{stats.shells}</InfoWithDelta>
)} )}
{gun.type === 'autoReloader' ? ( {gun.type === 'autoReloader' ? (
<> <>
<Info indent name="Most optimal"> <Info indent name="Most optimal">
@@ -218,21 +216,17 @@ export function Characteristics() {
{stats.shellReload!} {stats.shellReload!}
</InfoWithDelta> </InfoWithDelta>
)} )}
{gun.type === 'autoLoader' || {(gun.type === 'autoLoader' || gun.type === 'autoReloader') && (
(gun.type === 'autoReloader' && ( <InfoWithDelta
<InfoWithDelta indent
indent decimals={2}
decimals={2} name="Intra-clip"
name="Intra-clip" unit="s"
unit="s" deltaType="lowerIsBetter"
deltaType="lowerIsBetter" >
> {stats.intraClip!}
{stats.intraClip!} </InfoWithDelta>
</InfoWithDelta> )}
))}
<InfoWithDelta name="Caliber" decimals={0} unit="mm">
{stats.caliber}
</InfoWithDelta>
<InfoWithDelta decimals={0} name="Penetration" unit="mm"> <InfoWithDelta decimals={0} name="Penetration" unit="mm">
{stats.penetration} {stats.penetration}
</InfoWithDelta> </InfoWithDelta>
@@ -296,6 +290,22 @@ export function Characteristics() {
{stats.explosionRadius!} {stats.explosionRadius!}
</InfoWithDelta> </InfoWithDelta>
)} )}
<InfoWithDelta name="Caliber" decimals={0} unit="mm">
{stats.caliber}
</InfoWithDelta>
<InfoWithDelta name="Normalization" decimals={0} unit="°">
{stats.shellNormalization}
</InfoWithDelta>
{shell.type !== 'he' && (
<InfoWithDelta
name="Ricochet"
decimals={0}
deltaType="lowerIsBetter"
unit="°"
>
{stats.shellRicochet!}
</InfoWithDelta>
)}
<InfoWithDelta name="Shell velocity" unit="m/s" decimals={0}> <InfoWithDelta name="Shell velocity" unit="m/s" decimals={0}>
{stats.shellVelocity} {stats.shellVelocity}
</InfoWithDelta> </InfoWithDelta>
@@ -517,7 +527,7 @@ export function Characteristics() {
const count = member.count ?? 1; const count = member.count ?? 1;
return ( return (
<> <Fragment key={member.type}>
<InfoWithDelta <InfoWithDelta
key={`${member.type}-root`} key={`${member.type}-root`}
name={`${CREW_MEMBER_NAMES[member.type]}${count > 1 ? ` x ${count}` : ''}`} name={`${CREW_MEMBER_NAMES[member.type]}${count > 1 ? ` x ${count}` : ''}`}
@@ -546,7 +556,7 @@ export function Characteristics() {
} }
/> />
)} )}
</> </Fragment>
); );
})} })}
</Flex> </Flex>
@@ -4,9 +4,9 @@ import { useEffect, useRef } from 'react';
import { Vector3 } from 'three'; import { Vector3 } from 'three';
import { OrbitControls as OrbitControlsClass } from 'three-stdlib'; import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits'; import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
import { hasEquipment } from '../../../../../core/blitzkrieg/hasEquipment'; import { hasEquipment } from '../../../../../core/blitzkit/hasEquipment';
import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions'; import { modelDefinitions } from '../../../../../core/blitzkit/modelDefinitions';
import { Pose, poseEvent } from '../../../../../core/blitzkrieg/pose'; import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
import { useAwait } from '../../../../../hooks/useAwait'; import { useAwait } from '../../../../../hooks/useAwait';
import { useDuel } from '../../../../../stores/duel'; import { useDuel } from '../../../../../stores/duel';
import { useTankopediaPersistent } from '../../../../../stores/tankopedia'; import { useTankopediaPersistent } from '../../../../../stores/tankopedia';
@@ -7,14 +7,14 @@ import { isMobile } from 'react-device-detect';
import { Armor } from '../../../../../../components/Armor'; import { Armor } from '../../../../../../components/Armor';
import { ShotDisplay } from '../../../../../../components/Armor/components/ShotDisplay'; import { ShotDisplay } from '../../../../../../components/Armor/components/ShotDisplay';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits'; import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions'; import { modelDefinitions } from '../../../../../../core/blitzkit/modelDefinitions';
import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform'; import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
import { Pose, poseEvent } from '../../../../../../core/blitzkrieg/pose'; import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
import { tankIcon } from '../../../../../../core/blitzkrieg/tankIcon'; import { tankIcon } from '../../../../../../core/blitzkit/tankIcon';
import { useEquipment } from '../../../../../../hooks/useEquipment'; import { useEquipment } from '../../../../../../hooks/useEquipment';
import { useFullScreen } from '../../../../../../hooks/useFullScreen'; import { useFullScreen } from '../../../../../../hooks/useFullScreen';
import { useWideFormat } from '../../../../../../hooks/useWideFormat'; import { useWideFormat } from '../../../../../../hooks/useWideFormat';
import { BlitzkriegWormWide } from '../../../../../../icons/BlitzkriegWormWide'; import { BlitzkitWide } from '../../../../../../icons/BlitzkitWide';
import { mutateDuel, useDuel } from '../../../../../../stores/duel'; import { mutateDuel, useDuel } from '../../../../../../stores/duel';
import mutateTankopediaPersistent, { import mutateTankopediaPersistent, {
TankopediaMode, TankopediaMode,
@@ -177,16 +177,18 @@ export function TankSandbox() {
</Tabs.List> </Tabs.List>
</Tabs.Root> </Tabs.Root>
<Text {wideFormat && (
color="gray" <Text
style={{ color="gray"
position: 'absolute', style={{
left: '50%', position: 'absolute',
top: 11, left: '50%',
}} top: 10,
> }}
<BlitzkriegWormWide height={32} /> >
</Text> <BlitzkitWide height={32} />
</Text>
)}
<div <div
style={{ style={{
@@ -199,6 +201,10 @@ export function TankSandbox() {
{loadModel ? ( {loadModel ? (
<div style={{ width: '100%', height: '100%' }}> <div style={{ width: '100%', height: '100%' }}>
<Canvas <Canvas
gl={{
clippingPlanes: Object.freeze([]),
localClippingEnabled: true,
}}
shadows shadows
ref={canvas} ref={canvas}
onPointerDown={handlePointerDown} onPointerDown={handlePointerDown}
@@ -1,5 +1,5 @@
import { Html, useProgress } from '@react-three/drei'; import { Html, useProgress } from '@react-three/drei';
import { Loader } from '../../../../../components/Loader'; import { Loader } from '../../../../../../../components/Loader';
export function ModelLoader() { export function ModelLoader() {
const { progress } = useProgress(); const { progress } = useProgress();
@@ -6,7 +6,8 @@ import {
} from '@radix-ui/react-icons'; } from '@radix-ui/react-icons';
import { Button, DropdownMenu, Flex } from '@radix-ui/themes'; import { Button, DropdownMenu, Flex } from '@radix-ui/themes';
import { RefObject } from 'react'; import { RefObject } from 'react';
import { Pose, poseEvent } from '../../../../../../../core/blitzkrieg/pose'; import { Pose, poseEvent } from '../../../../../../../core/blitzkit/pose';
import { useApp } from '../../../../../../../stores/app';
import mutateTankopediaPersistent, { import mutateTankopediaPersistent, {
mutateTankopediaTemporary, mutateTankopediaTemporary,
useTankopediaPersistent, useTankopediaPersistent,
@@ -40,6 +41,7 @@ export function Options({ isFullScreen }: OptionsProps) {
const environment = useTankopediaPersistent( const environment = useTankopediaPersistent(
(state) => state.model.visual.environment, (state) => state.model.visual.environment,
); );
const developerMode = useApp((state) => state.developerMode);
return ( return (
<Flex <Flex
@@ -107,16 +109,18 @@ export function Options({ isFullScreen }: OptionsProps) {
Green penetration Green penetration
</DropdownMenu.CheckboxItem> </DropdownMenu.CheckboxItem>
<DropdownMenu.CheckboxItem {developerMode && (
checked={wireframe} <DropdownMenu.CheckboxItem
onCheckedChange={(checked) => { checked={wireframe}
mutateTankopediaPersistent((draft) => { onCheckedChange={(checked) => {
draft.model.visual.wireframe = checked; mutateTankopediaPersistent((draft) => {
}); draft.model.visual.wireframe = checked;
}} });
> }}
Wireframe >
</DropdownMenu.CheckboxItem> Wireframe
</DropdownMenu.CheckboxItem>
)}
<DropdownMenu.CheckboxItem <DropdownMenu.CheckboxItem
checked={opaque} checked={opaque}
@@ -4,13 +4,13 @@ import { Euler, Group, Vector3 } from 'three';
import { degToRad } from 'three/src/math/MathUtils'; import { degToRad } from 'three/src/math/MathUtils';
import { I_HAT, J_HAT, K_HAT } from '../../../../../../../constants/axis'; import { I_HAT, J_HAT, K_HAT } from '../../../../../../../constants/axis';
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits'; import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
import { hasEquipment } from '../../../../../../../core/blitzkrieg/hasEquipment'; import { hasEquipment } from '../../../../../../../core/blitzkit/hasEquipment';
import { jsxTree } from '../../../../../../../core/blitzkrieg/jsxTree'; import { jsxTree } from '../../../../../../../core/blitzkit/jsxTree';
import { modelDefinitions } from '../../../../../../../core/blitzkrieg/modelDefinitions'; import { modelDefinitions } from '../../../../../../../core/blitzkit/modelDefinitions';
import { import {
ModelTransformEventData, ModelTransformEventData,
modelTransformEvent, modelTransformEvent,
} from '../../../../../../../core/blitzkrieg/modelTransform'; } from '../../../../../../../core/blitzkit/modelTransform';
import { normalizeAngleRad } from '../../../../../../../core/math/normalizeAngleRad'; import { normalizeAngleRad } from '../../../../../../../core/math/normalizeAngleRad';
import { useAwait } from '../../../../../../../hooks/useAwait'; import { useAwait } from '../../../../../../../hooks/useAwait';
import { useModel } from '../../../../../../../hooks/useModel'; import { useModel } from '../../../../../../../hooks/useModel';
@@ -124,11 +124,15 @@ export const TankModel = memo(() => {
event.stopPropagation(); event.stopPropagation();
} }
return jsxTree(node, { return jsxTree(
castShadow: true, node,
receiveShadow: true, {
onPointerDown, castShadow: true,
}); receiveShadow: true,
onPointerDown,
},
node.uuid,
);
})} })}
<group ref={turretContainer}> <group ref={turretContainer}>
@@ -189,11 +193,15 @@ export const TankModel = memo(() => {
window.removeEventListener('pointerup', handlePointerUp); window.removeEventListener('pointerup', handlePointerUp);
} }
return jsxTree(node, { return jsxTree(
castShadow: true, node,
receiveShadow: true, {
onPointerDown, castShadow: true,
}); receiveShadow: true,
onPointerDown,
},
node.uuid,
);
})} })}
<group ref={gunContainer}> <group ref={gunContainer}>
@@ -255,11 +263,15 @@ export const TankModel = memo(() => {
window.removeEventListener('pointerup', handlePointerUp); window.removeEventListener('pointerup', handlePointerUp);
} }
return jsxTree(node, { return jsxTree(
castShadow: true, node,
receiveShadow: true, {
onPointerDown, castShadow: true,
}); receiveShadow: true,
onPointerDown,
},
node.uuid,
);
})} })}
</group> </group>
</group> </group>
@@ -1,6 +1,6 @@
import { Button } from '@radix-ui/themes'; import { Button } from '@radix-ui/themes';
import { ComponentProps } from 'react'; import { ComponentProps } from 'react';
import { asset } from '../../../../../../../core/blitzkrieg/asset'; import { asset } from '../../../../../../../core/blitzkit/asset';
interface QuickEquipmentButtonProps extends ComponentProps<typeof Button> { interface QuickEquipmentButtonProps extends ComponentProps<typeof Button> {
equipment: number; equipment: number;
@@ -2,8 +2,8 @@ import { Flex, TextField } from '@radix-ui/themes';
import { use, useEffect, useRef } from 'react'; import { use, useEffect, useRef } from 'react';
import { degToRad, radToDeg } from 'three/src/math/MathUtils'; import { degToRad, radToDeg } from 'three/src/math/MathUtils';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits'; import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions'; import { modelDefinitions } from '../../../../../../core/blitzkit/modelDefinitions';
import { modelTransformEvent } from '../../../../../../core/blitzkrieg/modelTransform'; import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
import { useEquipment } from '../../../../../../hooks/useEquipment'; import { useEquipment } from '../../../../../../hooks/useEquipment';
import { mutateDuel, useDuel } from '../../../../../../stores/duel'; import { mutateDuel, useDuel } from '../../../../../../stores/duel';
import { useTankopediaPersistent } from '../../../../../../stores/tankopedia'; import { useTankopediaPersistent } from '../../../../../../stores/tankopedia';
@@ -1,19 +1,31 @@
import { UpdateIcon } from '@radix-ui/react-icons'; import { CaretRightIcon, UpdateIcon } from '@radix-ui/react-icons';
import { Button, Dialog, Flex, Heading } from '@radix-ui/themes'; import { Button, Dialog, Flex, Heading } from '@radix-ui/themes';
import Link from 'next/link';
import { useState } from 'react'; import { useState } from 'react';
import { TREE_TYPE_ICONS } from '../../../../../components/Tanks/components/Item/constants'; import { TREE_TYPE_ICONS } from '../../../../../components/Tanks/components/Item/constants';
import { assignDuelMember } from '../../../../../core/blitzkrieg/assignDuelMember'; import { assignDuelMember } from '../../../../../core/blitzkit/assignDuelMember';
import { pushTankopediaPath } from '../../../../../core/blitzkrieg/pushTankopediaPath'; import { pushTankopediaPath } from '../../../../../core/blitzkit/pushTankopediaPath';
import { useDuel } from '../../../../../stores/duel'; import { useDuel } from '../../../../../stores/duel';
import { TankSearch } from '../../components/TankSearch'; import { TankSearch } from '../../components/TankSearch';
export function Title() { export function Title() {
const protagonist = useDuel((state) => state.protagonist!); const protagonist = useDuel((state) => state.protagonist!);
const antagonist = useDuel((state) => state.antagonist!);
const compareTanks =
protagonist.tank.id === antagonist.tank.id
? [protagonist.tank.id]
: [protagonist.tank.id, antagonist.tank.id];
const [changeTankDialogOpen, setChangeTankDialogOpen] = useState(false); const [changeTankDialogOpen, setChangeTankDialogOpen] = useState(false);
return ( return (
<> <>
<Flex gap="6" justify="center" align="center" style={{ padding: 16 }}> <Flex
gap="6"
justify="center"
align="center"
style={{ padding: 16 }}
wrap="wrap"
>
<Flex gap="2" align="center"> <Flex gap="2" align="center">
<Heading <Heading
color={ color={
@@ -41,36 +53,44 @@ export function Title() {
</Heading> </Heading>
</Flex> </Flex>
<Dialog.Root <Flex gap="6">
open={changeTankDialogOpen} <Dialog.Root
onOpenChange={setChangeTankDialogOpen} open={changeTankDialogOpen}
> onOpenChange={setChangeTankDialogOpen}
<Dialog.Trigger> >
<Button variant="ghost"> <Dialog.Trigger>
Change <UpdateIcon /> <Button variant="ghost">
</Button> Change <UpdateIcon />
</Dialog.Trigger> </Button>
</Dialog.Trigger>
<Dialog.Content> <Dialog.Content>
<Flex gap="4" direction="column"> <Flex gap="4" direction="column">
<Flex <Flex
direction="column" direction="column"
gap="4" gap="4"
style={{ flex: 1 }} style={{ flex: 1 }}
justify="center" justify="center"
> >
<TankSearch <TankSearch
compact compact
onSelect={(tank) => { onSelect={(tank) => {
assignDuelMember('protagonist', tank.id); assignDuelMember('protagonist', tank.id);
setChangeTankDialogOpen(false); setChangeTankDialogOpen(false);
pushTankopediaPath(tank.id); pushTankopediaPath(tank.id);
}} }}
/> />
</Flex>
</Flex> </Flex>
</Flex> </Dialog.Content>
</Dialog.Content> </Dialog.Root>
</Dialog.Root>
<Link href={`/tools/compare?tanks=${compareTanks.join('%2C')}`}>
<Button variant="ghost">
Compare <CaretRightIcon />
</Button>
</Link>
</Flex>
</Flex> </Flex>
</> </>
); );
@@ -0,0 +1,64 @@
import { PlusIcon } from '@radix-ui/react-icons';
import { Card, Flex, Heading, Link, Text } from '@radix-ui/themes';
import { use } from 'react';
import { videoDefinitions } from '../../../../../core/blitzkit/videos';
import { useFullScreen } from '../../../../../hooks/useFullScreen';
import { useDuel } from '../../../../../stores/duel';
export function Videos() {
const isFullScreen = useFullScreen();
const tank = useDuel((state) => state.protagonist!.tank);
const awaitedVideoDefinitions = use(videoDefinitions);
const videos = awaitedVideoDefinitions[tank.id]?.videos ?? [];
if (isFullScreen) return null;
return (
<Flex direction="column" gap="4" mt="4" align="start">
<Heading size="6">Review videos</Heading>
<Flex gap="3" wrap="wrap">
{videos.map((video) => (
<Link
key={video}
href={`https://www.youtube.com/watch?v=${video}`}
target="_blank"
>
<img
src={`https://i.ytimg.com/vi/${video}/hqdefault.jpg`}
style={{
aspectRatio: '16 / 9',
objectFit: 'cover',
height: 128,
borderRadius: 8,
}}
/>
</Link>
))}
<Link href="https://discord.gg/nDt7AjGJQH" target="_blank">
<Card
style={{
aspectRatio: '16 / 9',
height: 128,
}}
>
<Flex
align="center"
justify="center"
gap="1"
style={{
width: '100%',
height: '100%',
}}
>
<Text color="purple">
{videos.length === 0 ? 'Suggest' : 'Suggest more'} <PlusIcon />
</Text>
</Flex>
</Card>
</Link>
</Flex>
</Flex>
);
}
+12 -7
View File
@@ -1,6 +1,8 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { tankDefinitions } from '../../../../core/blitzkrieg/tankDefinitions'; import { tankDefinitions } from '../../../../core/blitzkit/tankDefinitions';
import { tankIcon } from '../../../../core/blitzkrieg/tankIcon'; import { TIER_ROMAN_NUMERALS } from '../../../../core/blitzkit/tankDefinitions/constants';
import { tankIcon } from '../../../../core/blitzkit/tankIcon';
import strings from '../../../../lang/en-US.json';
interface TankopediaLayoutProps { interface TankopediaLayoutProps {
children: ReactNode; children: ReactNode;
@@ -14,15 +16,18 @@ export default async function TankopediaLayout({
const id = Number(params.id); const id = Number(params.id);
const awaitedTankDefinitions = await tankDefinitions; const awaitedTankDefinitions = await tankDefinitions;
const tank = awaitedTankDefinitions[id]; const tank = awaitedTankDefinitions[id];
const title = `${tank.name} - Tier ${TIER_ROMAN_NUMERALS[tank.tier]} ${
(strings.common.nations as Record<string, string>)[tank.nation]
} ${strings.common.tank_class_short[tank.class]}`;
const description = `Statistics, armor profiles, and equipment for ${tank.name}`;
return ( return (
<> <>
<title>{tank.name}</title> <title>{title}</title>
<meta <meta name="description" content={description} />
name="description" <meta property="og:title" content={title} />
content={`Statistics, armor profiles, equipment, and more about ${tank.name}`}
/>
<meta property="og:image" content={tankIcon(id)} /> <meta property="og:image" content={tankIcon(id)} />
<meta property="og:description" content={description} />
{children} {children}
</> </>
+5 -3
View File
@@ -3,7 +3,7 @@
import { Flex } from '@radix-ui/themes'; import { Flex } from '@radix-ui/themes';
import { useEffect } from 'react'; import { useEffect } from 'react';
import PageWrapper from '../../../../components/PageWrapper'; import PageWrapper from '../../../../components/PageWrapper';
import { assignDuelMember } from '../../../../core/blitzkrieg/assignDuelMember'; import { assignDuelMember } from '../../../../core/blitzkit/assignDuelMember';
import { useWideFormat } from '../../../../hooks/useWideFormat'; import { useWideFormat } from '../../../../hooks/useWideFormat';
import { mutateDuel, useDuel } from '../../../../stores/duel'; import { mutateDuel, useDuel } from '../../../../stores/duel';
import { mutateTankopediaTemporary } from '../../../../stores/tankopedia'; import { mutateTankopediaTemporary } from '../../../../stores/tankopedia';
@@ -17,6 +17,7 @@ import { Provisions } from './components/Characteristics/components/Provisions';
import { Skills } from './components/Characteristics/components/Skills'; import { Skills } from './components/Characteristics/components/Skills';
import { TankSandbox } from './components/Model/TankSandbox'; import { TankSandbox } from './components/Model/TankSandbox';
import { Title } from './components/Title'; import { Title } from './components/Title';
import { Videos } from './components/Videos';
export default function Page({ params }: { params: { id: string } }) { export default function Page({ params }: { params: { id: string } }) {
const initialId = parseInt(params.id); const initialId = parseInt(params.id);
@@ -83,7 +84,7 @@ export default function Page({ params }: { params: { id: string } }) {
style={{ style={{
flex: 1, flex: 1,
width: '100%', width: '100%',
top: 64, top: 64 + 16,
position: wideFormat ? 'sticky' : undefined, position: wideFormat ? 'sticky' : undefined,
}} }}
> >
@@ -91,7 +92,6 @@ export default function Page({ params }: { params: { id: string } }) {
<TankSandbox /> <TankSandbox />
<AntagonistBar /> <AntagonistBar />
</Flex> </Flex>
<Flex gap="5" wrap="wrap"> <Flex gap="5" wrap="wrap">
<Modules /> <Modules />
<Provisions /> <Provisions />
@@ -110,6 +110,8 @@ export default function Page({ params }: { params: { id: string } }) {
<Characteristics /> <Characteristics />
</Flex> </Flex>
</Flex> </Flex>
<Videos />
</> </>
)} )}
</PageWrapper> </PageWrapper>
@@ -1,7 +1,7 @@
import { CaretLeftIcon, CaretRightIcon } from '@radix-ui/react-icons'; import { CaretLeftIcon, CaretRightIcon } from '@radix-ui/react-icons';
import { Button, Flex, TextField } from '@radix-ui/themes'; import { Button, Flex, TextField } from '@radix-ui/themes';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { TankDefinition } from '../../../../core/blitzkrieg/tankDefinitions'; import { TankDefinition } from '../../../../core/blitzkit/tankDefinitions';
import mutateTankopediaPersistent, { import mutateTankopediaPersistent, {
useTankopediaPersistent, useTankopediaPersistent,
} from '../../../../stores/tankopedia'; } from '../../../../stores/tankopedia';
@@ -1,7 +1,7 @@
import { Button, Flex, Text } from '@radix-ui/themes'; import { Button, Flex, Text } from '@radix-ui/themes';
import { SmallTankIcon } from '../../../../../../components/SmallTankIcon'; import { SmallTankIcon } from '../../../../../../components/SmallTankIcon';
import { TankDefinition } from '../../../../../../core/blitzkrieg/tankDefinitions'; import { TankDefinition } from '../../../../../../core/blitzkit/tankDefinitions';
import { TIER_ROMAN_NUMERALS } from '../../../../../../core/blitzkrieg/tankDefinitions/constants'; import { TIER_ROMAN_NUMERALS } from '../../../../../../core/blitzkit/tankDefinitions/constants';
interface CompactSearchResultRowProps { interface CompactSearchResultRowProps {
tanks: TankDefinition[]; tanks: TankDefinition[];
@@ -11,11 +11,11 @@ import {
TREE_TYPE_ICONS, TREE_TYPE_ICONS,
TREE_TYPE_IMAGES, TREE_TYPE_IMAGES,
} from '../../../../../../../components/Tanks/components/Item/constants'; } from '../../../../../../../components/Tanks/components/Item/constants';
import { NATIONS } from '../../../../../../../core/blitzkrieg/tankDefinitions'; import { NATIONS } from '../../../../../../../core/blitzkit/tankDefinitions';
import { import {
TIERS, TIERS,
TIER_ROMAN_NUMERALS, TIER_ROMAN_NUMERALS,
} from '../../../../../../../core/blitzkrieg/tankDefinitions/constants'; } from '../../../../../../../core/blitzkit/tankDefinitions/constants';
import mutateTankopediaPersistent, { import mutateTankopediaPersistent, {
TankopediaTestTankDisplay, TankopediaTestTankDisplay,
useTankopediaPersistent, useTankopediaPersistent,
@@ -28,32 +28,6 @@ export function Options() {
return ( return (
<Flex gap="2" wrap="wrap" justify="center"> <Flex gap="2" wrap="wrap" justify="center">
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="soft" color="gray">
Test tanks
<CaretDownIcon />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.RadioGroup
value={filters.test}
onValueChange={(value) =>
mutateTankopediaPersistent((draft) => {
draft.filters.test = value as TankopediaTestTankDisplay;
})
}
>
<DropdownMenu.RadioItem value="include">
Include
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="exclude">
Exclude
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="only">Only</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.Content>
</DropdownMenu.Root>
<Flex> <Flex>
{TREE_TYPES.map((type, index) => ( {TREE_TYPES.map((type, index) => (
<Button <Button
@@ -334,6 +308,32 @@ export function Options() {
))} ))}
</Flex> </Flex>
</Flex> </Flex>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<Button variant="soft" color="gray">
Test tanks
<CaretDownIcon />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.RadioGroup
value={filters.test}
onValueChange={(value) =>
mutateTankopediaPersistent((draft) => {
draft.filters.test = value as TankopediaTestTankDisplay;
})
}
>
<DropdownMenu.RadioItem value="include">
Include
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="exclude">
Exclude
</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="only">Only</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.Content>
</DropdownMenu.Root>
</Flex> </Flex>
); );
} }
@@ -21,24 +21,24 @@ import { use, useEffect, useMemo, useRef, useState } from 'react';
import { TreeType } from '../../../../../components/Tanks'; import { TreeType } from '../../../../../components/Tanks';
import { TANK_CLASSES } from '../../../../../components/Tanks/components/Item/constants'; import { TANK_CLASSES } from '../../../../../components/Tanks/components/Item/constants';
import { resolveNearPenetration } from '../../../../../core/blitz/resolveNearPenetration'; import { resolveNearPenetration } from '../../../../../core/blitz/resolveNearPenetration';
import { asset } from '../../../../../core/blitzkrieg/asset'; import { asset } from '../../../../../core/blitzkit/asset';
import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions'; import { modelDefinitions } from '../../../../../core/blitzkit/modelDefinitions';
import { normalizeBoundingBox } from '../../../../../core/blitzkrieg/normalizeBoundingBox'; import { normalizeBoundingBox } from '../../../../../core/blitzkit/normalizeBoundingBox';
import { resolveDpm } from '../../../../../core/blitzkrieg/resolveDpm'; import { resolveDpm } from '../../../../../core/blitzkit/resolveDpm';
import { resolveReload } from '../../../../../core/blitzkrieg/resolveReload'; import { resolveReload } from '../../../../../core/blitzkit/resolveReload';
import { import {
NATIONS, NATIONS,
TankDefinition, TankDefinition,
tanksDefinitionsArray, tanksDefinitionsArray,
} from '../../../../../core/blitzkrieg/tankDefinitions'; } from '../../../../../core/blitzkit/tankDefinitions';
import { import {
TANK_ICONS, TANK_ICONS,
TANK_ICONS_COLLECTOR, TANK_ICONS_COLLECTOR,
TANK_ICONS_PREMIUM, TANK_ICONS_PREMIUM,
TIER_ROMAN_NUMERALS, TIER_ROMAN_NUMERALS,
} from '../../../../../core/blitzkrieg/tankDefinitions/constants'; } from '../../../../../core/blitzkit/tankDefinitions/constants';
import { tankIcon } from '../../../../../core/blitzkrieg/tankIcon'; import { tankIcon } from '../../../../../core/blitzkit/tankIcon';
import { unionBoundingBox } from '../../../../../core/blitzkrieg/unionBoundingBox'; import { unionBoundingBox } from '../../../../../core/blitzkit/unionBoundingBox';
import { theme } from '../../../../../stitches.config'; import { theme } from '../../../../../stitches.config';
import mutateTankopediaPersistent, { import mutateTankopediaPersistent, {
SORT_NAMES, SORT_NAMES,
+29
View File
@@ -0,0 +1,29 @@
'use client';
import { Flex, Link } from '@radix-ui/themes';
import { use } from 'react';
import { AdHorizontal } from '../../../../components/Ads/Horizontal';
import PageWrapper from '../../../../components/PageWrapper';
import { tankDefinitions } from '../../../../core/blitzkit/tankDefinitions';
export default function Page() {
const awaitedTankDefinitions = use(tankDefinitions);
return (
<PageWrapper>
<title>Super duper secret map for SEO</title>
<AdHorizontal />
<Flex direction="column" align="center">
{Object.values(awaitedTankDefinitions)
.sort((a, b) => b.tier - a.tier)
.map((tank) => (
<Link href={`./${tank.id}`} key={tank.id}>
{tank.name}
</Link>
))}
</Flex>
</PageWrapper>
);
}
+6
View File
@@ -1,5 +1,7 @@
'use client'; 'use client';
import { Flex } from '@radix-ui/themes';
import Link from 'next/link';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import PageWrapper from '../../../components/PageWrapper'; import PageWrapper from '../../../components/PageWrapper';
import { TankSearch } from './components/TankSearch'; import { TankSearch } from './components/TankSearch';
@@ -14,6 +16,10 @@ export default function Page() {
router.push(`./tankopedia/${tank.id}`); router.push(`./tankopedia/${tank.id}`);
}} }}
/> />
<Flex justify="center" mt="4">
<Link href="/tools/tankopedia/map">Super duper secret map</Link>
</Flex>
</PageWrapper> </PageWrapper>
); );
} }
+2 -2
View File
@@ -1,5 +1,5 @@
import { registerProcesses } from './core/blitzkrieg/processes'; import { registerProcesses } from './core/blitzkit/processes';
import { secrets } from './core/blitzkrieg/secrets'; import { secrets } from './core/blitzkit/secrets';
import { client } from './core/discord/client'; import { client } from './core/discord/client';
// console.log(`🟡 Launching bot ${client.shard?.ids[0]}`); // console.log(`🟡 Launching bot ${client.shard?.ids[0]}`);
+1 -1
View File
@@ -60,7 +60,7 @@ export const aboutCommand = new Promise<CommandRegistry>(async (resolve) => {
.join('\n')}`; .join('\n')}`;
} }
const url = `https://raw.githubusercontent.com/tresabhi/blitzkrieg/main/docs/${DOCS[subcommand as Subcommand]}.md`; const url = `https://raw.githubusercontent.com/tresabhi/blitzkit/main/docs/${DOCS[subcommand as Subcommand]}.md`;
const content = fetch(url).then((response) => response.text()); const content = fetch(url).then((response) => response.text());
return content; return content;
+8 -3
View File
@@ -7,11 +7,11 @@ import TitleBar from '../../components/TitleBar';
import { AllStats, getAccountInfo } from '../../core/blitz/getAccountInfo'; import { AllStats, getAccountInfo } from '../../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../../core/blitz/getClanAccountInfo'; import { getClanAccountInfo } from '../../core/blitz/getClanAccountInfo';
import getTankStats from '../../core/blitz/getTankStats'; import getTankStats from '../../core/blitz/getTankStats';
import { filtersToDescription } from '../../core/blitzkrieg/filtersToDescription'; import { filtersToDescription } from '../../core/blitzkit/filtersToDescription';
import { import {
TankDefinition, TankDefinition,
tankDefinitions, tankDefinitions,
} from '../../core/blitzkrieg/tankDefinitions'; } from '../../core/blitzkit/tankDefinitions';
import getStatsInPeriod from '../../core/blitzstars/getStatsInPeriod'; import getStatsInPeriod from '../../core/blitzstars/getStatsInPeriod';
import { tankAverages } from '../../core/blitzstars/tankAverages'; import { tankAverages } from '../../core/blitzstars/tankAverages';
import { ResolvedPeriod } from '../../core/discord/resolvePeriodFromCommand'; import { ResolvedPeriod } from '../../core/discord/resolvePeriodFromCommand';
@@ -20,6 +20,7 @@ import { translator } from '../../core/localization/translator';
import calculateWN8 from '../../core/statistics/calculateWN8'; import calculateWN8 from '../../core/statistics/calculateWN8';
import { StatFilters, filterStats } from '../../core/statistics/filterStats'; import { StatFilters, filterStats } from '../../core/statistics/filterStats';
import getWN8Percentile from '../../core/statistics/getWN8Percentile'; import getWN8Percentile from '../../core/statistics/getWN8Percentile';
import { UserError } from '../../hooks/userError';
const ROWS_PER_PAGE = 8; const ROWS_PER_PAGE = 8;
const MAX_PAGES = 9; const MAX_PAGES = 9;
@@ -37,7 +38,7 @@ export async function renderBreakdown(
const { filteredOrder } = await filterStats(statsInPeriod, filters); const { filteredOrder } = await filterStats(statsInPeriod, filters);
const accountInfo = await getAccountInfo(region, id); const accountInfo = await getAccountInfo(region, id);
const clanData = await getClanAccountInfo(region, id, ['clan']); const clanData = await getClanAccountInfo(region, id, ['clan']);
const tankStats = await getTankStats(region, id, locale); const tankStats = await getTankStats(region, id);
const filterDescriptions = await filtersToDescription(filters, locale); const filterDescriptions = await filtersToDescription(filters, locale);
const orderedCurrentStats: AllStats[] = []; const orderedCurrentStats: AllStats[] = [];
const orderedCareerStats: AllStats[] = []; const orderedCareerStats: AllStats[] = [];
@@ -45,6 +46,10 @@ export async function renderBreakdown(
const orderedCareerWN8: (number | undefined)[] = []; const orderedCareerWN8: (number | undefined)[] = [];
const orderedCareerWN8Full: (number | undefined)[] = []; const orderedCareerWN8Full: (number | undefined)[] = [];
if (tankStats === null) {
throw new UserError(t`bot.common.errors.no_tank_stats`);
}
filteredOrder.forEach((id) => { filteredOrder.forEach((id) => {
const tankAveragesEntry = awaitedTankAverages[id]; const tankAveragesEntry = awaitedTankAverages[id];
const current = statsInPeriod.diff[id]; const current = statsInPeriod.diff[id];
+1 -1
View File
@@ -84,7 +84,7 @@
// embedWarning( // embedWarning(
// 'This is an approximation!', // 'This is an approximation!',
// "Wargaming provides very little information about platoons publicly. Caveats:\n- Players in two different clans platooning artificially inflates the total count\n- Game-mode battles aren't counted since Wargaming doesn't publicly share that data\n- This command is also subject to Blitzkrieg's reset times (use `/about timezones` for more info)", // "Wargaming provides very little information about platoons publicly. Caveats:\n- Players in two different clans platooning artificially inflates the total count\n- Game-mode battles aren't counted since Wargaming doesn't publicly share that data\n- This command is also subject to Blitzkit's reset times (use `/about timezones` for more info)",
// ), // ),
// ]; // ];
// }, // },
+3 -3
View File
@@ -7,9 +7,9 @@
// import { SlashCommandBuilder } from 'discord.js'; // import { SlashCommandBuilder } from 'discord.js';
// import { getAccountInfo } from '../core/blitz/getAccountInfo'; // import { getAccountInfo } from '../core/blitz/getAccountInfo';
// import getPeriodNow from '../core/blitzkrieg/getPeriodNow'; // import getPeriodNow from '../core/blitzkit/getPeriodNow';
// import getTimeDaysAgo from '../core/blitzkrieg/getTimeDaysAgo'; // import getTimeDaysAgo from '../core/blitzkit/getTimeDaysAgo';
// import { tankDefinitions } from '../core/blitzkrieg/tankDefinitions'; // import { tankDefinitions } from '../core/blitzkit/tankDefinitions';
// import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod'; // import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
// import { tankAverages } from '../core/blitzstars/tankAverages'; // import { tankAverages } from '../core/blitzstars/tankAverages';
// import addUsernameChoices from '../core/discord/addUsernameChoices'; // import addUsernameChoices from '../core/discord/addUsernameChoices';
+2 -2
View File
@@ -7,8 +7,8 @@ import TitleBar from '../components/TitleBar';
import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import resolveTankId from '../core/blitz/resolveTankId'; import resolveTankId from '../core/blitz/resolveTankId';
import { emblemURL } from '../core/blitzkrieg/emblemURL'; import { emblemURL } from '../core/blitzkit/emblemURL';
import getTankHistories from '../core/blitzkrieg/getTankHistories'; import getTankHistories from '../core/blitzkit/getTankHistories';
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton'; import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import getPlayerHistories from '../core/blitzstars/getPlayerHistories'; import getPlayerHistories from '../core/blitzstars/getPlayerHistories';
import { addPeriodSubCommands } from '../core/discord/addPeriodSubCommands'; import { addPeriodSubCommands } from '../core/discord/addPeriodSubCommands';
Loaded 100 of 299 files, more files were not shown because too many files have changed in this diff. Show more