Compare commits

..
194 Commits
Author SHA1 Message Date
tresabhi bac29d843c remove path 2026-10-26 09:55:22 -05:00
tresabhi 5807473fb6 Merge branch 'main' into data-driven 2026-10-26 09:50:43 -05:00
TrèsAbhi 091607b0a8 Add Beta option to bug report issue template 2026-10-25 21:53:59 -05:00
tresabhi 4d45029bfa imports 2026-10-25 18:52:41 -05:00
tresabhi 89d272166f Merge branch 'main' into data-driven 2026-10-25 18:49:49 -05:00
TrèsAbhi 590f4a5df3 Merge pull request #407 from blitzkit/main-i18n
New Crowdin updates
2026-10-25 18:41:43 -05:00
TrèsAbhi e326985ec4 Merge pull request #411 from blitzkit/beg 2026-10-25 18:40:34 -05:00
TrèsAbhi dcd77d5666 Merge branch 'main' into beg 2026-10-25 18:38:48 -05:00
TrèsAbhi d45425ec57 Merge pull request #414 from blitzkit:simplify-navbar
simplify
2026-10-25 18:37:52 -05:00
tresabhi 46fd061aff simplify 2026-10-25 18:37:28 -05:00
TrèsAbhi 752b11e56d Merge pull request #413 from Hitjack007/legendary-camos-fix
Fix legendary camos
2026-10-25 18:25:40 -05:00
Mark Greene 049ef58691 Fix legendary camos
Fixed https://github.com/blitzkit/blitzkit/commit/b540ab781419cbaa693597bf89a2f62962d8295f @tresabhi
2026-10-26 12:22:51 +13:00
TrèsAbhi bb64d718c2 Merge branch 'main' into beg 2026-10-25 18:11:37 -05:00
tresabhi 0d6c508382 remove skins from api 2026-10-25 15:00:17 -05:00
tresabhi 7f77b4006d shit ass wargaming legacy systems 2026-10-25 13:31:12 -05:00
tresabhi c11285e985 exit code retry 2026-10-25 13:03:35 -05:00
tresabhi a4ee9cf2bd automatic url 2026-10-25 12:50:48 -05:00
tresabhi b3ad122542 fix CLIENT_PROVIDER 2026-10-25 12:36:39 -05:00
tresabhi 41fb822571 apply smart dlc 2026-10-25 12:27:57 -05:00
tresabhi 9236c54b4b rgba8888 2026-10-24 21:29:45 -05:00
tresabhi 92ef7f4020 force bun 2026-10-24 21:18:26 -05:00
TrèsAbhi 96c8d3d801 New translations en.json (Russian)
[ci skip]
2026-10-24 20:34:55 -05:00
TrèsAbhi 2cdbd0b810 New translations en.json (Portuguese)
[ci skip]
2026-10-24 20:34:54 -05:00
TrèsAbhi 61a4711029 New translations en.json (Polish)
[ci skip]
2026-10-24 20:34:52 -05:00
TrèsAbhi f04cfb8875 New translations en.json (Japanese)
[ci skip]
2026-10-24 20:34:51 -05:00
TrèsAbhi 7ea6032244 New translations en.json (Spanish)
[ci skip]
2026-10-24 20:34:49 -05:00
TrèsAbhi a1bc1bc630 New translations en.json (French)
[ci skip]
2026-10-24 20:34:48 -05:00
TrèsAbhi 34b96f4003 New translations en.json (Chinese Simplified)
[ci skip]
2026-10-24 20:34:46 -05:00
TrèsAbhi 3f4306b3b3 New translations en.json (Ukrainian)
[ci skip]
2026-10-24 20:34:45 -05:00
tresabhi 40b732803e retry time 2026-10-24 20:32:34 -05:00
TrèsAbhi ac673cd47f Merge pull request #392 from blitzkit/new-credits 2026-10-24 20:21:58 -05:00
tresabhi 76445c67b2 some updates 2026-10-24 20:21:07 -05:00
tresabhi 8f399e5c54 Merge branch 'main' into new-credits 2026-10-24 20:19:15 -05:00
TrèsAbhi 14dc40a3e7 New translations en.json (Ukrainian)
[ci skip]
2026-10-24 15:30:30 -05:00
TrèsAbhi 3daa56e100 New translations en.json (Polish)
[ci skip]
2026-10-23 09:57:28 -05:00
TrèsAbhi 37c257c8c4 New translations en.json (Russian)
[ci skip]
2026-10-22 17:04:11 -05:00
TrèsAbhi dfc6a2bada New translations en.json (Portuguese)
[ci skip]
2026-10-22 17:04:08 -05:00
TrèsAbhi 9b8b06ca70 New translations en.json (Polish)
[ci skip]
2026-10-22 17:04:06 -05:00
TrèsAbhi f7ffb2d34d New translations en.json (Japanese)
[ci skip]
2026-10-22 17:04:04 -05:00
TrèsAbhi 2b7e88e30c New translations en.json (Spanish)
[ci skip]
2026-10-22 17:04:00 -05:00
TrèsAbhi af172c95f2 New translations en.json (French)
[ci skip]
2026-10-22 17:03:59 -05:00
TrèsAbhi f8794de782 New translations en.json (Chinese Simplified)
[ci skip]
2026-10-22 17:03:57 -05:00
TrèsAbhi 5fe295b209 New translations en.json (Ukrainian)
[ci skip]
2026-10-22 17:03:55 -05:00
tresabhi 41418eb8e0 restrict branches 2026-10-22 17:03:05 -05:00
tresabhi f6f8e2b4df polish 2026-10-22 17:00:13 -05:00
tresabhi a4fca4085e dedicated tool 2026-10-22 16:49:03 -05:00
tresabhi 26f71df9f9 remove some... people... 2026-10-22 16:40:11 -05:00
tresabhi d32b8cc3f5 Merge branch 'main' into new-credits 2026-10-22 16:38:40 -05:00
tresabhi 0922614dbc finalize 2026-10-22 16:37:16 -05:00
tresabhi ae4b2e1bb3 Merge branch 'main' into beg 2026-10-22 16:25:11 -05:00
TrèsAbhi 3b75d7b523 New translations en.json (Russian)
[ci skip]
2026-10-22 15:56:13 -05:00
TrèsAbhi 0cbd67ff38 New translations en.json (Portuguese)
[ci skip]
2026-10-22 15:56:11 -05:00
TrèsAbhi 068f481b93 New translations en.json (Polish)
[ci skip]
2026-10-22 15:56:10 -05:00
TrèsAbhi 1430c32440 New translations en.json (Japanese)
[ci skip]
2026-10-22 15:56:08 -05:00
TrèsAbhi d14afc8b39 New translations en.json (Spanish)
[ci skip]
2026-10-22 15:56:07 -05:00
TrèsAbhi cba591b368 New translations en.json (French)
[ci skip]
2026-10-22 15:56:05 -05:00
TrèsAbhi 42b5ab6f02 New translations en.json (Chinese Simplified)
[ci skip]
2026-10-22 15:56:04 -05:00
TrèsAbhi c1363be70a New translations en.json (Ukrainian)
[ci skip]
2026-10-22 15:56:02 -05:00
tresabhi 72e358f034 add pr checks 2026-10-22 15:45:04 -05:00
tresabhi b4a689dfbb start adding tiers 2026-10-22 15:42:07 -05:00
tresabhi 19e535969e colors 2026-10-22 11:51:08 -05:00
tresabhi eebc373b46 better 2026-10-22 11:45:18 -05:00
tresabhi ecb1a7c21e base 2026-10-22 11:30:03 -05:00
tresabhi 625d8d1595 cleaner logic 2026-10-22 09:13:57 -05:00
TrèsAbhi 602bf37411 New translations en.json (Russian)
[ci skip]
2026-10-22 05:51:52 -05:00
tresabhi fc9050e7fe fix imports 2026-10-21 23:23:55 -05:00
tresabhi 654887dd66 versio 2026-10-21 23:06:18 -05:00
tresabhi b540ab7814 sk 2026-10-21 22:59:12 -05:00
tresabhi 0ee9c99a28 move tank models to models_tanks chunk 2026-10-21 22:56:40 -05:00
tresabhi ab9149a11f git keep 2026-10-21 22:48:33 -05:00
tresabhi 924c130516 git keep 2026-10-21 22:47:01 -05:00
tresabhi 33366a8f79 Merge branch 'main' into split-branches 2026-10-21 22:44:54 -05:00
TrèsAbhi 342497563f Merge pull request #373 from Hitjack007/legendary-camo-models 2026-10-21 22:42:53 -05:00
Mark Greene a495a5f208 Feature: Legendary camouflages in the Tankopedia viewer 2026-10-22 16:12:03 +13:00
TrèsAbhi 10b0e29a26 New translations en.json (Polish)
[ci skip]
2026-10-21 10:03:16 -05:00
tresabhi 007d4e9540 make tier 11's test tanks 2026-10-21 09:56:21 -05:00
tresabhi 273fea1599 promote preview 2026-10-21 09:36:06 -05:00
TrèsAbhi ed36eedbfb New translations en.json (Polish)
[ci skip]
2026-10-21 07:42:08 -05:00
tresabhi 7df53d845d vfs debug 2026-10-19 20:12:03 -05:00
tresabhi 1b5c79c2b8 Merge branch 'production' 2026-10-19 19:54:58 -05:00
tresabhi 51900f4c80 countdown 2026-10-19 19:22:20 -05:00
tresabhi b9388cc544 add basic xi compatibility 2026-10-19 19:08:25 -05:00
TrèsAbhi 05648ea040 Merge pull request #353 from Hitjack007/remove-imgur-references
Fix: migrate Imgur images to BlitzKit hosting
2026-10-18 14:12:50 -05:00
TrèsAbhi 73080fd60a Merge branch 'main' into remove-imgur-references 2026-10-18 14:03:19 -05:00
TrèsAbhi 14be326c41 Merge pull request #368 from Hitjack007/calculated-bounding-boxes 2026-10-18 14:02:34 -05:00
TrèsAbhi 8132f2d91e Merge branch 'main' into calculated-bounding-boxes 2026-10-18 13:59:49 -05:00
TrèsAbhi 1258cd1fa2 New Crowdin updates (#399) 2026-10-18 13:59:07 -05:00
tresabhi b00a63a9f8 sorting 2026-10-15 00:38:01 -05:00
TrèsAbhi 989e3d395a Merge pull request #385 from blitzkit/main
Production
2026-10-14 13:15:55 -05:00
tresabhi 8ec7fcb90a ui shit 2026-10-13 21:14:42 -05:00
tresabhi 830e1597e7 revive sandbox 2026-10-13 21:03:06 -05:00
tresabhi 3131217600 custom scroller for loadout 2026-10-13 20:53:15 -05:00
tresabhi 0b740e3f60 port scroller 2026-10-13 20:40:47 -05:00
tresabhi 1416d64e34 add skills 2026-10-13 20:37:53 -05:00
tresabhi bc6257a0c3 reloads 2026-10-12 10:47:00 -05:00
tresabhi 3d8cfc3aeb reload visualizer 2026-10-12 10:34:01 -05:00
tresabhi 1c4ef7c129 add container type to get container widht 2026-10-12 00:28:03 -05:00
tresabhi a649995cdc nest styles 2026-10-12 00:08:11 -05:00
tresabhi df53f687f7 bar trans 2026-10-12 00:06:02 -05:00
tresabhi cae63a70b0 traverse toy 2026-10-12 00:04:47 -05:00
tresabhi de80519f06 ricochet toy 2026-10-11 16:49:54 -05:00
tresabhi f935adb70e start of port 2026-10-11 16:27:53 -05:00
tresabhi 3902c63a65 rename 2026-10-11 16:02:08 -05:00
tresabhi e7b81f39b8 shell selector 2026-10-11 16:01:02 -05:00
tresabhi 9692110733 invert logic 2026-10-11 15:47:02 -05:00
tresabhi ae2839cb83 ranks 2026-10-11 15:44:42 -05:00
tresabhi eb0c35d6c6 max cons and provs 2026-10-11 14:00:09 -05:00
tresabhi 1f0392681a shell params 2026-10-11 13:42:20 -05:00
tresabhi 53eebc7a43 game mode abilities 2026-10-11 13:28:38 -05:00
tresabhi 5c482e0bac speed 2026-10-11 01:41:18 -05:00
tresabhi fe28738fe4 reorder 2026-10-11 01:38:20 -05:00
tresabhi e1bc11644d hull traverse speeds 2026-10-11 01:36:34 -05:00
tresabhi e7708ce01b indent 2026-10-11 01:14:08 -05:00
tresabhi 9a66fb5115 terrain coeffs 2026-10-11 01:03:35 -05:00
tresabhi 62da186a25 frontal and back depression 2026-10-11 00:47:00 -05:00
tresabhi 95318d0a99 woohoo more data driven stats! 2026-10-11 00:43:08 -05:00
tresabhi 9c67fd3204 a few imports 2026-10-11 00:37:39 -05:00
tresabhi 1b08393f65 Merge branch 'main' into data-driven 2026-10-11 00:34:39 -05:00
tresabhi d8925a47b5 i am satisfied 2026-10-10 23:12:41 -05:00
tresabhi 60f482d820 hahaha 2026-10-10 20:48:37 -05:00
tresabhi c2fd81c98e remove old credits 2026-10-10 18:47:46 -05:00
tresabhi 0fbc868738 more colors 2026-10-10 18:43:44 -05:00
tresabhi ff0cfbaf0a target 2026-10-10 18:38:05 -05:00
tresabhi 4642b74bca colors! 2026-10-10 18:37:47 -05:00
tresabhi 88d49bd5a3 avenir 2026-10-10 18:24:14 -05:00
tresabhi 6fd2423445 more progress 2026-10-10 18:12:01 -05:00
tresabhi a0dba52649 complete list? 2026-10-10 17:49:06 -05:00
tresabhi 80d76e3729 more more more! 2026-10-10 17:34:07 -05:00
tresabhi 7915b39d9d more! 2026-10-10 17:17:57 -05:00
tresabhi c94a84222d add more 2026-10-10 17:05:36 -05:00
tresabhi 1bca762781 start of credits 2026-10-10 16:59:34 -05:00
tresabhi 7f7d0cc499 remove outdated docs 2026-10-10 16:41:57 -05:00
TrèsAbhi 0c91ee3600 New Crowdin updates (#389)
* New translations en.json (Ukrainian)

[ci skip]

* New translations en.json (Chinese Simplified)

[ci skip]

* New translations en.json (French)

[ci skip]

* New translations en.json (Spanish)

[ci skip]

* New translations en.json (Japanese)

[ci skip]

* New translations en.json (Polish)

[ci skip]

* New translations en.json (Portuguese)

[ci skip]

* New translations en.json (Russian)

[ci skip]
2026-10-09 23:33:06 -05:00
tresabhi 1988ecff92 smaller titles 2026-10-09 18:20:11 -05:00
tresabhi ff03f8ffb2 remove bold 2026-10-09 18:17:56 -05:00
tresabhi d050776628 format 2026-10-09 18:14:59 -05:00
tresabhi 78bff2cdd2 logs 2026-10-09 18:13:22 -05:00
tresabhi 072174ad45 promote version 2026-10-09 18:01:10 -05:00
TrèsAbhi 03f2bcdc1a Merge pull request #366 from Hitjack007/track-repair-time 2026-10-09 17:29:38 -05:00
TrèsAbhi b6ae34dafa Merge pull request #377 from Hitjack007/armor-penetration-chance 2026-10-09 17:28:05 -05:00
TrèsAbhi 2e10052732 Merge pull request #282 from Hitjack007/armor-controls-edge 2026-10-09 17:06:22 -05:00
TrèsAbhi 9a95306b37 Merge pull request #284 from Hitjack007/mixer-mobile 2026-10-09 17:03:52 -05:00
tresabhi fbc4355984 remove clamped vw 2026-10-09 17:02:54 -05:00
TrèsAbhi 24026a95ee Merge pull request #359 from Hitjack007/flag-offset 2026-10-09 16:57:27 -05:00
TrèsAbhi 8f4a43fc8a Merge pull request #357 from Hitjack007/playlist-filters-layout 2026-10-09 16:55:20 -05:00
tresabhi 646d6fa7d5 center align and max width 32rem 2026-10-09 16:54:37 -05:00
TrèsAbhi 37fae7477d Merge branch 'main' into playlist-filters-layout 2026-10-09 16:53:35 -05:00
tresabhi 12ff9a90eb update matrix 2026-10-08 11:13:12 -05:00
tresabhi 5ad5c701c9 update envs for chunk splitting 2026-10-08 11:10:47 -05:00
Mark Greene a365fa5e50 Fix: migrate Imgur images to BlitzKit hosting 2026-10-08 20:28:02 +13:00
Mark Greene 57d77bf2b4 Fix: calculate tank dimensions from collision models 2026-10-08 20:25:03 +13:00
Mark Greene 940c3c537b Feature: track repair time in Tankopedia
Adds track repair time to the Tankopedia survivability stats, matching the value the game lists.

- Added a required `repair_time` to track definitions, filled from each chassis's hysteresis health divided by its health regen per second
- Applied the toolbox, the repair crew skill and the protective kit as multiplicative repair speed bonuses
- Added a Track repair time row under fire chance; it appears once the definitions are regenerated on the next build

Fixes #308
2026-10-08 18:02:05 +13:00
Mark Greene fbdfa2c063 Feature: Penetration chance in the armor shot card
Shows the chance a shot penetrates in the dynamic armor shot card, right-aligned on the shot's result line.

- Penetration varies by ±5% (the same randomisation the armor highlighter shader uses), so the chance is the share of that range where the shot's final outcome is a penetration
- Re-runs the existing shot calculation at different penetration values, so spaced armor, HEAT gap losses and ricochets are taken into account
- For ricochets the chance is shown on the ricocheted shot's line; HE splashes don't count as penetrations
- Adds the `shot_card.stats.penetration_chance` string

Fixes #342
2026-10-08 18:00:57 +13:00
tresabhi 7234c39ad8 move credits back 2026-10-07 21:32:38 -05:00
TrèsAbhi c292adac41 Merge pull request #356 from Hitjack007/track-texture-scroll-rates 2026-10-07 17:05:52 -05:00
TrèsAbhi 1af104d4d6 Merge branch 'main' into track-texture-scroll-rates 2026-10-07 17:01:58 -05:00
TrèsAbhi 7b7513f227 Merge pull request #355 from Hitjack007/restore-locale-redirect 2026-10-07 17:01:01 -05:00
tresabhi 02f5cc0686 remove redundant logic 2026-10-07 17:00:47 -05:00
TrèsAbhi 6f1d653d54 Merge branch 'main' into restore-locale-redirect 2026-10-07 16:59:43 -05:00
tresabhi 92c8b90bb3 remove api check 2026-10-07 16:56:40 -05:00
TrèsAbhi 1b807e6d0e Merge pull request #360 from Hitjack007/equalizer-background-sliver 2026-10-07 16:48:29 -05:00
tresabhi 0e7b020d60 fix sliver 2026-10-07 16:48:08 -05:00
TrèsAbhi eda5d10290 Merge branch 'main' into equalizer-background-sliver 2026-10-07 16:37:48 -05:00
TrèsAbhi 85e93cd827 Merge pull request #361 from Hitjack007/contact-shadows-low-angles 2026-10-07 16:36:58 -05:00
TrèsAbhi bc2dd8c7b7 Merge branch 'main' into contact-shadows-low-angles 2026-10-07 16:35:43 -05:00
TrèsAbhi 904d50f4c3 New Crowdin updates (#379) 2026-10-07 16:35:15 -05:00
TrèsAbhi 112472762d Merge pull request #362 from Hitjack007/discover-ids-await-loop 2026-10-07 16:34:49 -05:00
TrèsAbhi f6e5fabb65 Merge pull request #363 from Hitjack007/stable-tank-sort-order 2026-10-07 16:33:25 -05:00
tresabhi e19d0d5895 revert tank exclusion 2026-10-07 16:32:48 -05:00
TrèsAbhi aa6268a86b Merge branch 'main' into stable-tank-sort-order 2026-10-07 16:30:42 -05:00
TrèsAbhi 0f878485b3 Merge pull request #364 from Hitjack007/tier-eleven-compatibility 2026-10-07 16:28:33 -05:00
tresabhi 0e119b6ba8 optimize awaitable tiers 2026-10-07 16:27:38 -05:00
tresabhi 3397e7f67b truthy array 2026-10-07 16:25:34 -05:00
TrèsAbhi de03a2ab95 Merge branch 'main' into tier-eleven-compatibility 2026-10-07 16:23:11 -05:00
tresabhi d4e649804e add astro cleaning script 2026-10-07 16:15:07 -05:00
TrèsAbhi c31d5705eb Merge pull request #367 from Hitjack007/tank-model-auto-load 2026-10-07 16:11:04 -05:00
tresabhi 16be5faae6 use UpdateIcon 2026-10-07 16:08:56 -05:00
tresabhi e1b079ca02 high contrast button 2026-10-07 16:06:52 -05:00
tresabhi 00149a3d9d simplify strings 2026-10-07 16:03:41 -05:00
TrèsAbhi e3e94e2b49 Merge branch 'main' into tank-model-auto-load 2026-10-07 15:59:34 -05:00
tresabhi 5ce927be4e force bun even with node installed 2026-10-07 10:14:44 -05:00
tresabhi f5dc3684d2 some of reload 2026-10-05 17:45:56 -05:00
Mark Greene 541da65bfc Feature: option to turn off automatic tank model loading
Lets people on limited data stop the Tankopedia from downloading 3D models automatically.

- Added an Automatically load 3D models switch to the settings page, on by default
- When off, the tank page hero shows a Load 3D model button where the loading bar sits
- Held back every model download on the tank page until requested, including the dynamic armor switcher and the gun flexibility visualizer
- Wrapped the tank sandbox in its own suspense boundary so the loading bar shows real progress instead of the whole page waiting on the model
- Measured loading progress from when each loading bar appears so it always starts empty
- Added the English strings; other languages fall back until translated

Fixes #311
2026-10-05 19:52:46 +13:00
Mark Greene e4e92d76b4 Feature: tier XI compatibility
Prepares BlitzKit for tier XI tanks ahead of their release. Tier X was hardcoded as the maximum in the tank search filters, the guesser and the tech tree, and had no roman numeral beyond X.

- Added `XI` to `TIER_ROMAN_NUMERALS`
- Added an awaitable tier list derived from the highest tier in the tank definitions, so nothing changes until tier XI tanks exist
- Used it for the tank search tier filters and the guesser tier picker and defaults
- Made the tech tree check for successors instead of assuming tier X is the last tier

Fixes #302
2026-10-05 18:18:19 +13:00
Mark Greene 273881c460 Fix: keep tank order stable on select
Tanks sharing the same tier, nation, class and type were shown in an arbitrary order that changed after selecting a tank. The meta sort had no tiebreaker, and the filtered list was sorted and reversed in place, so each re-sort started from the previous reversed order and flipped tied tanks around.

- Added tank ID as the first sort key in `metaSortTank` so tied tanks always come out in the same order
- Sorted a copy of the tank search results, pre-sorted by ID, instead of the cached list
- Added an `exclude` option to tank search and used it in Compare so tanks already being compared can't be added again, including through select all or by swapping another slot

Fixes #313
2026-10-05 18:00:43 +13:00
Mark Greene 3aefe7d985 Fix: await the ID discovery loop so it runs to completion
The ID discovery script exited about a second after starting. Since the switch to `atMaxRate`, neither the discovery loop nor the request loop was awaited, and `atMaxRate` didn't await its callback, so the script went straight to `save()`, committed and exited while overlapping duplicate requests failed in the background.

- Awaited both `atMaxRate` loops and the callback inside `atMaxRate`
- Moved the ID range exhaustion check out of the retry loop so retries can't remove extra regions
- Treated network and JSON errors as failed API calls so a long run retries instead of crashing without saving

Fixes #306
2026-10-05 17:33:01 +13:00
Mark Greene bfe31053a9 Fix: keep contact shadows visible at low camera angles
Contact shadows under the tank disappeared when the camera orbited low enough to look up at it. The shadow plane and the ground disc are both transparent and centred on the origin, so three.js sorts them by camera depth. Looking upward flips that order, and because the shadow doesn't write depth, the ground disc was drawn over it.

- Gave `ContactShadows` a `renderOrder` of 1 so it always renders after the ground disc

Fixes #299
2026-10-05 17:28:01 +13:00
Mark Greene a5eac5572e Fix: remove background sliver under equalizer toggle gradient
On some browsers a thin sliver of the Insanity banner showed through along the rounded edges of the equalizer toggle, most visibly on the right where the gradient is opaque. The banner and the gradient were painted on two separately clipped elements, so their anti-aliased edges didn't line up.

- Moved the gradient onto the outer box as the top layer of its `backgroundImage`, above the banner
- Removed the separate gradient layer from the inner flex

Fixes #297
2026-10-05 17:20:52 +13:00
Mark Greene 643e1dd352 Fix: crop transparent padding from fade flags
The fade flag images are 2:1 but the flag only fills the left part, with the rest transparent. That made `cover` + `center` in the tech tree node crop and offset the visible flag.

- Trim the transparent border from the fade flag in the `/flags/fade/[id].webp` route using `sharp`
- Revert the tech tree node back to `cover` + `center`, since the image is now just the flag
2026-10-05 08:10:52 +13:00
Mark Greene fbba0bf13b Fix: constrain playlist filter width
The filters on the playlist page stretched into one long strip beside the tank pool, squeezing the pool into the remaining space.

**Changes**
- Place the filters and the generate button in a fixed-width column (`22rem`) on medium screens and up so the filter buttons wrap compactly
- Stretch the generate button to the column width
- Let the tank pool take the remaining width
- Tighten the gap between the two columns
- Keep the existing stacked layout on small screens
2026-10-04 20:07:52 +13:00
Mark Greene c2c586c2e7 Fix: scroll track textures at the same rate
Dragging a tank's track scrolled its roughness/metalness texture at twice the speed of the base color and normal textures, so highlights drifted off the track links.

**Cause**
- `translateTexture` added the scroll offset to `roughnessMap` and `metalnessMap` separately
- glTF has a single metallic-roughness texture, and the loader assigns that same texture object to both slots, so it received the offset twice

**Changes**
- Collect the track's maps in a `Set` before scrolling so a texture shared between slots is offset exactly once
2026-10-04 20:00:51 +13:00
Mark Greene aaf0e52be4 Fix: restore redirect to the user's preferred locale
Users were no longer sent to their preferred locale. `LocaleRedirector` was emptied in `115fa5aca` after its previous versions caused repeated redirects, and nothing replaced it.

**Cause**
- The earlier logic compared the best locale (a string) against the current locale entry (an object), so the check was always true on non-default locale pages
- That made the redirect fire repeatedly, which led to the logic being removed entirely

**Changes**
- Derive the current locale from the URL, treating the default locale as unprefixed to match how pages are generated
- Redirect only when the best locale differs from the current locale, so it stops after one hop
- Redirect users with a saved `preferred-locale` from any page
- Redirect users without a saved preference from default-locale URLs only, using their browser language, so shared locale links are respected
- Preserve the rest of the path when swapping the locale prefix
- Skip `/api` paths
2026-10-04 19:47:20 +13:00
Mark Greene 6907e5fb7a Fix: mixer buttons clipping at the screen edges on small phones
The mixer's hull/turret/gun row is about 440px wide at its fixed sizes and is centered with `left: 50%` and `translateX(-50%)`, so on phones narrower than that it overflowed past both edges.

Use smaller button sizes on phones and scale the tank images with the viewport (`clamp(2rem, 11vw, 4em)`), with smaller separators and gaps. From 520px up the layout is unchanged.
2026-10-04 15:17:51 +13:00
Mark Greene e5e2f2505c Fix: static armor controls peeking in at the screen edge
The static armor panel hid itself with a fixed `right: -6rem`. In languages with longer labels (e.g. Spanish) the panel is wider than that, so part of it stayed visible at the edge.

Anchor the panel at `right: 0` and slide it out with a transform based on its own width (`translateX(calc(100% + var(--space-4)))`) when static armor isn't active, so it always clears the edge regardless of label length.
2026-10-04 14:50:10 +13:00
205 changed files with 6412 additions and 2721 deletions

No files matched your search

+1
View File
@@ -26,6 +26,7 @@ body:
description: What branch of BlitzKit were you using? Please see the "Branch" section of the footer at the very bottom of any page.
options:
- Production
- Beta
- Preview
- OpenTest
default: 0
+21 -8
View File
@@ -2,6 +2,11 @@ name: Website
on:
push:
branches:
- main
- production
pull_request:
schedule:
- cron: "0 4 * * *"
@@ -93,7 +98,7 @@ jobs:
strategy:
fail-fast: true
matrix:
chunk: [default, api]
chunk: [default, api, models_tanks, models_legendary, models_maps]
steps:
- name: Free disk space
@@ -102,7 +107,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ inputs.branch || 'production' }}
# the PR's ref, or the manual input, or production (selected when cron)
ref: ${{ github.event.pull_request.head.sha || inputs.branch || 'production' }}
submodules: recursive
token: ${{ secrets.GH_TOKEN }}
@@ -122,9 +128,14 @@ jobs:
- name: Install dependencies
run: bun install
- name: Download Client
- name: Download client
run: bun run download-client
- name: Download SmartDLC
if: ${{ env.CLIENT_PROVIDER == 'steam' }}
run: bun run download-smart-dlc
- name: Build
run: bun run --bun build
env:
@@ -139,13 +150,15 @@ jobs:
- name: Trigger deployment
# Deploy if:
# 1. Running cron from main using content from production to target production
# 2. Running manually and targeting non-production
# workflow should trigger if:
# - is by cron and is on main: triggering to production
# - is by push and is on production: triggering to production
# - is by manual and is on anything: triggering to anything
if: >-
${{
github.ref == 'refs/heads/production' ||
(inputs.environment != '' && inputs.environment != 'production')
(github.event_name == 'schedule' && github.ref == 'refs/heads/main')
|| (github.event_name == 'push' && github.ref == 'refs/heads/production')
|| (github.event_name == 'workflow_dispatch')
}}
env:
+2
View File
@@ -96,6 +96,7 @@
"fullstats",
"fuzzysort",
"gamemode",
"gamemodes",
"gameplay",
"glsl",
"GLTF",
@@ -157,6 +158,7 @@
"ownedtanks",
"padder",
"partytown",
"passability",
"patchnotes",
"PIXELFORMAT",
"playerachievements",
-11
View File
@@ -1,11 +0,0 @@
{
"words": [
"Debuff",
"gamemodes",
"Passability",
"Recoverkit",
"Repairkit",
"Respawns",
"Tankopedia"
]
}
-47
View File
@@ -1,47 +0,0 @@
# BlitzKit Credits
BlitzKit is a one man band **TrèsAbhi** but I am far from solo. Helpers, in the order of contribution over time:
### S0AP
BlitzKit Discord bot verification, testing, advertisement, and the initial encouragement to create BlitzKit.
### Prince_NA
BlitzKit Discord bot and website testing and general advise.
### Vovko
BlitzKit Discord bot and website rendering pipeline.
### Maddox
Game file decompression.
### Pyogenics
Game 3D model parsing.
### Aeson
Game file layout, general advise, advertisement, and Tankopedia pre-release testing.
### Minitelrose, ChickenMan7777
3-caliber rule edge cases.
### HisRoyalFatness, Synx
Embed UX.
### [OPTML] clan, \_34, xkdkw, Droodles Blitz
Tankopedia pre-release testing.
### GonnaHetzMe
Redacted by request.
### Mark Greene
GitHub contributor on bug fixes and feature additions.
+3 -3
View File
@@ -1,14 +1,14 @@
# BlitzKit 1.29.10
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update.
## Features
- Combined game mode passive and active abilities into a single dropdown
- [Mark Greene] Added tier filters to the Guess game
- Added tier filters to the Guess game [HitJack]
- Changing tiers will break your streak
## Fixes
- [Mark Greene] module damage is now properly shown when sorting with it in the tank search
- module damage is now properly shown when sorting with it in the tank search [HitJack]
- Duplicate abilities in the tank filters are now removed
+1 -1
View File
@@ -4,7 +4,7 @@ Thank you to [GonnaHetzMe](https://github.com/karelpak1) for your contributions
## Fixes
- [GonnaHetzMe] The turret for Centurion I is no longer offset from the body
- The turret for Centurion I is no longer offset from the body [GonnaHetzMe]
- Please allow up to 24 hours for the model to update
- Vertical Stabilizers now give the correct 12% buff instead of 15%
- Re-ordering tanks in Compare now works once again
+2 -2
View File
@@ -1,6 +1,6 @@
# BlitzKit 1.29.8
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update.
## Features
@@ -15,5 +15,5 @@ Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions
## Fixes
- The Tankopedia now smoothly resizes when enlarging on interaction
- [Mark Greene] Stock turrets now correctly mount the corresponding stock gun
- Stock turrets now correctly mount the corresponding stock gun [HitJack]
- The consumable and provision buttons in Compare are no longer tiny
+3 -3
View File
@@ -1,14 +1,14 @@
# BlitzKit 1.29.9
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update.
## Features
- Reworked tank search filters to be far more compact to make way for new filters
- [Mark Greene] Added the ability to filter tanks by consumables
- Added the ability to filter tanks by consumables [HitJack]
- Added the ability to filter tanks by provisions and game-mode abilities and powers
## Fixes
- Consumable and provision icons in Compare are now properly sized
- [Mark Greene] Provisions now properly affect terrain resistances
- Provisions now properly affect terrain resistances [HitJack]
+5 -5
View File
@@ -13,11 +13,11 @@ Thank you to [GonnaHetzMe](https://github.com/karelpak1) for your contributions
## Technical Features
- [GonnaHetzMe] Enabling dev mode now displays the tank's non-numerical id
- [GonnaHetzMe] The dev id and the new dev name are now copyable on click
- Enabling dev mode now displays the tank's non-numerical id [GonnaHetzMe]
- The dev id and the new dev name are now copyable on click [GonnaHetzMe]
## Fixes
- [GonnaHetzMe] Tech trees that start at tiers higher than tier 1 like the Blitz Nation ones now properly render their trees in Tankopedia
- [GonnaHetzMe] The tank pitch/yaw inputs in the Tank Sandbox now initialize with the correct values instead of 0
- [GonnaHetzMe] Large Repair Kits once again use the correct modern icon
- Tech trees that start at tiers higher than tier 1 like the Blitz Nation ones now properly render their trees in Tankopedia [GonnaHetzMe]
- The tank pitch/yaw inputs in the Tank Sandbox now initialize with the correct values instead of 0 [GonnaHetzMe]
- Large Repair Kits once again use the correct modern icon [GonnaHetzMe]
+1 -1
View File
@@ -4,5 +4,5 @@ Thank you to [GonnaHetzMe](https://github.com/karelpak1) for your contributions
## Fixes
- [GonnaHetzMe] Tank hit points in Equalizer mode is now accurate to the game
- Tank hit points in Equalizer mode is now accurate to the game [GonnaHetzMe]
- Armor highlighting now reflects the actual thicknesses of tanks in Equalizer mode
+3 -3
View File
@@ -4,9 +4,9 @@ Thank you to [GonnaHetzMe](https://github.com/karelpak1) for your contributions
## Fixes
- [GonnaHetzMe] Tungsten shells are now properly cropped
- Tungsten shells are now properly cropped [GonnaHetzMe]
- Equalizer no longer gives insanely high penetration values
- The tank reveal animation no longer repeats when you scroll away and back to the tank sandbox
- [GonnaHetzMe] The RHM. Pzw.'s turret no longer has the floating plate
- The RHM. Pzw.'s turret no longer has the floating plate [GonnaHetzMe]
- Please give BlitzKit up to 1 hour to update the model
- [GonnaHetzMe] Tanks with inclined turret rings like the Minotauro now display the correct pitch in the tank sandbox inputs
- Tanks with inclined turret rings like the Minotauro now display the correct pitch in the tank sandbox inputs [GonnaHetzMe]
+5 -5
View File
@@ -4,7 +4,7 @@ Thank you to [GonnaHetzMe](https://github.com/karelpak1) for your contributions
## Features
- [GonnaHetzMe] Added tooltips to the module selector in Tankopedia
- Added tooltips to the module selector in Tankopedia [GonnaHetzMe]
## Fixes
@@ -12,7 +12,7 @@ Thank you to [GonnaHetzMe](https://github.com/karelpak1) for your contributions
- Removed tank visual and collision model caching
- Guesser once again breaks streaks if not all tiers are selected
- BlitzKit now properly unloads tank models from memory when visually hidden in Guesser
- [GonnaHetzMe] Tanks with complex transparency like the Herdbreaker once again handle opacity correctly
- [GonnaHetzMe] Pieces of tank models that have transparency now handle culling properly (they're visible from both sides)
- [GonnaHetzMe] Tanks with "cardboard" textures have been fixed, thanks to better PVR file parsing
- [GonnaHetzMe] Tank icons are now scaled correctly so small tanks are now properly zoomed out in contrast to bigger ones
- Tanks with complex transparency like the Herdbreaker once again handle opacity correctly [GonnaHetzMe]
- Pieces of tank models that have transparency now handle culling properly (they're visible from both sides) [GonnaHetzMe]
- Tanks with "cardboard" textures have been fixed, thanks to better PVR file parsing [GonnaHetzMe]
- Tank icons are now scaled correctly so small tanks are now properly zoomed out in contrast to bigger ones [GonnaHetzMe]
+5 -5
View File
@@ -1,6 +1,6 @@
# BlitzKit 1.30.7
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update.
## Features
@@ -8,13 +8,13 @@ Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions
## Fixes
- [Mark Greene] Clicking on the armor multiple times with HE no longer crashes BlitzKit
- [Mark Greene] Ricochets that later intersect with external modules no longer falsely claim a successful damaging penetration
- Clicking on the armor multiple times with HE no longer crashes BlitzKit [HitJack]
- Ricochets that later intersect with external modules no longer falsely claim a successful damaging penetration [HitJack]
- The lighting animation no longer stops prematurely when BlitzKit is unfocused
- The spot light angle has been increased to more evenly light up larger tanks
- BlitzKit once again uses soft shadows, reducing the pixelated shadows
- Fixed the weird patterns that formed on the tracks of tanks under self-shadows
- Increased the floor size and light decay to create the illusion that the floor is infinite
- [Mark Greene] Safari no longer crashes when unloading textures for unloaded tanks
- Safari no longer crashes when unloading textures for unloaded tanks [HitJack]
- The gun depression transition between two constraints (like often behind tanks) now uses a gentler zoned and can never be 0
- [Mark Greene] Armor plate names in Static Armor are now properly capitalized in English and translated to other languages
- Armor plate names in Static Armor are now properly capitalized in English and translated to other languages [HitJack]
-41
View File
@@ -1,41 +0,0 @@
# BlitzKit 1.31.4
Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update.
## Features
- [Mark Greene] Added Peekaboo DPM
- Your Peekaboo DPM is lower than your normal DPM if your reload is faster than 10s as you'll have to wait for the remaining 10s for you to get unspotted before you can poke and shoot
- [Mark Greene] The test and owned tank filters are now more intuitive as clicking on them now turns the clicked option on instead of off
- [Mark Greene] Added a spall liner button to the Tankopedia beside the other equipment
## Fixes
- [Mark Greene] BlitzKit Session now properly handles tanks removed from the game
- [Mark Greene] Removed the Beta badge from the Equalizer button
- Updated the background image for the Equalizer button to the actual banner of the new game mode
- [Mark Greene] The armor no longer disappears when you turn on Dynamic Armor System
- [Mark Greene] Camouflage values in Tankopedia are now more descriptive when shooting
- [Mark Greene] The contact shadows on the ground no longer grow & darken when switching between tabs
- The "Help Translate BlitzKit" text on the home page is now properly centered vertically
- [Mark Greene] The footer now says "Branch" instead of "CDN"
- [Mark Greene] The game version in the footer now displays a shorthand version instead of the entire version number
- [Mark Greene] The reload visualizer now displays just 1 decimal place
- [Mark Greene] The search bar no longer redirects to a seemingly random tank when clicking enter when searching
- [Mark Greene] Tank search now properly handles the full names of tanks like the Super Pershing
- [Mark Greene] The loading bar below the name of the tanks in Tankopedia is now sufficiently spaced below
- [Mark Greene] The gun depression visualizer now properly handles the inherent tilt of tanks like the Minotauro
## Technical Features
- Added documentation to the README on setting up and developing BlitzKit locally
- Added bug report and feature request forms to GitHub
- Switched to the GNU AGPLv3 license
## Technical Fixes
- BlitzKit now handles the new ID system for game-modes in WoTB
- The directory which preview versions of the game are downloaded to are now created properly
- This was the last bug standing in the way of BlitzKit Previews
- Welcome back, BlitzKit Previews!
- Removed some dead code from the BlitzKit model mangler
+98
View File
@@ -0,0 +1,98 @@
# BlitzKit 1.32.0
Thank you to [HitJack](https://github.com/Hitjack007) for your contributions this update.
## Features
### Tankopedia & Armor
- Added a spall liner button to the Tankopedia beside the other equipment [HitJack]
- Listed track repair times in the Tankopedia [HitJack]
- The penetration chance is now displayed when penetration testing armor [HitJack]
- Added a new setting to disable automatically loading tanks in Tankopedia [HitJack]
### Tank Performance & Mechanics
- Added Peekaboo DPM [HitJack]
- Your Peekaboo DPM is lower than your normal DPM if your reload is faster than 10s as you'll have to wait for the remaining 10s for you to get unspotted before you can poke and shoot
### Tank Filters
- The test and owned tank filters are now more intuitive as clicking on them now turns the clicked option on instead of off [HitJack]
### Game Compatibility
- Added compatibility for tier XI tanks [HitJack]
## Fixes
### Tankopedia & Armor
- The armor no longer disappears when you turn on Dynamic Armor System [HitJack]
- Camouflage values in Tankopedia are now more descriptive when shooting [HitJack]
- The penetration chance is now displayed when penetration testing armor [HitJack]
- Listed track repair times in the Tankopedia [HitJack]
- Added a spall liner button to the Tankopedia beside the other equipment [HitJack]
- The flags in the Tankopedia tech tree are no longer offset to the top left [HitJack]
- The loading bar below the name of the tanks in Tankopedia is now sufficiently spaced below [HitJack]
- Removed the vertical sliver of pixels found to the right of the Equalizer toggle in Tankopedia
### Tank Search & Filters
- The search bar no longer redirects to a seemingly random tank when clicking enter when searching [HitJack]
- Tank search now properly handles the full names of tanks like the Super Pershing [HitJack]
- The tank search now remains consistent in order even when an added filter does not add/remove tanks from the list [HitJack]
- The test and owned tank filters are now more intuitive as clicking on them now turns the clicked option on instead of off [HitJack]
### Visuals & UI
- Removed the Beta badge from the Equalizer button [HitJack]
- Updated the background image for the Equalizer button to the actual banner of the new game mode
- The contact shadows on the ground no longer grow & darken when switching between tabs [HitJack]
- Additionally, the contact shadows stays visible at low angles [HitJack]
- The "Help Translate BlitzKit" text on the home page is now properly centered vertically
- The footer now says "Branch" instead of "CDN" [HitJack]
- The game version in the footer now displays a shorthand version instead of the entire version number [HitJack]
- The reload visualizer now displays just 1 decimal place [HitJack]
- Plot controls for the Static armor tab no longer pokes in from the right on non-Static armor tabs in some languages [HitJack]
- Mixer's controllers now scale better on smaller screens [HitJack]
- The list of available tanks in the Playlist is no longer excessively wide [HitJack]
### Tank Mechanics & Models
- The gun depression visualizer now properly handles the inherent tilt of tanks like the Minotauro [HitJack]
- When moving the tracks of tanks, the textures no longer move at different rates [HitJack]
### Localization
- Users with a preference on a language are once again redirected to the correct localized pages [HitJack]
### Session & Game Compatibility
- BlitzKit Session now properly handles tanks removed from the game [HitJack]
## Technical Fixes
- BlitzKit now handles the new ID system for game-modes in WoTB
- The directory which preview versions of the game are downloaded to are now created properly
- This was the last bug standing in the way of BlitzKit Previews
- Welcome back, BlitzKit Previews!
- Removed some dead code from the BlitzKit model mangler
- The player discovery loop now persists for longer than 1 iteration [HitJack]
- Bun is now forced to run scripts with the `--bun` flag
## Technical Features
- Added documentation to the README on setting up and developing BlitzKit locally
- Added bug report and feature request forms to GitHub
- Switched to the GNU AGPLv3 license
## Technical Fixes
- BlitzKit now handles the new ID system for game-modes in WoTB
- The directory which preview versions of the game are downloaded to are now created properly
- This was the last bug standing in the way of BlitzKit Previews
- Welcome back, BlitzKit Previews!
- Removed some dead code from the BlitzKit model mangler
- The player discovery loop now persists for longer than 1 iteration [HitJack]
- Bun is now forced to run scripts with the `--bun` flag
-9
View File
@@ -1,9 +0,0 @@
# Timezones
What is considered a "day's worth of games" is determined by the reset time. The reset time is generally at midnight everyday after which newer games are counted towards the next day. Reset times vary from region to region:
- North America: `UTC-5:00` (Central North American Time midnight)
- Europe: `UTC+1:00` (Central European Time midnight)
- Asia: `UTC+7:00` (Central Asia Standard Time midnight)
**Reset times are different from Blitz Stars** (Blitz Stars resets at 5:00 am locally relative to the user opening their website). Inconsistencies with Blitz Stars only occur with sleep-deprived players and players who play in the wrong region.
-45
View File
@@ -1,45 +0,0 @@
# Blitzkit Rating Leaderboard
A straightforward way of storing the entire World of Tanks Blitz rating leaderboard to disk.
```cpp
primary BlitzkitRatingLeaderboard {
utf8"BKRL" magic;
Header header;
primary Body<header> body;
}
Header {
uint16 version; // always 1
Format format;
uint32 count;
}
Format enum<uint8> {
Base,
Superset1,
}
Body<Header header> match<header.format> {
Base {
primary {
uint32 id;
uint16 score;
}[header.count] entries;
};
Superset1 {
primary {
uint32 id;
uint16 score;
uint32 battles;
uint32 wins;
uint32 survived;
uint32 damage;
uint32 kills;
}[header.count] entries;
};
}
```
-18
View File
@@ -1,18 +0,0 @@
# Discovered Ids
```c
primary DiscoveredIds {
utf8"DIDS" magic;
Header header;
primary Body body;
}
Header {
uint16 version; // always 1
}
Body {
uint32 count;
uint32[count] ids;
}
```
+1 -170
View File
@@ -1,172 +1,3 @@
# KitScore
## Memo
KitScore is a performance metric that makes sense. This metric can be applied to any game, virtual or real. Specific pieces of information relating directly to World of Tanks Blitz will be provided within parentheses and with a "WoTB:" prefix.
## Consumption of Observations
KitScore is driven solely by the behavior of other players and finding meaning of a new observation within a pool of condensed data based on previously observed data. Generally, we will be provided with a number of statistics (the $x_i$ of an observation) per player (WoTB: $x_\text{frags}$, $x_\text{xp}$, $x_\text{spots}$, etc.) including a target statistic (the $x_j$ of an observation) that all other statistics try to maximize (WoTB: "winrate" which we will defined later as $x_j$).
If the statistics are cumulative, they will have to be normalized (WoTB: statistics fetched from Wargaming are indeed cumulative even for a single player; $x_\text{frags}=\frac{\Sigma x_\text{frags}}{\Sigma x_\text{battles}}$, $x_\text{spots}=\frac{\Sigma x_\text{spots}}{\Sigma x_\text{battles}}$, $x_j=\frac{\Sigma x_\text{wins}}{\Sigma x_\text{battles}}$, etc.). Each player will have such average statistics, giving us many points of data to work with. If the statistics are not cumulative, then you will be dealing with multiple observations per player which will only help improve accuracy.
## It's Impossible to "Find What Makes the Metric Tick"
WN8, a widely adopted metric in the World of Tanks and World of Tanks Blitz communities is plagued with problems. One of them being that people often find and exploit "what makes the metric tick." In other words, people easily exploit the rudimentary nature of WN8 to score undeservedly high.
In WN8, the gains of some rewarding categories overshadow the losses of others though that may not necessarily be in the best interest in maximizing winrate. KitScore makes this nearly impossible as it punishes a player for scoring well solely high in a certain category. You will never exploit KitScore. Period.
## Generating Reasonable Values
TODO: there's like no runaway cases
## Symbol Interpretation
| Symbol | Interpretation |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| $A_\text{human}$ | The human readable version of the raw score $A$. |
| $A$ | The raw KitScore. |
| $C$ | An arbitrary coefficient. |
| $I$ | The number of statistics including the target statistic. This means that there are $I-1$ entries for $x_i$ and $1$ entry for $y$ for each observation. |
| $i$ | The statistic index of where there are $I$ entries. |
| $j$ | A discriminator for the target statistic. There may never be more than $1$ entries for $j$. |
| $m$ | The degree of the polynomial regression. |
| $n$ | The number of observations. |
| $a$ | An "atomic" score which is a function of the $i$th statistic. |
| $w$ | The weight of the $i$th atomic statistic. |
## Computing the Score
The final metric $A_\text{human}$ is an arbitrarily scaled value of the raw score $A$ where $C$ is any coefficient of your liking. BlitzKit uses $C=10^3$.
$$
A_\text{human}=CA
$$
The final metric $A$ is the weighted average of individual scores $a$ which are functions of the $i$th statistic excluding the target statistic $j$ itself.
$$
A=\frac{\sum_{i \neq j} w_{i}a_{i}}{\sum_{i \neq j} w_{i}}
$$
The "atomic" score $a$ is the difference between the predicted target statistic $P_{i \leftrightarrow j}(x_i)$ acquired using a polynomial regression of degree $m$ and the actual newly observed $x_j$.
$$
a_i=x_j-P_{i \leftrightarrow j}(x_i)
$$
The polynomial regression of $m$th degree can be written as follows:
$$
P(x)=\beta_0x^0+\beta_1x^1+\beta_2x^2+\dots+\beta_mx^m
$$
Polynomial regression coefficients $\beta$ can be acquired through basic linear algebra where an $m \times m$ of constants is multiplied with $m \times 1$ of unknowns which equals another $m \times 1$ of constants.
```math
\begin{bmatrix}
\sum_{k=1}^n x_i^0 & \sum_{k=1}^n x_i^1 & \sum_{k=1}^n x_i^2 & \dots & \sum_{k=1}^n x_i^m \\
\sum_{k=1}^n x_i^1 & \sum_{k=1}^n x_i^2 & \sum_{k=1}^n x_i^3 & \dots & \sum_{k=1}^n x_i^{m+1} \\
\sum_{k=1}^n x_i^2 & \sum_{k=1}^n x_i^3 & \sum_{k=1}^n x_i^4 & \dots & \sum_{k=1}^n x_i^{m+2} \\
\vdots & \vdots & \vdots & \ddots & \vdots \\
\sum_{k=1}^n x_i^m & \sum_{k=1}^n x_i^{m+1} & \sum_{k=1}^n x_i^{m+1} & \dots & \sum_{k=1}^n x_i^{2m} \\
\end{bmatrix}
\begin{bmatrix}
\beta_0 \\
\beta_1 \\
\beta_2 \\
\vdots \\
\beta_m
\end{bmatrix}
=
\begin{bmatrix}
\sum_{k=1}^n x_jx_i^0 \\
\sum_{k=1}^n x_jx_i^1 \\
\sum_{k=1}^n x_jx_i^2 \\
\vdots \\
\sum_{k=1}^n x_jx_i^m
\end{bmatrix}
```
The weight $w$ is a slice of the Pearson corelation matrix. The numerator is the corelation between the $i$th statistic and the target statistic $j$ while the denominator the sum of the corelations between the $i$th statistic and all statistics but the target statistic $j$.
$$
w_{i}=\frac{r_{i\leftrightarrow j}^2}{\sum_{i_{k}\neq j}r_{i\leftrightarrow i_{k}}^2}
$$
## Understanding the Corelation Matrix
Forming the corelation matrix will create an $I \times I$ matrix where. This matrix is best represented as a table. The example below illustrates a matrix/table for $I=4$ which includes $3$ entries for $i$ statistics and $1$ entry for the $j$ target statistic.
| | $x_1$ | $x_2$ | $x_3$ | $x_j$ |
| ----- | --------------------------- | --------------------------- | --------------------------- | --------------------------- |
| $x_1$ | $1$ | $r_{1 \leftrightarrow 2}^2$ | $r_{1 \leftrightarrow 3}^2$ | $r_{1 \leftrightarrow j}^2$ |
| $x_2$ | $r_{1 \leftrightarrow 2}^2$ | $1$ | $r_{2 \leftrightarrow 3}^2$ | $r_{2 \leftrightarrow j}^2$ |
| $x_3$ | $r_{1 \leftrightarrow 3}^2$ | $r_{2 \leftrightarrow 3}^2$ | $1$ | $r_{3 \leftrightarrow j}^2$ |
| $x_j$ | $r_{1 \leftrightarrow j}^2$ | $r_{2 \leftrightarrow j}^2$ | $r_{3 \leftrightarrow j}^2$ | $1$ |
It should not be a surprise to find $r_{i \leftrightarrow i}^2=1$ which forms a neat diagonal across the table. Note that there are many duplicates within the $I^2$ entries. The "bottom-right triangle" can be eliminated to remove redundancies leaving us with only $\frac{I(I+1)}{2}$ fields occupied.
| | $x_1$ | $x_2$ | $x_3$ | $x_j$ |
| ----- | ----- | --------------------------- | --------------------------- | --------------------------- |
| $x_1$ | $1$ | $r_{1 \leftrightarrow 2}^2$ | $r_{1 \leftrightarrow 3}^2$ | $r_{1 \leftrightarrow j}^2$ |
| $x_2$ | | $1$ | $r_{2 \leftrightarrow 3}^2$ | $r_{2 \leftrightarrow j}^2$ |
| $x_3$ | | | $1$ | $r_{3 \leftrightarrow j}^2$ |
| $x_j$ | | | | $1$ |
When programming this matrix into an array, $r_{i \leftrightarrow i}^2=1$ will be redundant. I recommend condensing the matrix into an array of size $\frac{I(I+1)}{2}+I=\frac{I(I-1)}{2}$ to avoid all redundant entries. For $I=4$, the array will look something like the one illustrated below (here, $j=4$).
$$
[r_{1 \leftrightarrow 2}^2, r_{1 \leftrightarrow 3}^2, r_{1 \leftrightarrow 4}^2, r_{2 \leftrightarrow 3}^2, r_{2 \leftrightarrow 4}^2, r_{3 \leftrightarrow 4}^2]
$$
## Understanding the Weights
Some may find the weight equation arbitrary; it evidently isn't. The goal of $w$ is to eliminate over-rewarding the same behavior represented in multiple statistics. It makes all atomic statistics concurrent by leveraging the Pearson corelation matrix.
A simplified example will clarify the idea. First, we compute a Pearson corelation matrix for $I=3$ statistics. We have $x_1$ and $x_2$, the statistics that are trying to maximize $x_j$. Reminder: a Pearson corelation matrix finds the $r$ value not only between $x_i$ and $x_j$ but also between all combinations of $x_i$ and themselves. Here, $r^2$ is calculated using polynomial regression corelation analysis to the $m$th degree.
| | $x_1$ | $x_2$ | $x_3$ | $x_j$ |
| ----- | ----- | ----------- | ----- | ----- |
| $x_1$ | $1$ | $\boxed{0}$ | $0$ | $1$ |
| $x_2$ | | $1$ | $0$ | $1$ |
| $x_3$ | | | $1$ | $1$ |
| $x_j$ | | | | $1$ |
In the case above, $r^2_{1 \leftrightarrow 2}=0$ meaning that both statistics have absolutely nothing to do with each other. Hence, the player will be awarded for optimizing both individually. And since $r^2_{1 \leftrightarrow j}=r^2_{2 \leftrightarrow j}$, they should both be rewarded equally; in other words, they should have equal weights.
$$
w_{1}=\frac{1}{1 + 0 + 0}=1
$$
$$
w_{2}=\frac{1}{0 + 1 + 0}=1
$$
$$
w_{3}=\frac{1}{0 + 0 + 1}=1
$$
Now consider a case where $x_1$ and $x_2$ are fully correlated. It would be a bad idea to reward them equally; hence, their weights suffer.
| | $x_1$ | $x_2$ | $x_3$ | $x_j$ |
| ----- | ----- | ----------- | ----- | ----- |
| $x_1$ | $1$ | $\boxed{1}$ | $0$ | $1$ |
| $x_2$ | | $1$ | $0$ | $1$ |
| $x_3$ | | | $1$ | $1$ |
| $x_j$ | | | | $1$ |
$$
w_{1}=\frac{1}{1 + 1 + 0}=0.5
$$
$$
w_{2}=\frac{1}{1 + 1 + 0}=0.5
$$
$$
w_{3}=\frac{1}{0 + 0 + 1}=1
$$
Remind me to write out the linearized model!
-71
View File
@@ -1,71 +0,0 @@
# 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;
}
```
+22 -3
View File
@@ -4,14 +4,33 @@
"paths": {
"default": null,
"api": null
"api": null,
"models_tanks": null,
"models_legendary": null,
"models_maps": null
},
"nodes": {
"default": "blitzkit/deploy-node-beta.blitzkit.app",
"api": "blitzkit/deploy-node-api.beta.blitzkit.app"
"api": "blitzkit/deploy-node-api.beta.blitzkit.app",
"models_tanks": "blitzkit/deploy-node-tanks.models.beta.blitzkit.app",
"models_legendary": "blitzkit/deploy-node-legendary.models.beta.blitzkit.app",
"models_maps": "blitzkit/deploy-node-maps.models.beta.blitzkit.app"
},
"aliases": {
"default": "/",
"api": "https://api.beta.blitzkit.app",
"static": "https://static.beta.blitzkit.app"
"models_tanks": "https://tanks.models.beta.blitzkit.app",
"models_legendary": "https://legendary.models.beta.blitzkit.app",
"models_maps": "https://maps.models.beta.blitzkit.app",
"static": "https://static.blitzkit.app"
}
}
+15 -1
View File
@@ -4,11 +4,25 @@
"paths": {
"default": null,
"api": "api"
"api": "api",
"models_tanks": "models/tanks",
"models_legendary": "models/legendary",
"models_maps": "models/maps"
},
"nodes": {},
"aliases": {
"default": "/",
"api": "/api",
"models_tanks": "/models/tanks",
"models_legendary": "/models/legendary",
"models_maps": "/models/maps",
"static": "https://static.blitzkit.app"
}
}
+22 -3
View File
@@ -4,14 +4,33 @@
"paths": {
"default": null,
"api": null
"api": null,
"models_tanks": null,
"models_legendary": null,
"models_maps": null
},
"nodes": {
"default": "blitzkit/deploy-node-opentest.blitzkit.app",
"api": "blitzkit/deploy-node-api.opentest.blitzkit.app"
"api": "blitzkit/deploy-node-api.opentest.blitzkit.app",
"models_tanks": "blitzkit/deploy-node-tanks.models.opentest.blitzkit.app",
"models_legendary": "blitzkit/deploy-node-legendary.models.opentest.blitzkit.app",
"models_maps": "blitzkit/deploy-node-maps.models.opentest.blitzkit.app"
},
"aliases": {
"default": "/",
"api": "https://api.opentest.blitzkit.app",
"static": "https://static.opentest.blitzkit.app"
"models_tanks": "https://tanks.models.opentest.blitzkit.app",
"models_legendary": "https://legendary.models.opentest.blitzkit.app",
"models_maps": "https://maps.models.opentest.blitzkit.app",
"static": "https://static.blitzkit.app"
}
}
+22 -3
View File
@@ -4,14 +4,33 @@
"paths": {
"default": null,
"api": null
"api": null,
"models_tanks": null,
"models_legendary": null,
"models_maps": null
},
"nodes": {
"default": "blitzkit/deploy-node-preview.blitzkit.app",
"api": "blitzkit/deploy-node-api.preview.blitzkit.app"
"api": "blitzkit/deploy-node-api.preview.blitzkit.app",
"models_tanks": "blitzkit/deploy-node-tanks.models.preview.blitzkit.app",
"models_legendary": "blitzkit/deploy-node-legendary.models.preview.blitzkit.app",
"models_maps": "blitzkit/deploy-node-maps.models.preview.blitzkit.app"
},
"aliases": {
"default": "/",
"api": "https://api.preview.blitzkit.app",
"static": "https://static.preview.blitzkit.app"
"models_tanks": "https://tanks.models.preview.blitzkit.app",
"models_legendary": "https://legendary.models.preview.blitzkit.app",
"models_maps": "https://maps.models.preview.blitzkit.app",
"static": "https://static.blitzkit.app"
}
}
+21 -2
View File
@@ -4,14 +4,33 @@
"paths": {
"default": null,
"api": null
"api": null,
"models_tanks": null,
"models_legendary": null,
"models_maps": null
},
"nodes": {
"default": "blitzkit/deploy-node-blitzkit.app",
"api": "blitzkit/deploy-node-api.blitzkit.app"
"api": "blitzkit/deploy-node-api.blitzkit.app",
"models_tanks": "blitzkit/deploy-node-tanks.models.blitzkit.app",
"models_legendary": "blitzkit/deploy-node-legendary.models.blitzkit.app",
"models_maps": "blitzkit/deploy-node-maps.models.blitzkit.app"
},
"aliases": {
"default": "/",
"api": "https://api.blitzkit.app",
"models_tanks": "https://tanks.models.blitzkit.app",
"models_legendary": "https://legendary.models.blitzkit.app",
"models_maps": "https://maps.models.blitzkit.app",
"static": "https://static.blitzkit.app"
}
}
+7 -4
View File
@@ -1,15 +1,18 @@
{
"name": "blitzkit",
"version": "1.31.4",
"version": "1.33.0-beta.1",
"license": "BlitzKit License",
"private": true,
"scripts": {
"dev": "cd packages/website && bun run dev",
"build": "cd packages/website && bun run build",
"dev": "cd packages/website && bun --bun run dev",
"build": "cd packages/website && bun --bun run build",
"discover-ids": "cd packages/scripts && bun run discover-ids",
"build-protos": "cd packages/scripts && bun run build-protos",
"record-performance": "cd packages/scripts && bun run record-performance",
"download-client": "cd packages/scripts && bun run download-client"
"download-client": "cd packages/scripts && bun run download-client",
"download-smart-dlc": "cd packages/scripts && bun run download-smart-dlc",
"clean-astro": "rm -rf packages/website/.astro packages/website/node_modules/.astro packages/website/node_modules/.vite",
"vfs": "cd packages/scripts && bun run vfs"
},
"type": "module",
"workspaces": [
@@ -253,6 +253,27 @@ export abstract class ServerBlitzKitAPI0 extends AbstractBlitzKitAPI {
return this;
}
protected getTankFilterIds(
name: string,
definitionKey: string,
filter: "include" | "exclude",
) {
const keys = name.trim().split(/\s+/).filter(Boolean);
return keys.flatMap((key) => {
const id = this.tankStringIdMap[key];
if (id === undefined) {
console.warn(
`Skipping unavailable tank "${key}" in ${filter} vehicle filter for "${definitionKey}"`,
);
return [];
}
return [id];
});
}
protected getString(name: string) {
const collection: Record<string, string> = {
[locales.default]: this.stringsI18n[locales.default][name],
@@ -271,6 +292,8 @@ export abstract class ServerBlitzKitAPI0 extends AbstractBlitzKitAPI {
collection[locale] = localizedString;
}
collection[locales.default] = collection[locales.default] ?? name;
return { locales: collection } satisfies I18nString;
}
}
@@ -390,7 +390,7 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 {
: TankType.TANK_TYPE_RESEARCHABLE,
tier: tank.level,
class: tankClass,
testing: tankTags.includes("testTank"),
testing: tankTags.includes("testTank") || tank.level === 11,
deprecated: tankTags.includes("deprecated"),
price: tankPrice,
camouflage_still: tankDefinition.root.invisibility.still,
@@ -437,6 +437,7 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 {
resistance_hard: terrainResistances[0],
resistance_medium: terrainResistances[1],
resistance_soft: terrainResistances[2],
repair_time: track.hysteresisHealth / track.healthRegenPerSec,
tier: track.level,
unlocks: resolveUnlocks(
this.blitzModuleTypeToBlitzkit,
@@ -597,9 +598,25 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 {
for (const shellKey in gunListEntry.shots) {
const gunShellEntry = gunListEntry.shots[shellKey];
const shell = shellList.root[shellKey];
// Newer client data stores shot properties on the shell rather
// than repeating them under every gun's <shots> entry.
const speed =
typeof gunShellEntry === "string"
? shell.speed
: gunShellEntry.speed;
const maxDistance =
typeof gunShellEntry === "string"
? shell.maxDistance
: gunShellEntry.maxDistance;
const piercingPower =
typeof gunShellEntry === "string"
? shell.piercingPower
: gunShellEntry.piercingPower;
const shellId = toUniqueId(nation, shell.id);
const shellName = this.getString(shell.userString);
const penetrationRaw = gunShellEntry.piercingPower
const penetrationRaw = piercingPower
.split(" ")
.filter((penetrationString) => penetrationString !== "")
.map(Number);
@@ -609,7 +626,7 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 {
].shells.push({
id: shellId,
name: shellName,
velocity: gunShellEntry.speed,
velocity: speed,
armor_damage: shell.damage.armor,
module_damage: shell.damage.devices,
caliber: shell.caliber,
@@ -625,7 +642,7 @@ export abstract class ServerBlitzKitAPI1 extends ServerBlitzKitAPI0 {
near: penetrationRaw[0],
far: penetrationRaw[1],
},
range: gunShellEntry.maxDistance,
range: maxDistance,
});
}
@@ -1,15 +1,60 @@
import { CamouflageDefinitions } from "@blitzkit/protos";
import {
Sc2ReadStream,
type TankParameters,
} from "@blitzkit/core";
import { isEqual, times } from "lodash-es";
import { Cache } from "./0_base";
import { ServerBlitzKitAPI1 } from "./1_tanks";
export abstract class ServerBlitzKitAPI2 extends ServerBlitzKitAPI1 {
private async skinPresets(vehicle: string) {
const [nation, tankKey] = vehicle.split(":");
const parameters = await this.vfs.yaml<TankParameters>(
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
);
const sc2 = new Sc2ReadStream(
(
await this.vfs.file(
`Data/3d/${parameters.resourcesPath.blitzModelPath}`,
)
).buffer as ArrayBuffer,
).sc2();
const presets = new Set<string>();
for (const node of sc2["#dataNodes"]) {
if (typeof node.configCount !== "number") continue;
times(node.configCount, (index) => node[`configArchive_${index}`])
.filter(
(archive) =>
!isEqual(archive.textures, node.configArchive_0.textures),
)
.forEach((archive) => presets.add(archive.configName));
}
return presets;
}
@Cache()
async camouflages() {
const camouflageDefinitions = CamouflageDefinitions.create();
const customization = await this.vfs.yaml<
Record<string, { Name: string; Path: string }>
>("Data/3d/Customization.yaml");
const items = new Map(
Object.values(customization).map(({ Name, Path }) => [
Name,
Path.replace("~res:/", "Data/").replace(/\.sc2$/, ""),
]),
);
const skinPresets = new Map<string, Promise<Set<string>>>();
for (const camoKey in this.camouflagesXml!.root.camouflages) {
const camo = this.camouflagesXml!.root.camouflages[camoKey];
if (!(camoKey in this.camouflagesYaml!)) continue;
const yamlEntry = this.camouflagesYaml![camoKey];
const fullName = yamlEntry.userString
? this.getString(yamlEntry.userString)
@@ -20,12 +65,51 @@ export abstract class ServerBlitzKitAPI2 extends ServerBlitzKitAPI1 {
const resolvedTankName = shortName ?? fullName;
const resolvedTankNameFull =
resolvedTankName === fullName ? undefined : fullName;
const include = camo.vehicleFilter.include;
const vehicle =
include && !Array.isArray(include) && "vehicle" in include
? include.vehicle?.name
: undefined;
let tankId: number | undefined = undefined;
if (vehicle && vehicle in this.tankStringIdMap) {
const resolvedParts = await Promise.all(
Object.values(yamlEntry.customEntities ?? {}).map(
async ({ item }) => {
const path = items.get(item);
return (
path !== undefined &&
(await this.vfs.resolve(`${path}.sc2`)) &&
(await this.vfs.resolve(`${path}.scg`))
);
},
),
);
let hasPreset = false;
if (camo.group === "legendary" && yamlEntry.preset !== undefined) {
if (!skinPresets.has(vehicle)) {
skinPresets.set(vehicle, this.skinPresets(vehicle));
}
hasPreset = (await skinPresets.get(vehicle)!).has(yamlEntry.preset);
}
if (resolvedParts.some(Boolean) || hasPreset) {
tankId = this.tankStringIdMap[vehicle];
}
}
const name = this.getString(camo.userString);
camouflageDefinitions.camouflages[camo.id] = {
id: camo.id,
name: this.getString(camo.userString),
name,
tank_name: resolvedTankName,
tank_name_full: resolvedTankNameFull,
tank_id: tankId,
};
}
+113 -24
View File
@@ -2,15 +2,22 @@ import {
BlitzCrewType,
ChassisDefinitionsList,
GunDefinitionsList,
I_HAT,
J_HAT,
K_HAT,
parseHierarchies,
Sc2ReadStream,
ScgReadStream,
TankParameters,
toUniqueId,
TurretDefinitionsList,
VehicleDefinitionArmor,
VehicleDefinitionList,
VehicleDefinitions,
VertexAttribute,
} from "@blitzkit/core";
import { Armor, Crew, ModelDefinitions, Vector3 } from "@blitzkit/protos";
import { Vector3Tuple } from "three";
import { MathUtils, Vector3 as THREEVector3, Vector3Tuple } from "three";
import { Cache } from "./0_base";
import { ServerBlitzKitAPI2 } from "./2_camouflages";
@@ -19,6 +26,81 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
return { x: tuple[0], y: tuple[1], z: -tuple[2] } satisfies Vector3;
}
private async collisionBoundingBoxes(
nation: string,
tankKey: string,
turretRotation?: { yaw: number; pitch: number; roll: number },
) {
const boundingBoxes: Record<
string,
{ min: Vector3Tuple; max: Vector3Tuple }
> = {};
const path = `Data/3d/Tanks/CollisionMeshes/${nation}-${tankKey}`;
if (!(await this.vfs.resolve(`${path}.sc2`))) {
throw new Error(`Missing collision mesh for ${nation}-${tankKey}`);
}
const sc2 = new Sc2ReadStream(
(await this.vfs.file(`${path}.sc2`)).buffer as ArrayBuffer,
).sc2();
const scg = new ScgReadStream(
(await this.vfs.file(`${path}.scg`)).buffer as ArrayBuffer,
).scg();
parseHierarchies(sc2["#hierarchy"], null, (hierarchy, components) => {
for (const component of components) {
if (component["comp.typename"] !== "RenderComponent") continue;
const polygonGroup = scg.get(
component["rc.renderObj"]["ro.batches"]["0000"]["rb.datasource"],
);
if (!polygonGroup) continue;
const min: Vector3Tuple = [Infinity, Infinity, Infinity];
const max: Vector3Tuple = [-Infinity, -Infinity, -Infinity];
const rotate =
turretRotation && hierarchy.name.toLowerCase().startsWith("turret");
polygonGroup.vertices.forEach((vertex) => {
vertex.forEach(({ attribute, value }) => {
if (attribute !== VertexAttribute.VERTEX) return;
const position = new THREEVector3(...value);
if (rotate) {
position
.applyAxisAngle(
I_HAT,
-MathUtils.degToRad(turretRotation.pitch),
)
.applyAxisAngle(J_HAT, -MathUtils.degToRad(turretRotation.roll))
.applyAxisAngle(K_HAT, -MathUtils.degToRad(turretRotation.yaw));
}
const coordinates = position.toArray();
for (let axis = 0; axis < 3; axis++) {
min[axis] = Math.min(min[axis], coordinates[axis]);
max[axis] = Math.max(max[axis], coordinates[axis]);
}
});
});
boundingBoxes[hierarchy.name.toLowerCase()] = { min, max };
}
return null;
});
if (!boundingBoxes.hull) {
throw new Error(`Missing hull collision mesh for ${nation}-${tankKey}`);
}
return boundingBoxes;
}
private assignArmor(
raw: VehicleDefinitionArmor[string],
id: number,
@@ -63,6 +145,12 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
const tankParameters = await this.vfs.yaml<TankParameters>(
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
);
const collisionBoundingBoxes = await this.collisionBoundingBoxes(
nation,
tankKey,
tankDefinition.root.hull.turretInitialRotation,
);
const hullBoundingBox = collisionBoundingBoxes.hull;
const turretOrigin = tankDefinition.root.hull.turretPositions.turret
.split(" ")
.map(Number) as Vector3Tuple;
@@ -137,12 +225,8 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
}
: undefined,
bounding_box: {
min: this.vector3TupleToBlitzkit(
tankParameters.collision.hull.bbox.min,
),
max: this.vector3TupleToBlitzkit(
tankParameters.collision.hull.bbox.max,
),
min: this.vector3TupleToBlitzkit(hullBoundingBox.min),
max: this.vector3TupleToBlitzkit(hullBoundingBox.max),
},
turrets: {},
tracks: {},
@@ -202,32 +286,37 @@ export abstract class ServerBlitzKitAPI3 extends ServerBlitzKitAPI2 {
this.assignArmor(armorRaw, armorId, turretArmor);
});
turret.userString;
const turretCollisionName = turret.hitTester.collisionModel
.split("/")
.at(-1)!
.split(".")[0]
.toLowerCase();
const isFullYaw = -turretYaw[0] + turretYaw[1] === 360;
if (!collisionBoundingBoxes[turretCollisionName] && isFullYaw) {
throw new Error(
`Missing turret collision mesh for ${nation}-${tankKey} ${turretKey}`,
);
}
const turretBoundingBox = collisionBoundingBoxes[
turretCollisionName
] ?? {
min: [0, 0, 0] as Vector3Tuple,
max: [0, 0, 0] as Vector3Tuple,
};
modelDefinitions.models[tankId].turrets[turretId] = {
armor: turretArmor,
gun_origin: this.vector3TupleToBlitzkit(gunOrigin),
model_id: turretModel,
yaw:
-turretYaw[0] + turretYaw[1] === 360
yaw: isFullYaw
? undefined
: { min: turretYaw[0], max: turretYaw[1] },
guns: {},
bounding_box: {
min: this.vector3TupleToBlitzkit(
tankParameters.collision[
this.parsePath!(
turret.hitTester.collisionModel,
).name.toLowerCase()
].bbox.min,
),
max: this.vector3TupleToBlitzkit(
tankParameters.collision[
this.parsePath!(
turret.hitTester.collisionModel,
).name.toLowerCase()
].bbox.max,
),
min: this.vector3TupleToBlitzkit(turretBoundingBox.min),
max: this.vector3TupleToBlitzkit(turretBoundingBox.max),
},
};
@@ -8,7 +8,7 @@ export abstract class ServerBlitzKitAPI6 extends ServerBlitzKitAPI5 {
async consumables() {
const consumableDefinitions = ConsumableDefinitions.create();
Object.entries(this.consumablesCommon).forEach(([, consumable]) => {
Object.entries(this.consumablesCommon).forEach(([key, consumable]) => {
const entry: Consumable = {
id: consumable.id,
game_mode_exclusive: "gameModeFilter" in consumable,
@@ -39,9 +39,7 @@ export abstract class ServerBlitzKitAPI6 extends ServerBlitzKitAPI5 {
filter_type: {
$case: "ids",
value: {
ids: includeRaw.name.split(/ +/).map((key) => {
return this.tankStringIdMap[key];
}),
ids: this.getTankFilterIds(includeRaw.name, key, "include"),
},
},
});
@@ -67,9 +65,7 @@ export abstract class ServerBlitzKitAPI6 extends ServerBlitzKitAPI5 {
filter_type: {
$case: "ids",
value: {
ids: excludeRaw.name.split(/ +/).map((key) => {
return this.tankStringIdMap[key];
}),
ids: this.getTankFilterIds(excludeRaw.name, key, "exclude"),
},
},
});
@@ -8,7 +8,7 @@ export abstract class ServerBlitzKitAPI7 extends ServerBlitzKitAPI6 {
async provisions() {
const provisionDefinitions = ProvisionDefinitions.create();
Object.entries(this.provisionsCommon).forEach(([, provision]) => {
Object.entries(this.provisionsCommon).forEach(([key, provision]) => {
const entry = Provision.create({
id: provision.id,
exclude: [],
@@ -39,9 +39,7 @@ export abstract class ServerBlitzKitAPI7 extends ServerBlitzKitAPI6 {
filter_type: {
$case: "ids",
value: {
ids: includeRaw.name.split(/ +/).map((key) => {
return this.tankStringIdMap[key];
}),
ids: this.getTankFilterIds(includeRaw.name, key, "include"),
},
},
});
@@ -67,9 +65,7 @@ export abstract class ServerBlitzKitAPI7 extends ServerBlitzKitAPI6 {
filter_type: {
$case: "ids",
value: {
ids: excludeRaw.name
.split(/ +/)
.map((key) => this.tankStringIdMap[key]),
ids: this.getTankFilterIds(excludeRaw.name, key, "exclude"),
},
},
});
@@ -0,0 +1,11 @@
import { SkillDefinitions } from "@blitzkit/protos";
import { createDefaultSkills } from "./createDefaultSkills";
import { TankEnvironment } from "./tankEnvironment";
export function createDefaultEnvironment(skills: SkillDefinitions) {
return {
equalize: false,
distance: 0,
skills: createDefaultSkills(skills),
} satisfies TankEnvironment;
}
@@ -1,12 +1,13 @@
import { SkillDefinitions } from "..";
import { SkillDefinitions } from "@blitzkit/protos";
import { permanentSkills } from "../blitz/permanentSkills";
import { MAX_SKILL_LEVEL } from "../config/skills";
export function createDefaultSkills(skillDefinitions: SkillDefinitions) {
const skills: Record<string, number> = {};
Object.values(skillDefinitions.classes).forEach((tankSkills) => {
tankSkills.skills.forEach((skill) => {
skills[skill] = permanentSkills.includes(skill) ? 7 : 0;
skills[skill] = permanentSkills.includes(skill) ? MAX_SKILL_LEVEL : 0;
});
});
+1 -1
View File
@@ -1,3 +1,3 @@
export function degressiveStat(crew: number) {
return 0.875 / (0.00375 * 100 * crew + 0.5) - 1;
return 0.875 / (0.00375 * 100 * crew + 0.5);
}
+3 -1
View File
@@ -3,6 +3,7 @@ export * from "./api";
export * from "./assertSecret";
export * from "./availableProvisions";
export * from "./coefficient";
export * from "./createDefaultEnvironment";
export * from "./createDefaultProvisions";
export * from "./createDefaultSkills";
export * from "./degressiveStat";
@@ -17,7 +18,6 @@ export * from "./normalizeBoundingBox";
export * from "./patientFetchJSON";
export * from "./popularTanks";
export * from "./progressiveStat";
export * from "./resolveDpm";
export * from "./resolvePenetrationCoefficient";
export * from "./reviews";
export * from "./romanize";
@@ -25,6 +25,8 @@ export * from "./sluggify";
export * from "./staticAsset";
export * from "./sum";
export * from "./tankDefinitions";
export * from "./tankEnvironment";
export * from "./tankBoundingBox";
export * from "./tankIcon";
export * from "./tankNames";
export * from "./unionBoundingBox";
@@ -7,6 +7,7 @@ export function metaSortTank(
gameDefinitions: GameDefinitions,
) {
return tanks
.sort((a, b) => a.id - b.id)
.sort((a, b) => b.tier - a.tier)
.sort(
(a, b) => treeTypeOrder.indexOf(b.type) - treeTypeOrder.indexOf(a.type),
@@ -1,3 +1,3 @@
export function progressiveStat(crew: number) {
return (0.00375 * 100 * crew + 0.5) / 0.875 - 1;
return (0.00375 * 100 * crew + 0.5) / 0.875;
}
-63
View File
@@ -1,63 +0,0 @@
import {
GunDefinition,
GunDefinitionAutoReloader,
ShellDefinition,
} from '../protos';
export function resolveDpm(
gun: GunDefinition,
shell: ShellDefinition,
damageCoefficient = 1,
reloadCoefficient = 1,
intraClipCoefficient = 1,
) {
const alpha = shell.armor_damage * damageCoefficient;
let dps: number;
if (gun.gun_type!.$case === 'regular') {
dps = alpha / (reloadCoefficient * gun.gun_type!.value.reload);
} else if (gun.gun_type!.$case === 'auto_loader') {
const damage = alpha * gun.gun_type!.value.shell_count;
let time = gun.gun_type!.value.clip_reload * reloadCoefficient;
if (gun.burst) {
time +=
(gun.gun_type!.value.shell_count / gun.burst.count - 1) *
gun.gun_type!.value.intra_clip *
intraClipCoefficient;
time +=
(gun.gun_type!.value.shell_count / gun.burst.count) *
(gun.burst.count - 1) *
gun.burst.interval;
} else {
time +=
(gun.gun_type!.value.shell_count - 1) *
gun.gun_type!.value.intra_clip *
intraClipCoefficient;
}
dps = damage / time;
} else {
const mostOptimalShell = gun.gun_type!.value.shell_reloads.reduce<null | {
index: number;
reload: number;
}>((current, reloadRaw, index) => {
const reload =
reloadRaw * reloadCoefficient +
(index > 0
? (gun.gun_type!.value as GunDefinitionAutoReloader).intra_clip *
intraClipCoefficient
: 0);
if (current === null || reload < current.reload) {
return { index, reload };
}
return current;
}, null)!;
dps = alpha / mostOptimalShell?.reload;
}
return dps * 60;
}
@@ -0,0 +1,32 @@
import { ModelDefinition, TurretModelDefinition } from "../protos";
import { unionBoundingBox } from "./unionBoundingBox";
export function tankBoundingBox(
tankModelDefinition: ModelDefinition,
turretModelDefinition: TurretModelDefinition,
) {
const hull = tankModelDefinition.bounding_box!;
const turret = turretModelDefinition.bounding_box!;
const origin = tankModelDefinition.turret_origin!;
if (
turret.min!.x === turret.max!.x &&
turret.min!.y === turret.max!.y &&
turret.min!.z === turret.max!.z
) {
return unionBoundingBox(hull, hull);
}
return unionBoundingBox(hull, {
min: {
x: turret.min!.x + origin.x,
y: turret.min!.y + origin.z,
z: turret.min!.z + origin.y,
},
max: {
x: turret.max!.x + origin.x,
y: turret.max!.y + origin.z,
z: turret.max!.z + origin.y,
},
});
}
@@ -1,4 +1,4 @@
import { CrewType, ShellType, TankClass, TankType } from "@blitzkit/protos";
import { CrewType, ShellType } from "@blitzkit/protos";
import { times } from "lodash-es";
export const SHELL_NAMES: Record<ShellType, string> = {
@@ -15,46 +15,7 @@ export const CREW_MEMBER_NAMES = {
[CrewType.CREW_TYPE_RADIOMAN]: "radioman",
} as const;
/**
* @deprecated use svg
*/
export const TANK_ICONS: Record<TankClass, string> = {
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/BIHSEH0.png",
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/CSNha5V.png",
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/wvf3ltm.png",
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/ECeqlZa.png",
};
/**
* @deprecated use svg
*/
export const TANK_ICONS_PREMIUM: Record<TankClass, string> = {
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/TCu3EdR.png",
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/zdkpTRb.png",
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/3z7eHX6.png",
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/P3vbmyA.png",
};
/**
* @deprecated use svg
*/
export const TANK_ICONS_COLLECTOR: Record<TankClass, string> = {
[TankClass.TANK_CLASS_TANK_DESTROYER]: "https://i.imgur.com/WTjeirB.png",
[TankClass.TANK_CLASS_LIGHT]: "https://i.imgur.com/EwhtKkU.png",
[TankClass.TANK_CLASS_MEDIUM]: "https://i.imgur.com/u8YDMBh.png",
[TankClass.TANK_CLASS_HEAVY]: "https://i.imgur.com/8xRf3nc.png",
};
export const TIERS = times(10, (index) => index + 1);
export const TIER_ROMAN_NUMERALS: Record<number, string> = {
1: "I",
2: "II",
3: "III",
4: "IV",
5: "V",
6: "VI",
7: "VII",
8: "VIII",
9: "IX",
10: "X",
};
export const flags: Record<string, string> = {
ussr: "<:ussr:1218421042033197197>",
@@ -67,8 +28,3 @@ export const flags: Record<string, string> = {
european: "🇪🇺",
other: "<:other:1218421572243558482>",
};
export const TREE_TYPE_ICONS: Record<TankType, Record<TankClass, string>> = {
[TankType.TANK_TYPE_RESEARCHABLE]: TANK_ICONS,
[TankType.TANK_TYPE_PREMIUM]: TANK_ICONS_PREMIUM,
[TankType.TANK_TYPE_COLLECTOR]: TANK_ICONS_COLLECTOR,
};
@@ -0,0 +1,5 @@
export interface TankEnvironment {
equalize: boolean;
distance: number;
skills: Record<string, number>;
}
+1
View File
@@ -1,3 +1,4 @@
export * from "./gunTypeOrder";
export * from "./skills";
export * from "./tankClassOrder";
export * from "./treeTypeOrder";
+1
View File
@@ -0,0 +1 @@
export const MAX_SKILL_LEVEL = 7;
-50
View File
@@ -1,50 +0,0 @@
export enum ImgurSize {
/**
* The default size
*/
Default = '',
/**
* Unknown size but really really small
*/
UltraSmall = 'ss',
/**
* 90x90
*/
SmallSquare = 's',
/**
* 160x160
*/
BigSquare = 'b',
/**
* 160x160
*/
SmallThumbnail = 't',
/**
* 320x320
*/
MediumThumbnail = 'm',
/**
* 640x640
*/
LargeThumbnail = 'l',
/**
* 1024x1024
*/
HugeThumbnail = 'h',
}
interface ImgurOptions {
format: 'jpeg' | 'png';
size: ImgurSize;
}
const defaultImgurOptions: ImgurOptions = {
format: 'png',
size: ImgurSize.Default,
};
export function imgur(id: string, imgurOptions?: Partial<ImgurOptions>) {
const options = { ...defaultImgurOptions, ...imgurOptions };
return `https://i.imgur.com/${id}${options.size}.${options.format}`;
}
-1
View File
@@ -1 +0,0 @@
export * from './imgur';
-1
View File
@@ -2,7 +2,6 @@ export * from "./api";
export * from "./blitz";
export * from "./blitzkit";
export * from "./config";
export * from "./imgur";
export * from "./math";
export * from "./patreon";
export * from "./statistics";
+26 -1
View File
@@ -88,6 +88,7 @@ enum PvrChannelType {
enum ResolvedBitRatePixelFormat {
R4G4B4A4,
R8G8B8A8,
R5G6B5A0,
}
@@ -137,6 +138,27 @@ export class PvrReadStream extends ReadStream {
};
}
case ResolvedBitRatePixelFormat.R8G8B8A8: {
const data = Buffer.alloc(pixelCount * 4);
times(pixelCount, (index) => {
const buffer = this.consumeUint8Array(4);
const bufferIndex = index * 4;
data[bufferIndex] = buffer[0]; // R
data[bufferIndex + 1] = buffer[1]; // G
data[bufferIndex + 2] = buffer[2]; // B
data[bufferIndex + 3] = buffer[3]; // A
});
return {
data,
width: header.width,
height: header.height,
channels: 4 as const,
};
}
case ResolvedBitRatePixelFormat.R5G6B5A0: {
const data = Buffer.alloc(pixelCount * 3);
@@ -208,10 +230,13 @@ export class PvrReadStream extends ReadStream {
if (pixelFormat.order === "rgba") {
if (isEqual(pixelFormat.bitRates, [4, 4, 4, 4])) {
return ResolvedBitRatePixelFormat.R4G4B4A4;
} else
} else if (isEqual(pixelFormat.bitRates, [8, 8, 8, 8])) {
return ResolvedBitRatePixelFormat.R8G8B8A8;
} else {
throw new TypeError(
`Unhandled rgba bit rate ${pixelFormat.bitRates.join(", ")}`,
);
}
} else if (pixelFormat.order === "rgb\0") {
if (isEqual(pixelFormat.bitRates, [5, 6, 5, 0])) {
return ResolvedBitRatePixelFormat.R5G6B5A0;
+22
View File
@@ -90,6 +90,28 @@ export interface Hierarchy {
name: string;
}
export function parseHierarchies<Parent>(
hierarchies: Hierarchy[],
parent: Parent,
parseHierarchy: (
hierarchy: Hierarchy,
components: Component[],
parent: Parent,
) => Parent | undefined,
) {
for (const hierarchy of hierarchies) {
const components = Array.from(
{ length: hierarchy.components.count },
(_, index) => hierarchy.components[index.toString().padStart(4, "0")],
);
const child = parseHierarchy(hierarchy, components, parent);
if (child !== undefined && hierarchy["#hierarchy"]) {
parseHierarchies(hierarchy["#hierarchy"], child, parseHierarchy);
}
}
}
// 0001, 0002, 0003...: Component
type Components = {
count: number;
@@ -3,5 +3,7 @@ export type CamouflagesYaml = Record<
{
userString?: string;
shortUserString?: string;
preset?: string;
customEntities?: Record<string, { item: string }>;
}
>;
@@ -20,7 +20,9 @@ export interface GunDefinitionsList {
burst?: { count: number; rate: number };
clip?: { count: number; rate: number };
shots: {
[key: string]: {
[key: string]:
| ""
| {
speed: number;
piercingPower: string;
maxDistance: number;
@@ -10,6 +10,9 @@ export type ShellDefinitionsList = Record<
normalizationAngle?: number;
ricochetAngle?: number;
explosionRadius?: number;
speed: number;
maxDistance?: number;
piercingPower: string;
}
> & {
icons: Record<string, string>;
@@ -34,6 +34,8 @@ export interface VehicleDefinitions {
};
level: number;
hullPosition: string;
hysteresisHealth: number;
healthRegenPerSec: number;
armor: {
leftTrack:
| number
+75 -54
View File
@@ -8,11 +8,46 @@
"s": "{value}s",
"percentage": "{value}%",
"deg_s": "{value}°/s",
"plus": "+{value}"
"m_s": "{value}m/s",
"km_h": "{value}km/h",
"t": "{value}t",
"bhp": "{value}bhp",
"bhp_t": "{value}bhp/t",
"kN": "{value}kN",
"hp_s": "{value}hp/s",
"plus": "+{value}",
"fraction": "{numerator} / {denominator}"
},
"tanks": {
"toys": {
"ricochet": {
"angle": "Angle",
"normalization": "Normalization",
"effective": "Effective",
"armor": "Armor",
"no_ricochet": "Explosive shells don't ricochet"
},
"traverse": {
"effective": "Effective",
"hull": "Hull",
"turret": "Turret"
},
"reload": {
"skip": "Skip"
}
},
"characteristics": {
"compare": {
"label": "Compare against",
"class": "Tier {tier} {class}",
"tier": "All tier {tier}",
"all": "All tanks"
},
"titles": {
"gun": "Gun",
"clip": "Clip",
@@ -38,11 +73,11 @@
"burst_size": "Burst size",
"intra_burst": "Intra-burst",
"penetration": "Penetration",
"penetration_loss_by_distance": "Penetration loss by distance",
"penetration_loss_after_ricochet": "Penetration loss after ricochet",
"shell_velocity": "Shell velocity",
"shell_range": "Shell range",
"shell_capacity": "Shell capacity",
"penetration_loss_by_distance": "Loss by 500m",
"penetration_loss_after_ricochet": "Loss after ricochet",
"shell_velocity": "Velocity",
"shell_range": "Range",
"shell_capacity": "Capacity",
"caliber": "Caliber",
"normalization": "Normalization",
"ricochet": "Ricochet",
@@ -51,16 +86,32 @@
"dispersion_angle": "Dispersion angle",
"gun_depression": "Gun depression",
"gun_elevation": "Gun elevation",
"gun_frontal_depression": "Frontal depression",
"gun_frontal_elevation": "Frontal elevation",
"gun_rear_depression": "Rear depression",
"gun_rear_elevation": "Rear elevation",
"azimuth_left": "Azimuth left",
"azimuth_right": "Azimuth right",
"speed_forward": "Speed forward",
"speed_backward": "Speed backward",
"speed_forward": "Forward",
"speed_backward": "Backward",
"speeds": "Speed",
"engine_power": "Engine power",
"brake_force": "Brake force",
"weight": "Weight",
"power_to_weight": "Power to weight",
"terrain_coefficient": "Terrain coefficient",
"terrain_coefficient": "Terrain coefficients",
"raw_terrain_coefficient": "Raw terrain coefficients",
"hard_terrain_coefficient": "Hard",
"medium_terrain_coefficient": "Medium",
"soft_terrain_coefficient": "Soft",
"hard_terrain_coefficient_raw": "Hard",
"medium_terrain_coefficient_raw": "Medium",
"soft_terrain_coefficient_raw": "Soft",
"hull_traverse_speed": "Hull traverse speed",
"hull_traverse_speeds": "Hull traverse speed",
"hull_traverse_speed_hard": "Hard",
"hull_traverse_speed_medium": "Medium",
"hull_traverse_speed_soft": "Soft",
"turret_traverse_speed": "Turret traverse speed",
"gun_traverse_speed": "Gun traverse speed",
"health": "Health",
@@ -75,6 +126,10 @@
}
},
"game_modes": {
"title": "Game mode abilities"
},
"navigation": {
"tank": "Tank",
"characteristics": "Characteristics"
@@ -87,6 +142,10 @@
"provisions": "Provisions",
"modules": "Modules",
"equipment": "Equipment",
"shells": "Shells",
"clear": "Clear",
"maximize": "Maximize",
"skills": "Crew skills",
"alternative": "Alt"
},
@@ -216,7 +275,7 @@
"branches": {
"beta": "Beta",
"development": "Development",
"development": "Post Malone Edition",
"opentest": "OpenTest",
"preview": "Preview",
"production": "Production"
@@ -387,6 +446,7 @@
"title": "Settings",
"description": "Change BlitzKit settings",
"dev_mode": "Development mode",
"auto_load_models": "Auto-load models",
"language": "Language"
},
@@ -424,55 +484,17 @@
},
"sort": {
"meta.none": "none",
"survivability.health": "health",
"survivability.viewRange": "view range",
"survivability.camouflageStill": "camouflage still",
"survivability.camouflageMoving": "camouflage moving",
"survivability.camouflageShooting": "camouflage shooting",
"survivability.volume": "volume",
"survivability.length": "length",
"fire.dpm": "standard DPM",
"fire.dpmPremium": "premium DPM",
"fire.reload": "reload",
"fire.caliber": "caliber",
"fire.standardPenetration": "standard penetration",
"fire.premiumPenetration": "premium penetration",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "damage",
"fire.moduleDamage": "module damage",
"fire.shellVelocity": "shell velocity",
"fire.shellRange": "shell range",
"fire.shellCapacity": "shell capacity",
"fire.aimTime": "aim time",
"fire.dispersionStill": "dispersion still",
"fire.dispersionMoving": "dispersion moving",
"fire.gunDepression": "gun depression",
"fire.gunElevation": "gun elevation",
"maneuverability.forwardsSpeed": "forwards speed",
"maneuverability.backwardsSpeed": "backwards speed",
"maneuverability.power": "power",
"maneuverability.powerToWeight": "power to weight",
"maneuverability.weight": "weight",
"maneuverability.traverseSpeed": "traverse speed"
},
"sort_dropdown": {
"label": "Sort",
"groups": {
"sort": {
"no_sort": "No sort",
"label": "Sort by",
"fire": "Fire",
"maneuverability": "Maneuverability",
"survivability": "Survivability"
},
"none": "None",
"by": "Sort by",
"direction": {
"label": "Direction",
"ascending": "Ascending",
"descending": "Descending"
}
}
}
},
"warnings": {
@@ -510,11 +532,10 @@
"footer": {
"motto": "Everything World of Tanks Blitz",
"copyright": "Unaffiliated with Wargaming or any service",
"association": "An Open Collective Project",
"about": {
"title": "About",
"credits": "Credits",
"discord": "Discord",
"github": "GitHub",
"patches": "Patches"
+9 -6
View File
@@ -1,9 +1,4 @@
{
"test": {
"default": "test",
"long": "This is a long test string. You MUST NEVER see this in the UI. My favorite fruit is {fruit} because it has the color of {color}. I like it so much that I will say its name 3 times! {fruit}! {fruit}! {fruit}! Once again, it's {color}, not {not_color}.",
"fox": "El rápido zorro marrón salta sobre el perro perezoso."
},
"common": {
"name": "BlitzKit",
"units": {
@@ -194,6 +189,7 @@
"title": "Settings",
"description": "Change BlitzKit settings",
"dev_mode": "Modo de desarrollador",
"auto_load_models": "Auto-load models",
"language": "Idioma"
},
"common": {
@@ -306,6 +302,12 @@
}
},
"tools": {
"credits": {
"name": "Credits",
"button": "View people",
"description": "Contributors of BlitzKit",
"seo_description": "BlitzKit is the work of many"
},
"playlist": {
"reset": "Restablecer",
"shuffle": "Shuffle",
@@ -412,6 +414,7 @@
}
},
"sandbox": {
"load_model": "Load model",
"poses": {
"hull_down": "Hull down",
"face_hug": "Cara a cara",
@@ -621,6 +624,7 @@
"hullTraverseSoftTerrain": "Suave",
"health": "Salud",
"fireChance": "Probabilidad de incendio",
"trackRepairTime": "Track repair time",
"viewRange": "Rango de visión",
"camouflageStill": "Quieto",
"camouflageMoving": "En movimiento",
@@ -919,7 +923,6 @@
"copyright": "No afiliado con Wargaming u otros servicios",
"about": {
"title": "About",
"credits": "Credits",
"discord": "Discord",
"github": "GitHub",
"patches": "Parches"
+9 -6
View File
@@ -1,9 +1,4 @@
{
"test": {
"default": "teste",
"long": "Ceci est une longue chaîne de test. Vous ne DEVEZ JAMAIS le voir dans l'interface utilisateur. Mon fruit préféré est {fruit}, car il a la couleur {color}. Je l'aime tellement que je vais dire son nom 3 fois ! {fruit}! {fruit}! {fruit} ! Encore une fois, c'est {color}, pas {not_color}.",
"fox": "Le renard brun et rapide saute par-dessus le chien paresseux."
},
"common": {
"name": "BlitzKit",
"units": {
@@ -194,6 +189,7 @@
"title": "Paramètres",
"description": "Modifier les paramètres de BlitzKit",
"dev_mode": "Mode développement",
"auto_load_models": "Auto-load models",
"language": "Langue"
},
"common": {
@@ -306,6 +302,12 @@
}
},
"tools": {
"credits": {
"name": "Credits",
"button": "View people",
"description": "Contributors of BlitzKit",
"seo_description": "BlitzKit is the work of many"
},
"playlist": {
"reset": "Reset",
"shuffle": "Shuffle",
@@ -412,6 +414,7 @@
}
},
"sandbox": {
"load_model": "Load model",
"poses": {
"hull_down": "Hull down",
"face_hug": "Face hug",
@@ -621,6 +624,7 @@
"hullTraverseSoftTerrain": "Soft",
"health": "Health",
"fireChance": "Fire chance",
"trackRepairTime": "Track repair time",
"viewRange": "View range",
"camouflageStill": "Still",
"camouflageMoving": "Moving",
@@ -919,7 +923,6 @@
"copyright": "Unaffiliated with Wargaming or any service",
"about": {
"title": "About",
"credits": "Credits",
"discord": "Discord",
"github": "GitHub",
"patches": "Patches"
+9 -6
View File
@@ -1,9 +1,4 @@
{
"test": {
"default": "test",
"long": "This is a long test string. You MUST NEVER see this in the UI. My favorite fruit is {fruit} because it has the color of {color}. I like it so much that I will say its name 3 times! {fruit}! {fruit}! {fruit}! Once again, it's {color}, not {not_color}.",
"fox": "すばしっこい茶色のキツネが怠け者の犬を飛び越した。"
},
"common": {
"name": "BlitzKit",
"units": {
@@ -194,6 +189,7 @@
"title": "Settings",
"description": "Change BlitzKit settings",
"dev_mode": "開発者モード",
"auto_load_models": "Auto-load models",
"language": "言語 / Language"
},
"common": {
@@ -306,6 +302,12 @@
}
},
"tools": {
"credits": {
"name": "Credits",
"button": "View people",
"description": "Contributors of BlitzKit",
"seo_description": "BlitzKit is the work of many"
},
"playlist": {
"reset": "リセット",
"shuffle": "Shuffle",
@@ -412,6 +414,7 @@
}
},
"sandbox": {
"load_model": "Load model",
"poses": {
"hull_down": "ハルダウン",
"face_hug": "密着",
@@ -621,6 +624,7 @@
"hullTraverseSoftTerrain": "軟弱地",
"health": "耐久値",
"fireChance": "火災発生率",
"trackRepairTime": "Track repair time",
"viewRange": "視認範囲",
"camouflageStill": "静止時",
"camouflageMoving": "移動時",
@@ -919,7 +923,6 @@
"copyright": "Wargamingやその他のサービスとは関係ありません",
"about": {
"title": "概要",
"credits": "クレジット",
"discord": "Discord",
"github": "GitHub",
"patches": "パッチノート"
+238 -235
View File
@@ -1,9 +1,4 @@
{
"test": {
"default": "test",
"long": "This is a long test string. You MUST NEVER see this in the UI. My favorite fruit is {fruit} because it has the color of {color}. I like it so much that I will say its name 3 times! {fruit}! {fruit}! {fruit}! Once again, it's {color}, not {not_color}.",
"fox": "Szybki brązowy lis przeskoczył nad leniwym psem."
},
"common": {
"name": "BlitzKit",
"units": {
@@ -19,7 +14,7 @@
"gun_types": {
"auto_loader": "Samo ładowacz",
"auto_reloader": "Automatyczne przeładowanie",
"regular": "Regular"
"regular": "Regularny"
},
"shells": {
"ap": "AP",
@@ -38,10 +33,10 @@
},
"branches": {
"beta": "Beta",
"development": "Development",
"opentest": "OpenTest",
"preview": "Preview",
"production": "Production"
"development": "Rozwój",
"opentest": "OtwartyTest",
"preview": "Przegląd",
"production": "Produkcja"
},
"locales": {
"en": "Angielski",
@@ -102,28 +97,28 @@
"other": "Hybryda"
},
"tank_class_short": {
"-1": "unrecognized",
"-1": "nierozpoznany",
"0": "Lekki",
"1": "Średni",
"2": "Ciężki",
"3": "TD"
"3": "NC"
},
"tank_class_short_plural_lowercase": {
"0": "lekkie",
"1": "średnie",
"2": "ciężkie",
"3": "TDs"
"3": "NC-ów"
},
"tank_class_medium": {
"-1": "unrecognized",
"-1": "nierozpoznany",
"0": "Lekki",
"1": "Średni",
"2": "Ciężki",
"3": "Niszczyciel czołgów"
},
"tree_type": {
"-1": "unrecognized",
"0": "Tech tree",
"-1": "nierozpoznany",
"0": "Drzewko technologiczne",
"1": "Premium",
"2": "Kolekcjonerski"
},
@@ -187,222 +182,229 @@
},
"website": {
"redirect": {
"title": "301: moved permanently",
"body": "This page has moved. Redirecting..."
"title": "301: przeniesiony na stałe",
"body": "Ta strona została przeniesiona. Przekierowywanie..."
},
"settings": {
"title": "Settings",
"description": "Change BlitzKit settings",
"dev_mode": "Development mode",
"language": "Language"
"title": "Ustawienia",
"description": "Zmień ustawienia BlitzKit",
"dev_mode": "Tryb deweloperski",
"auto_load_models": "Automatycznie wczytaj modele",
"language": "Język"
},
"common": {
"player_search": {
"hint": "Search players...",
"no_results": "No results",
"searching": "Searching..."
"hint": "Szukaj graczy...",
"no_results": "Brak wyników",
"searching": "Wyszukiwanie..."
},
"copy_button": {
"copied": "Copied!"
"copied": "Skopiowano!"
},
"tank_search": {
"select_dialog_title": "Search for tanks",
"swap_dialog_title": "Swap tank",
"clear_filters": "Clear filters",
"login": "Login to use this",
"search_bar_hint": "Search tanks...",
"recent": "Recently viewed",
"select_all": "Select all",
"count_singular": "1 tank",
"count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}",
"clear": "Clear",
"tier": "Tier {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"select_dialog_title": "Szukaj czołgów",
"swap_dialog_title": "Zamień czołg",
"clear_filters": "Wyczyść filtry",
"login": "Zaloguj się, aby tego użyć",
"search_bar_hint": "Szukaj czołgów...",
"recent": "Ostatnio oglądane",
"select_all": "Zaznacz wszystko",
"count_singular": "1 czołg",
"count_plural": "{count} czołgi",
"sorting_by": "Sortowanie według {name}",
"clear": "Wyczyść",
"tier": "Poziom czołgu {tier}",
"passive": "{name} (Pasywny)",
"active": "{name} (Aktywne)",
"no_tanks_found": {
"body": "No tanks found.",
"body": "Nie znaleziono czołgu.",
"search_again": "",
"clear_filters": "Try clearing filters."
"clear_filters": "Spróbuj wyczyścić filtry."
},
"sort": {
"meta.none": "none",
"survivability.health": "health",
"survivability.viewRange": "view range",
"survivability.camouflageStill": "camouflage still",
"survivability.camouflageMoving": "camouflage moving",
"survivability.camouflageShooting": "camouflage shooting",
"survivability.volume": "volume",
"survivability.length": "length",
"fire.dpm": "standard DPM",
"fire.dpmPremium": "premium DPM",
"fire.reload": "reload",
"fire.caliber": "caliber",
"fire.standardPenetration": "standard penetration",
"fire.premiumPenetration": "premium penetration",
"meta.none": "brak",
"survivability.health": "punkty.życia",
"survivability.viewRange": "zasięg widzenia",
"survivability.camouflageStill": "kamuflaż w bezruchu",
"survivability.camouflageMoving": "kamuflaż w trakcie ruchu",
"survivability.camouflageShooting": "kamuflaż podczas strzału",
"survivability.volume": "wartość",
"survivability.length": "długość",
"fire.dpm": "DPM pocisków podstawowych",
"fire.dpmPremium": "DPM pocisków premium",
"fire.reload": "przeładowanie",
"fire.caliber": "kaliber",
"fire.standardPenetration": "Penetracja pocisków standardowych",
"fire.premiumPenetration": "Penetracja pocisków premium",
"fire.tertiaryPenetration": "tertiary penetration",
"fire.damage": "damage",
"fire.moduleDamage": "module damage",
"fire.shellVelocity": "shell velocity",
"fire.shellRange": "shell range",
"fire.shellCapacity": "shell capacity",
"fire.aimTime": "aim time",
"fire.damage": "uszkodzenia",
"fire.moduleDamage": "Uszkodzenia modułów",
"fire.shellVelocity": "Prędkość pocisku",
"fire.shellRange": "Zasięg pocisku",
"fire.shellCapacity": "Maksymalna ilość pocisków",
"fire.aimTime": "Czas celowania",
"fire.dispersionStill": "dispersion still",
"fire.dispersionMoving": "dispersion moving",
"fire.gunDepression": "gun depression",
"fire.gunElevation": "gun elevation",
"maneuverability.forwardsSpeed": "forwards speed",
"maneuverability.backwardsSpeed": "backwards speed",
"maneuverability.power": "power",
"maneuverability.powerToWeight": "power to weight",
"maneuverability.weight": "weight",
"fire.gunDepression": "Depresja działa",
"fire.gunElevation": "Elewacja działa",
"maneuverability.forwardsSpeed": "Maksymalna prędkość do przodu",
"maneuverability.backwardsSpeed": "Maksymalna prędkość do tyłu",
"maneuverability.power": "Moc silnika",
"maneuverability.powerToWeight": "Proporcja mocy do masy",
"maneuverability.weight": "Masa czołgu",
"maneuverability.traverseSpeed": "traverse speed"
},
"sort_dropdown": {
"label": "Sort",
"label": "Filtruj",
"groups": {
"sort": {
"no_sort": "No sort",
"label": "Sort by",
"fire": "Fire",
"maneuverability": "Maneuverability",
"survivability": "Survivability"
"no_sort": "Brak filtrów",
"label": "Filtruj według",
"fire": "Siła ognia",
"maneuverability": "Mobilność ",
"survivability": "Przetrwanie"
},
"direction": {
"label": "Direction",
"ascending": "Ascending",
"descending": "Descending"
"ascending": "Rosnąco",
"descending": "Malejąco"
}
}
}
},
"warnings": {
"test": "Tanks in testing are subject to change and may not represent the final product."
"test": "Wartości statystyk czołgów w fazie testowej mogą ulec zmianie."
}
},
"home": {
"discord": {
"join_short": "Join cool people!",
"join_long": "Join super duper cool people!",
"join_short": "Dołącz do elity!",
"join_long": "Dołącz do topowej elity!",
"action": "Discord"
},
"hero": {
"credit": "By {name} ❤️"
"credit": "Według {name} ♥️"
},
"hot_tanks": {
"title": "Popular tanks",
"subtitle": "Last {days} days"
"title": "Popularne czołgi",
"subtitle": "Ostatnie {days} dni"
},
"other_projects": {
"title": "Other cool projects"
"title": "Inne epickie projekty"
},
"help_translate": "Please help translate {name} to {language}!",
"seo_description": "Tools for everything World of Tanks Blitz"
"help_translate": "Proszę pomóż przetłumaczyć {name} na {language}!",
"seo_description": "Narzędzia do wszystkiego World of Tanks Blitz"
},
"docs": {
"patches": {
"name": "BlitzKit Changelogs",
"seo_description": "Changelogs for BlitzKit Discord bot and website"
"name": "Zapisy zmian BlitzKit",
"seo_description": "Zapisy zmian BlitzKit: Discord bot i strona internetowa"
}
},
"tools": {
"credits": {
"name": "Credits",
"button": "View people",
"description": "Contributors of BlitzKit",
"seo_description": "BlitzKit is the work of many"
},
"playlist": {
"reset": "Reset",
"shuffle": "Shuffle",
"remove": "Remove played",
"tracking": "BlitzKit is tracking your battles",
"reset": "Resetuj",
"shuffle": "Wymieszaj",
"remove": "Usuń czołgi, którymi grałem",
"tracking": "BlitzKit zapisuje twoje bitwy",
"sort": {
"button": "Sort by",
"last_played": "Last played",
"least_played": "Least played"
"button": "Filtruj według",
"last_played": "Ostatnio używane czołgi",
"least_played": "Najmniej używane czołgi"
},
"table": {
"index": "#",
"never": "Never",
"tank": "Tank",
"last_played": "Last played",
"battles": "Career battles",
"never": "Nigdy",
"tank": "Czołg",
"last_played": "Ostatnio używane czołgi",
"battles": "Wszystkie bitwy na koncie",
"unrecorded": "--",
"wins": "Zwycięstwa"
},
"large_warning": {
"title": "That's a lot of tanks!",
"description": "Are you sure you want to create a playlist of {count} tanks? That's {time} hours of gameplay at 50% winrate!",
"cancel": "Cancel",
"proceed": "Proceed"
"title": "To jest mnóstwo czołgów!",
"description": "Czy na pewno chcesz stworzyć playlistę złożoną z {count} czołgów? To {time} godzin gry przy 50% zwycięstw!",
"cancel": "Anuluj",
"proceed": "Zatwierdź"
},
"generate": "Generate playlist",
"name": "Playlist",
"description": "Tank playlist generator",
"seo_description": "Don't know what to play in World of Tanks Blitz? Use BlitzKit Playlist to get suggestions!",
"button": "Generate",
"pool_size": "{count} possible tanks"
"generate": "Wygeneruj playlistę",
"name": "Playlista",
"description": "Generator playlisty czołgów",
"seo_description": "Nie wiesz, którym czołgiem grać w World of Tanks Blitz? Użyj funkcji playlisty BlitzKit, i ułóż własną listę czołgów do gry!",
"button": "Wygeneruj",
"pool_size": "{count} możliwe czołgi"
},
"mixer": {
"name": "Mixer",
"name": "Mikser",
"button": "Mix and match",
"description": "Mix tank modules",
"seo_description": "Mix and match hulls, turrets, and guns of tanks in World of Tanks Blitz",
"screenshot": "Screenshot",
"randomize": "Randomize"
"screenshot": "Zrzut ekranu",
"randomize": "Wymieszaj"
},
"guess": {
"name": "Guess",
"name": "Zgadnij",
"button": "What could it be?",
"description": "Silhouette mini-game",
"seo_description": "Play a tank guessing game with a silhouette in World of Tanks Blitz",
"search": {
"placeholder": "Type to guess...",
"skip": "Skip",
"guess": "Guess",
"next": "Next",
"placeholder": "Wpisz odpowiedź",
"skip": "Pomiń",
"guess": "Zgaduję",
"next": "Następny",
"no_results": "Brak wyników"
},
"stats": "{correct} / {total} correct, {streak} streak",
"help": {
"title": "Partially reveal tank?",
"title": "Odsłonić część czołgu?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
"cancel": "Anuluj",
"reveal": "Pokaż"
},
"clear": "Clear",
"select_all": "Select all",
"tier": "Tier {tier}"
"clear": "Wyczyść",
"select_all": "Zaznacz wszystko ",
"tier": "Poziom czołgu {tier}"
},
"tankopedia": {
"search": "Search a stat...",
"search": "Wyszukaj statystykę...",
"name": "Tankopedia",
"button": "Find tanks",
"description": "Blitz tank encyclopedia",
"button": "Znajdź czołg",
"description": "Encyklopedia czołgów w WoTb",
"seo_description": "Statistics, armor, and more for all tanks in World of Tanks Blitz",
"tank_seo_title": "{name} - Tier {tier} {nationality} {class} | {website} Tankopedia | World of Tanks Blitz (WoTB)",
"tank_seo_description": "{name} armor, statistics, equipment, provisions, and more. {dpm} DPM, {alpha}hp alpha, {penetration}mm penetration, {speed}kph top speed, {health}hp health.",
"visualizers": {
"reload": {
"skip": "Skip"
"skip": "Pomiń"
},
"assault": {
"title": "Assault shells"
},
"rotator": {
"hull": "Hull",
"turret": "Turret",
"hard": "Hard",
"hull": "Kadłub",
"turret": "Wieża",
"hard": "Utwardzony",
"medium": "Średni",
"soft": "Soft",
"effective": "Effective"
"soft": "Miękki",
"effective": "Rzeczywisty"
},
"ricochet": {
"angle": "Angle",
"angle": "Kąt",
"normalization": "Normalization",
"effective_angle": "Effective angle",
"armor": "Armor",
"no_ricochet": "Explosive shells don't ricochet"
"effective_angle": "Rzeczywisty kąt nachylenia pancerza",
"armor": "Pancerz",
"no_ricochet": "Pociski odłamkowe nie rykoszetują."
},
"view_range": {
"camo": "Camo",
"ghost_factory": "Ghost Factory",
"dead_rail": "Dead Rail"
"camo": "Kamuflaż",
"ghost_factory": "Fabryka duchów",
"dead_rail": "Martwy tor"
},
"flexibility": {
"yaw": "Yaw",
@@ -412,6 +414,7 @@
}
},
"sandbox": {
"load_model": "Load model",
"poses": {
"hull_down": "Hull down",
"face_hug": "Face hug",
@@ -441,71 +444,71 @@
"ricochet": "Ricochet",
"explosion_radius": "Explosion radius",
"exit": "Exit",
"reset": "Reset"
"reset": "Zresetuj"
},
"advanced": "Advanced highlighting",
"advanced": "Zaawansowane podświetlanie",
"shot_card": {
"element": {
"primary": "Primary",
"primary": "Główny",
"spaced": "Spaced",
"external": "External",
"gap": "Gap",
"gun": "Gun",
"track": "Tracks"
"external": "Zewnętrzny",
"gap": "Odstęp",
"gun": "Działo",
"track": "Gąsienice"
},
"status": {
"penetration": "{damage}hp penetration",
"blocked": "Blocked",
"ricochet": "Ricochet",
"splash": "{damage}hp splash"
"penetration": "{damage} penetracja punktów wytrzymałości",
"blocked": "Zablokowany",
"ricochet": "Rykoszet",
"splash": "{damage} rozprysk punktów wytrzymałości"
},
"stats": {
"thickness_and_angle": "{thickness}mm {angle}°",
"nominal": "{thickness}mm nominal",
"ricochet_loss": "{loss}% penetration"
"thickness_and_angle": "{thickness}mm {angle}",
"nominal": "{thickness}mm nominalny",
"ricochet_loss": "{loss}% penetracji"
}
}
},
"static": {
"name": "Static armor",
"primary": "Primary",
"spaced": "Spaced",
"external": "External",
"name": "Pancerz statyczny",
"primary": "Główny Pancerz",
"spaced": "Ekran przeciwkumulacyjny",
"external": "Zewnętrzny",
"edit": {
"label": "Edit",
"reset": "Reset"
"label": "Edytuj",
"reset": "Zresetuj"
}
}
},
"meta": {
"back": "Back",
"compare": "Compare",
"back": "Powrót",
"compare": "Porównaj",
"swap": {
"button": "Swap",
"title": "Swap tanks"
"button": "Zamień",
"title": "Zamień czołgi"
},
"vanilla": "Vanilla",
"opentest": "OpenTest",
"full_name": "Full name",
"full_name": "Pełna nazwa",
"nation": "Naród",
"class": "Klasa",
"tier": "Poziom",
"type": "Rodzaj",
"dev_id": "DEV: ID",
"dev_name": "DEV: NAME",
"purchase": "Purchase price",
"restoration": "Restoration price",
"dev_name": "DEV: NAZWA",
"purchase": "Cena zakupu",
"restoration": "Cena odzyskania",
"sale": "Sale price",
"research": "Research XP"
},
"tech_tree": {
"title": "Tech tree",
"battle": "1 battle",
"battles": "{count} battles",
"route": "Route {index} of {count}"
"title": "Drzewko technologiczne",
"battle": "1 bitwa",
"battles": "{count} bitew",
"route": "Ścieżka {index} z {count}"
},
"game_modes": {
"title": "Game mode abilities"
"title": "Umiejętności trybu gry"
},
"review": {
"title": "Review videos",
@@ -518,26 +521,26 @@
"equalize": "Equalize"
},
"modules": {
"title": "Modules",
"stock": "Stock",
"upgrade": "Upgrade"
"title": "Moduły",
"stock": "Standardowy",
"upgrade": "Ulepsz"
},
"equipment": {
"title": "Equipment",
"clear": "Clear"
"title": "Wyposażenie",
"clear": "Wyczyść"
},
"provisions": {
"title": "Provisions",
"clear": "Clear"
"title": "Materiały eksploatacyjne",
"clear": "Wyczyść"
},
"consumables": {
"title": "Consumables",
"clear": "Clear",
"clear": "Wyczyść",
"info": "Yellow consumables do not affect the statistics."
},
"skills": {
"title": "Crew skills",
"clear": "Clear",
"clear": "Wyczyść",
"maximize": "Maximize",
"info": "Yellow skills only apply under special circumstances. Hold Space to quickly toggle between level 0 and 7."
},
@@ -555,49 +558,49 @@
},
"values": {
"shell_index": "Shell {index}",
"at_distance": "At {distance}m",
"at_distance_slider_label": "Distance",
"dispersion_at_distance": "Dispersion at {distance}m",
"gun_flexibility": "Gun flexibility",
"speed": "Speed",
"terrain_coefficients": "Terrain coefficients",
"raw_terrain_coefficients": "Raw terrain coefficients",
"power_to_weight_ratio": "Power to weight ratio",
"hull_traverse_speed": "Hull traverse speed",
"camouflage": "Camouflage",
"reload": "Reload",
"crewCount": "Crew count",
"at_distance": "Z odległości {distance}m",
"at_distance_slider_label": "Zasięg widzenia",
"dispersion_at_distance": "Rozrzut na {distance}m",
"gun_flexibility": "Elastyczność działa",
"speed": "Prędkość",
"terrain_coefficients": "Współczynniki terenu",
"raw_terrain_coefficients": "Surowe współczynniki terenu",
"power_to_weight_ratio": "Stosunek mocy do masy",
"hull_traverse_speed": "Prędkość obrotu kadłuba",
"camouflage": "Kamuflaż",
"reload": "Przeładowanie",
"crewCount": "Liczba członków załogi",
"shellNormalization": "Normalization",
"shellRicochet": "Ricochet",
"dpm": "DPM",
"dpmEffective": "Effective at 60s",
"peekabooDpm": "Peekaboo DPM",
"peekabooDpmInfo": "Damage per minute assuming you only fire after resetting camouflage (or the reload finishes, if it's slower) — at least 10 seconds between shots.",
"shells": "Shells",
"mostOptimalShellIndex": "Most optimal shell",
"shellReloads": "Reloads",
"shellReload": "Reload",
"intraClip": "Intra-clip",
"dpm": "Uszkodzenia na minutę",
"dpmEffective": "Efektywne w 60s",
"peekabooDpm": "Obrażenia na minutę w stylu „wychyl i schowaj”",
"peekabooDpmInfo": "Obrażenia na minutę przy założeniu, że strzelisz tylko po zresetowaniu kamuflażu (lub ponownym załadowaniu, jeśli jest wolniejszy) — co najmniej 10 sekund pomiędzy strzałami.",
"shells": "Pociski",
"mostOptimalShellIndex": "Optymalny pocisk",
"shellReloads": "Przeładowania",
"shellReload": "Przeładowanie",
"intraClip": "Czas przeładowania pocisku",
"caliber": "Caliber",
"penetration": "Penetration",
"damage": "Uszkodzenia",
"damageWithoutAssault": "Damage without assault shells",
"clipDamage": "Clipping potential",
"moduleDamage": "Module damage",
"explosionRadius": "Explosion radius",
"shellVelocity": "Shell velocity",
"aimTime": "Aim time",
"damageWithoutAssault": "Uszkodzenia bez pocisków szturmowych",
"clipDamage": "Potencjał magazynu",
"moduleDamage": "Uszkodzenia modułów",
"explosionRadius": "Promień eksplozji",
"shellVelocity": "Prędkość pocisku",
"aimTime": "Czas celowania",
"dispersion": "Still",
"dispersionMoving": "Moving",
"dispersionHullTraversing": "Hull traversing",
"dispersionTurretTraversing": "Turret traversing",
"dispersionShooting": "Shooting",
"dispersionGunDamaged": "Gun damaged",
"gunDepression": "Depression",
"gunElevation": "Elevation",
"gunFrontalDepression": "Frontal depression",
"gunFrontalElevation": "Frontal elevation",
"gunRearDepression": "Rear depression",
"dispersionMoving": "Podczas ruchu",
"dispersionHullTraversing": "Obrót kadłuba",
"dispersionTurretTraversing": "Obrót wieży",
"dispersionShooting": "Strzelanie",
"dispersionGunDamaged": "Uszkodzone Działo",
"gunDepression": "Limit obrotu działa w dół",
"gunElevation": "Limit obrotu działa w górę",
"gunFrontalDepression": "Przedni limit obrotu działa w dół",
"gunFrontalElevation": "Przedni limit obrotu działa w górę",
"gunRearDepression": "Tylne opuszczanie działa",
"gunRearElevation": "Rear elevation",
"azimuthLeft": "Azimuth left",
"azimuthRight": "Azimuth right",
@@ -621,9 +624,10 @@
"hullTraverseSoftTerrain": "Soft",
"health": "Health",
"fireChance": "Fire chance",
"trackRepairTime": "Track repair time",
"viewRange": "View range",
"camouflageStill": "Still",
"camouflageMoving": "Moving",
"camouflageMoving": "Podczas ruchu",
"camouflageShootingStill": "Shooting while still",
"camouflageShootingMoving": "Shooting while moving",
"camouflageCaughtOnFire": "Caught on fire",
@@ -645,7 +649,7 @@
"crew": {
"title": "Crew training",
"info": "represents the other roles a crew member can take.",
"count": "Crew count",
"count": "Liczba członków załogi",
"commander": "Commander",
"gunner": "Gunner",
"driver": "Driver",
@@ -653,14 +657,14 @@
"loader": "Loader"
},
"maneuverability": {
"title": "Maneuverability"
"title": "Mobilność "
},
"survivability": {
"title": "Survivability"
"title": "Przetrwanie"
}
},
"compare": {
"name": "Compare",
"name": "Porównaj",
"button": "Compare tanks",
"description": "Compare tank statistics",
"seo_description": "Compare tanks statistics and loadouts in World of Tanks Blitz",
@@ -670,7 +674,7 @@
"title": "Add tanks",
"description": "Select tanks to add to comparison"
},
"clear": "Clear",
"clear": "Wyczyść",
"deltas": {
"none": "No deltas",
"percentage": "Percentage",
@@ -683,17 +687,17 @@
},
"table": {
"equipment": {
"clear": "Clear",
"clear": "Wyczyść",
"apply_to_all": "Apply to all"
},
"skills": {
"clear": "Clear",
"clear": "Wyczyść",
"maximize": "Maximize",
"equalize": "Equalize"
},
"modules": {
"stock": "Stock",
"upgrade": "Upgrade"
"stock": "Standardowy",
"upgrade": "Ulepsz"
},
"provisions": {
"clear": "Clear this tank",
@@ -758,14 +762,14 @@
"seo_description": "Track your stats in real time as you play World of Tanks Blitz",
"search": {
"lookup": "Look up a player to get started",
"hint": "Search players...",
"searching": "Searching...",
"hint": "Szukaj graczy...",
"searching": "Wyszukiwanie...",
"no_results": "No players found"
},
"controls": {
"title": "Tracking {name}",
"subtitle": "Since {time}",
"reset": "Reset",
"reset": "Zresetuj",
"change": "Change"
},
"no_cc": {
@@ -789,7 +793,7 @@
"description": "Rank Blitz tanks",
"seo_description": "Explore tier lists ranking tanks from best to worst, based on player ratings and statistical performance in World of Tanks Blitz",
"buttons": {
"reset": "Reset",
"reset": "Zresetuj",
"share": "Share"
}
},
@@ -825,7 +829,7 @@
"success": {
"title": "Copy URL",
"info": "Generated the URL for {name}. The embed will start tracking as soon as the URL is pasted. Reset at any time by right-clicking on PC or long-pressing on mobile and then selecting \"Reset\".",
"back": "Back",
"back": "Powrót",
"copy": "Copy URL"
}
},
@@ -836,7 +840,7 @@
},
"customize": {
"title": "Customize",
"reset": "Reset",
"reset": "Zresetuj",
"description": "Bells and whistles of your embed"
}
},
@@ -919,7 +923,6 @@
"copyright": "Unaffiliated with Wargaming or any service",
"about": {
"title": "About",
"credits": "Credits",
"discord": "Discord",
"github": "GitHub",
"patches": "Patches"
+14 -11
View File
@@ -1,9 +1,4 @@
{
"test": {
"default": "teste",
"long": "This is a long test string. You MUST NEVER see this in the UI. My favorite fruit is {fruit} because it has the color of {color}. I like it so much that I will say its name 3 times! {fruit}! {fruit}! {fruit}! Once again, it's {color}, not {not_color}.",
"fox": "A ágil raposa castanha salta por cima do cão preguiçoso."
},
"common": {
"name": "BlitzKit",
"units": {
@@ -41,7 +36,7 @@
"development": "Development",
"opentest": "TesteAberto",
"preview": "Preview",
"production": "Production"
"production": "Produção"
},
"locales": {
"en": "Inglês",
@@ -187,13 +182,14 @@
},
"website": {
"redirect": {
"title": "301: moved permanently",
"title": "301: movido permanentemente",
"body": "This page has moved. Redirecting..."
},
"settings": {
"title": "Settings",
"title": "Configurações",
"description": "Change BlitzKit settings",
"dev_mode": "Modo de desenvolvimento",
"auto_load_models": "Auto-load models",
"language": "Idioma"
},
"common": {
@@ -206,9 +202,9 @@
"copied": "Copiado!"
},
"tank_search": {
"select_dialog_title": "Search for tanks",
"select_dialog_title": "Procura por tanque",
"swap_dialog_title": "Swap tank",
"clear_filters": "Clear filters",
"clear_filters": "Limpar filtros",
"login": "Login to use this",
"search_bar_hint": "Procurar tanques...",
"recent": "Visto recentemente",
@@ -306,6 +302,12 @@
}
},
"tools": {
"credits": {
"name": "Credits",
"button": "View people",
"description": "Contributors of BlitzKit",
"seo_description": "BlitzKit is the work of many"
},
"playlist": {
"reset": "Restaurar",
"shuffle": "Shuffle",
@@ -412,6 +414,7 @@
}
},
"sandbox": {
"load_model": "Load model",
"poses": {
"hull_down": "Casco para baixo",
"face_hug": "Abraço facial",
@@ -621,6 +624,7 @@
"hullTraverseSoftTerrain": "Macio",
"health": "Vida",
"fireChance": "Chance de incêndio",
"trackRepairTime": "Track repair time",
"viewRange": "Alcance de visão",
"camouflageStill": "Parado",
"camouflageMoving": "Em movimento",
@@ -919,7 +923,6 @@
"copyright": "Não afiliado à Wargaming ou qualquer serviço",
"about": {
"title": "Sobre",
"credits": "Créditos",
"discord": "Discord",
"github": "GitHub",
"patches": "Atualizações"
+13 -10
View File
@@ -1,9 +1,4 @@
{
"test": {
"default": "тест",
"long": "Это длинная тестовая строка. Вы НИКОГДА не должны видеть её в пользовательском интерфейсе. Мой любимый фрукт — {fruit}, потому что это цвет {color}. Он мне так нравится, так что я скажу его 3 раза {fruit}! {fruit}! {fruit}! И ещё раз, это {color}, а не {not_color}.",
"fox": "Съешь же ещё этих мягких французских булок, да выпей чаю."
},
"common": {
"name": "BlitzKit",
"units": {
@@ -38,10 +33,10 @@
},
"branches": {
"beta": "Бета",
"development": "Development",
"development": "Разработка",
"opentest": "Открытый тест",
"preview": "Preview",
"production": "Production"
"preview": "Предпросмотр",
"production": "Производство"
},
"locales": {
"en": "Английский",
@@ -194,6 +189,7 @@
"title": "Настройки",
"description": "Изменить настройки BlitzKit",
"dev_mode": "Режим разработчика",
"auto_load_models": "Автозагрузка моделей",
"language": "Язык"
},
"common": {
@@ -306,6 +302,12 @@
}
},
"tools": {
"credits": {
"name": "Credits",
"button": "View people",
"description": "Contributors of BlitzKit",
"seo_description": "BlitzKit is the work of many"
},
"playlist": {
"reset": "Сброс",
"shuffle": "Перемешать",
@@ -412,6 +414,7 @@
}
},
"sandbox": {
"load_model": "Загрузить модель",
"poses": {
"hull_down": "За холмом",
"face_hug": "В упор",
@@ -572,7 +575,7 @@
"dpm": "ДПМ",
"dpmEffective": "Эффективность за 60с",
"peekabooDpm": "Peekaboo DPM",
"peekabooDpmInfo": "Damage per minute assuming you only fire after resetting camouflage (or the reload finishes, if it's slower) — at least 10 seconds between shots.",
"peekabooDpmInfo": "Урон в минуту при условии, если вы сбросите камуфляж(или перезарядка окончена, если она медленнее) - не менее 10 секунд между выстрелами.",
"shells": "Снарядов в барабане",
"mostOptimalShellIndex": "Самый эффективный снаряд",
"shellReloads": "Перезарядка",
@@ -621,6 +624,7 @@
"hullTraverseSoftTerrain": "На мягком грунте",
"health": "Прочность",
"fireChance": "Вероятность возгорания",
"trackRepairTime": "Track repair time",
"viewRange": "Радиус обзора",
"camouflageStill": "Когда неподвижен",
"camouflageMoving": "В движении",
@@ -919,7 +923,6 @@
"copyright": "Не связан с Wargaming или иным сервисом",
"about": {
"title": "О сайте",
"credits": "Авторы",
"discord": "Discord",
"github": "GitHub",
"patches": "Патчи"
+10 -7
View File
@@ -1,9 +1,4 @@
{
"test": {
"default": "тест",
"long": "{color}{fruit}",
"fox": "Швидка бура лисиця перестрибує через лінивого собаку."
},
"common": {
"name": "BlitzKit",
"units": {
@@ -194,6 +189,7 @@
"title": "Налаштування",
"description": "Змінити налаштування BlitzKit ",
"dev_mode": "Режим розробника",
"auto_load_models": "Auto-load models",
"language": "Мова"
},
"common": {
@@ -306,6 +302,12 @@
}
},
"tools": {
"credits": {
"name": "Credits",
"button": "View people",
"description": "Contributors of BlitzKit",
"seo_description": "BlitzKit is the work of many"
},
"playlist": {
"reset": "Скинути",
"shuffle": "Перемішати",
@@ -363,7 +365,7 @@
"title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "",
"reveal": "Reveal"
"reveal": "Рівень"
},
"clear": "Clear",
"select_all": "Вибрати все",
@@ -412,6 +414,7 @@
}
},
"sandbox": {
"load_model": "Load model",
"poses": {
"hull_down": "Від башти",
"face_hug": "Клінч",
@@ -621,6 +624,7 @@
"hullTraverseSoftTerrain": "Soft",
"health": "Здоров'я",
"fireChance": "Fire chance",
"trackRepairTime": "Track repair time",
"viewRange": "View range",
"camouflageStill": "Нерухомо",
"camouflageMoving": "У русі",
@@ -919,7 +923,6 @@
"copyright": "Unaffiliated with Wargaming or any service",
"about": {
"title": "About",
"credits": "Credits",
"discord": "Discord",
"github": "GitHub",
"patches": "Patches"
+9 -6
View File
@@ -1,9 +1,4 @@
{
"test": {
"default": "测试",
"long": "这是一个测试用长字符串。你绝对不会在用户界面中看见这些。我最喜欢的水果是{fruit}因为它的颜色是{color}的。我如此喜欢它,以至于我会把它的名字念3次!{fruit}!{fruit}!{fruit}!重申一次,它是{color}的而不是{not_color}的。",
"fox": "褐色的狐狸迅速地跳过那只懒狗"
},
"common": {
"name": "BlitzKit",
"units": {
@@ -194,6 +189,7 @@
"title": "设置",
"description": "更改 BlitzKit 设置",
"dev_mode": "开发者模式",
"auto_load_models": "Auto-load models",
"language": "语言"
},
"common": {
@@ -306,6 +302,12 @@
}
},
"tools": {
"credits": {
"name": "Credits",
"button": "View people",
"description": "Contributors of BlitzKit",
"seo_description": "BlitzKit is the work of many"
},
"playlist": {
"reset": "重置",
"shuffle": "换一批",
@@ -412,6 +414,7 @@
}
},
"sandbox": {
"load_model": "Load model",
"poses": {
"hull_down": "俯角视角",
"face_hug": "顶牛视角",
@@ -621,6 +624,7 @@
"hullTraverseSoftTerrain": "水面上车转速率",
"health": "血量",
"fireChance": "发动机受击时着火概率",
"trackRepairTime": "Track repair time",
"viewRange": "视野范围",
"camouflageStill": "静止隐蔽",
"camouflageMoving": "移动时",
@@ -919,7 +923,6 @@
"copyright": "本网站与 Wargaming战游网 或任何服务无关",
"about": {
"title": "关于",
"credits": "鸣谢",
"discord": "Discord",
"github": "GitHub",
"patches": "补丁"
@@ -13,4 +13,5 @@ message Camouflage {
I18nString name = 2;
optional I18nString tank_name = 3;
optional I18nString tank_name_full = 4;
optional uint32 tank_id = 5;
}
+3 -1
View File
@@ -5,7 +5,9 @@
"discover-ids": "bun src/discoverIds/index.ts",
"build-protos": "bun src/protos.ts",
"record-performance": "bun src/recordPerformance/index.ts",
"download-client": "bun src/downloadClient/index.ts"
"download-client": "bun src/downloadClient/index.ts",
"download-smart-dlc": "bun src/downloadSmartDLC/index.ts",
"vfs": "bun src/vfs/index.ts"
},
"dependencies": {
"@blitzkit/closed": "workspace:*",
+6 -5
View File
@@ -135,7 +135,7 @@ let regionI = 0;
console.log("Starting discovery loop...");
atMaxRate(async ({ _break: break1 }) => {
await atMaxRate(async ({ _break: break1 }) => {
if (regions.length === 0) {
console.log("All regions exhausted, exiting discovery loop...");
return break1();
@@ -166,7 +166,6 @@ atMaxRate(async ({ _break: break1 }) => {
fields
}&account_id=${accountIds}`;
atMaxRate(async ({ _break: break2 }) => {
if (idRange.length < MAX_IDS_PER_CALL) {
console.log(
`Removing region ${region.domain} removed due to ID range exhaustion...`,
@@ -174,10 +173,12 @@ atMaxRate(async ({ _break: break1 }) => {
regions.splice(regionI, 1);
}
const response = await fetch(url);
const data = (await response.json()) as QuickInfo;
await atMaxRate(async ({ _break: break2 }) => {
const data = await fetch(url)
.then((response) => response.json() as Promise<QuickInfo>)
.catch(() => null);
if (data.status !== "ok") {
if (data === null || data.status !== "ok") {
console.warn("Failed API call; trying again...");
return;
}
+4 -2
View File
@@ -20,7 +20,9 @@ interface MethodContext {
let lastT = 0;
const maxDelta = 1000 / API_RATE;
const DELTA_BUFFER = 0;
export async function atMaxRate(method: (context: MethodContext) => void) {
export async function atMaxRate(
method: (context: MethodContext) => void | Promise<void>,
) {
let isBroken = false;
const context: MethodContext = {
@@ -39,7 +41,7 @@ export async function atMaxRate(method: (context: MethodContext) => void) {
await sleep(maxDelta - delta + DELTA_BUFFER);
}
method(context);
await method(context);
lastT = t;
}
}
+23 -5
View File
@@ -9,6 +9,8 @@ import {
} from "./constants";
import { QuickGitHubRelease } from "./types";
const DEPOT_DOWNLOADER_RETRY_TIME = 30_000;
const provider = assertSecret(import.meta.env.CLIENT_PROVIDER);
const source = assertSecret(import.meta.env.CLIENT_SOURCE);
const dir = assertSecret(import.meta.env.CLIENT_DIR);
@@ -43,11 +45,27 @@ if (provider === "steam") {
const username = assertSecret(import.meta.env.STEAM_USERNAME);
const password = assertSecret(import.meta.env.STEAM_PASSWORD);
await $`${DEPOT_DOWNLOADER_EXECUTABLE} -os ${
platform
} -app ${id} -username ${username} -password ${
password
} -dir ${dir} -max-downloads ${DEPOT_DOWNLOADER_MAX_DOWNLOADS}`;
while (true) {
const result = await $`${
DEPOT_DOWNLOADER_EXECUTABLE
} -os ${platform} -app ${id} -username ${
username
} -password ${password} -dir ${dir} -max-downloads ${
DEPOT_DOWNLOADER_MAX_DOWNLOADS
}`.nothrow();
if (result.exitCode === 0) {
console.log("Client downloaded successfully!");
break;
}
console.error(
`DepotDownloader failed (exit ${result.exitCode}). Retrying in ${DEPOT_DOWNLOADER_RETRY_TIME / 1000} seconds...`,
);
await new Promise((resolve) =>
setTimeout(resolve, DEPOT_DOWNLOADER_RETRY_TIME),
);
}
} else if (provider === "zip") {
const url = assertSecret(import.meta.env.CLIENT_SOURCE);
@@ -2,9 +2,16 @@ import { dvp } from "@blitzkit/closed";
import { assertSecret } from "@blitzkit/core";
import { mkdir, writeFile } from "fs/promises";
import { parse as parsePath } from "path";
import ProgressBar from "progress";
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
import { PATCHES_ROOT, vfs } from "./buildAssets/constants";
import { RegionsPublish } from "../types/regionsPublish";
import { vfs } from "../vfs/vfs";
const CLIENT_DIR = assertSecret(import.meta.env.CLIENT_DIR);
const regionsPublish = await vfs.yaml<RegionsPublish>(
"Data/regions_publish.yaml",
);
const [smartDlcUrl] = regionsPublish.Common.smartDlcUrls;
const versionTextFile = await vfs.text(`Data/version.txt`);
const currentVersion = versionTextFile
@@ -18,9 +25,7 @@ console.log(`Installing patches for ${currentVersion}...`);
let patchIndex = 1;
while (true) {
const response = await fetch(
`${assertSecret(
import.meta.env.WOTB_DLC_CDN,
)}/dlc/s${currentVersion}_${patchIndex}.yaml`,
`${smartDlcUrl}/s${currentVersion}_${patchIndex}.yaml`,
);
console.log(patchIndex, response.status);
@@ -30,51 +35,34 @@ while (true) {
console.log(`Applying patch ${patchIndex}...`);
const data = parseYaml(await response.text());
const dvpm = await fetch(
`${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${data.dx11}`,
).then((response) => response.arrayBuffer());
const dvpm = await fetch(`${smartDlcUrl}/${data.dx11}`).then((response) =>
response.arrayBuffer(),
);
const dvpd = await fetch(
`${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${data.dx11.replace(
".dvpm",
".dvpd",
)}`,
`${smartDlcUrl}/${data.dx11.replace(".dvpm", ".dvpd")}`,
).then((response) => response.arrayBuffer());
const files = await dvp(dvpm, dvpd);
const bar = new ProgressBar(
`Patching ${files.length} files :bar`,
files.length,
);
for (const { path, data } of files) {
console.log(`Patching "${path}"...`);
const { dir } = parsePath(path);
try {
await mkdir(`${PATCHES_ROOT}/Data/${dir}`, { recursive: true });
} catch (error) {
console.warn(`Failed to make directory "${dir}"`);
}
await mkdir(`${CLIENT_DIR}/Data/${dir}`, { recursive: true });
const buffer = Buffer.from(data);
await writeFile(`${PATCHES_ROOT}/Data/${path}`, new Uint8Array(buffer));
bar.tick();
await writeFile(`${CLIENT_DIR}/Data/${path}`, new Uint8Array(buffer));
}
console.log("patches done for patch");
if ("dynamicContentLocalizationsDir" in data) {
console.log(
`${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${
data.dynamicContentLocalizationsDir
}/en.yaml`,
`${smartDlcUrl}/${data.dynamicContentLocalizationsDir}/en.yaml`,
);
const localizationsResponse = await fetch(
`${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${
data.dynamicContentLocalizationsDir
}/en.yaml`,
`${smartDlcUrl}/${data.dynamicContentLocalizationsDir}/en.yaml`,
);
console.log("fetched");
const newStrings = parseYaml(await localizationsResponse.text());
@@ -86,10 +74,10 @@ while (true) {
const patchedContent = stringifyYaml(patchedStrings);
console.log("stringified");
await mkdir(`${PATCHES_ROOT}/Data/Strings`, { recursive: true });
await mkdir(`${CLIENT_DIR}/Data/Strings`, { recursive: true });
console.log("mkdir");
await writeFile(
`${PATCHES_ROOT}/Data/Strings/en.yaml`,
`${CLIENT_DIR}/Data/Strings/en.yaml`,
new Uint8Array(Buffer.from(patchedContent)),
);
console.log("file");
@@ -0,0 +1,7 @@
export interface RegionsPublish {
Common: Common;
}
interface Common {
smartDlcUrls: string[];
}
+23
View File
@@ -0,0 +1,23 @@
import { vfs } from "./vfs";
const command = Bun.argv[2];
const path = Bun.argv[3];
if (!path) {
console.error("Gimme a path to assess dumb ass");
process.exit(1);
}
if (command === "cat") {
const content = await vfs.file(path);
await Bun.write(Bun.stdout, content);
} else if (command === "ls") {
const files = await vfs.dir(path);
for (const file of files) {
console.log(file);
}
} else if (command === "yaml") {
const content = await vfs.yaml(path);
await Bun.write(Bun.stdout, JSON.stringify(content, null, 2));
}
+4
View File
@@ -0,0 +1,4 @@
import { assertSecret } from "@blitzkit/core";
import { LocalVFS } from "@blitzkit/core/src/blitzkit/vfs/local";
export const vfs = new LocalVFS(assertSecret(import.meta.env.CLIENT_DIR));
Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

@@ -1,38 +0,0 @@
---
import { tankIcon } from "@blitzkit/core";
import { Flex, Heading, Link, Strong, Text } from "@radix-ui/themes";
---
<Flex justify="center" px="4">
<Link
color="gray"
highContrast
style={{ display: "content" }}
href="/docs/guides/christmas-2025"
underline="none"
>
<Flex
mt="3"
px="5"
py="4"
style={{
borderRadius: "var(--radius-3)",
backgroundImage: "url(https://i.imgur.com/LRYmSkg.png)",
backgroundSize: "cover",
backgroundPosition: "center",
boxSizing: "border-box",
}}
align="center"
gap="5"
>
<img src={tankIcon(25169)} style={{ height: "min(15vw, 5rem)" }} />
<Flex direction="column">
<Heading size={{ initial: "4", sm: "6" }}>Christmas 2025</Heading>
<Text size={{ initial: "2", sm: "3" }}>
Win the <Strong>GSOR the TANK</Strong> and more!
</Text>
</Flex>
</Flex>
</Link>
</Flex>
@@ -0,0 +1,36 @@
.outer {
padding: 0 var(--space-4);
margin-top: var(--space-3);
img {
width: 1.25em;
height: 1.25em;
border-radius: 50%;
}
.center {
position: relative;
background: linear-gradient(var(--pink-3), var(--purple-2));
box-shadow: var(--shadow-3);
max-width: 40rem;
padding: var(--space-5);
border-radius: var(--radius-4);
.important {
position: absolute;
top: 0;
left: 50%;
transform: translate(-50%, -50%);
background-color: inherit;
padding: var(--space-3);
}
}
}
@@ -0,0 +1,48 @@
import { HeartFilledIcon } from "@radix-ui/react-icons";
import { Button, Em, Flex, Strong, Text } from "@radix-ui/themes";
import { donationTiers } from "../../config/donations";
import styles from "./index.module.css";
export function Beg() {
return (
<Flex align="center" gap="4" direction="column" className={styles.outer}>
<Text size="3" className={styles.important}>
<Flex gap="2" align="center">
<img src="https://github.com/tresabhi.png" />
<Em>We need your attention</Em>
</Flex>
</Text>
<Flex direction="column" className={styles.center} gap="4">
<Flex direction="column" gap="3">
<Text color="tomato">
We can no longer keep BlitzKit sustainable for <Strong>free</Strong>
.
</Text>
<Text>
We want to add tank modules, player statistics, rebalancing patch
notes, bring back the Discord bot, and more.
</Text>
<Text>
But paying out of pocket makes that impossible. We are an earnest
non-profit, so consider donating to make the above possible. 100% of
your money goes back into BlitzKit.
</Text>
</Flex>
<Flex gap="3">
{donationTiers.map(({ name, value, link }) => (
<a target="_blank" key={name} href={link}>
<Button size="3" color="crimson">
{value === null && <HeartFilledIcon />}
{value === null ? "Custom" : `$${value}`}
</Button>
</a>
))}
</Flex>
</Flex>
</Flex>
);
}
@@ -16,6 +16,11 @@
cursor: pointer;
&[data-disabled="true"] {
cursor: default;
opacity: 0.5;
}
&[data-size="minor"] {
font-size: 0.7em;
padding: var(--space-2) var(--space-3);
@@ -81,5 +86,9 @@
&[data-variant="surface"],
&[data-variant="outline"] {
outline: var(--space-1) solid var(--accent-6);
&[data-high-contrast="true"] {
outline: var(--space-1) solid var(--accent-11);
}
}
}
@@ -8,7 +8,7 @@ export interface ButtonProps extends ComponentProps<"button"> {
radius?: `${1 | 2 | 3}` | "max";
variant?: "solid" | "surface" | "soft" | "inverted" | "ghost" | "outline";
size?: "regular" | "minor" | "major";
highContrast?: boolean; // TODO: add high contrast
highContrast?: boolean;
listChild?: boolean;
parentListChild?: boolean;
@@ -39,6 +39,8 @@ export function Button({
listChild,
parentListChild,
disabled,
highContrast,
...props
}: ButtonProps) {
return (
@@ -51,6 +53,9 @@ export function Button({
data-size={size}
data-list-child={listChild}
data-parent-list-child={parentListChild}
data-high-contrast={highContrast}
data-disabled={disabled}
disabled={disabled}
{...props}
/>
);
@@ -0,0 +1,19 @@
.checkbox {
display: flex;
align-items: center;
gap: var(--space-3);
.control {
width: 1.25em;
height: 1.25em;
margin: 0;
accent-color: var(--accent-9);
}
&,
* {
cursor: pointer;
}
}
@@ -0,0 +1,35 @@
import { useId, type ReactNode } from "react";
import { Text } from "../Text";
import styles from "./index.module.css";
export interface CheckboxProps {
checked: boolean;
onCheckedChange(checked: boolean): void;
children?: ReactNode;
}
export function Checkbox({
checked,
onCheckedChange,
children,
}: CheckboxProps) {
const id = useId();
return (
<div className={styles.checkbox}>
<input
className={styles.control}
type="checkbox"
id={id}
checked={checked}
onChange={(event) => onCheckedChange(event.target.checked)}
/>
{children && (
<label htmlFor={id}>
<Text lowContrast>{children}</Text>
</label>
)}
</div>
);
}
@@ -1,4 +1,9 @@
import { alias, availableProvisions, permanentSkills } from "@blitzkit/core";
import {
alias,
availableProvisions,
MAX_SKILL_LEVEL,
permanentSkills,
} from "@blitzkit/core";
import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
import { ComponentPlaceholderIcon } from "@radix-ui/react-icons";
import {
@@ -153,7 +158,7 @@ export function CompareTable({ stats }: CompareTableProps) {
onClick={() => {
CompareEphemeral.mutate((draft) => {
Object.keys(draft.crewSkills).forEach((skill) => {
draft.crewSkills[skill] = 7;
draft.crewSkills[skill] = MAX_SKILL_LEVEL;
});
});
}}
@@ -3,9 +3,9 @@ import { useEffect, useState, type ReactNode } from "react";
import type { MaybeSkeletonComponentProps } from "../types/maybeSkeletonComponentProps";
import { InlineSkeleton } from "./InlineSkeleton";
const TIME = 1764898200 * 1000;
const TIME = 1792598400 * 1000;
export function Giveaway({ skeleton }: MaybeSkeletonComponentProps) {
export function Countdown({ skeleton }: MaybeSkeletonComponentProps) {
const [timeLeft, setTimeLeft] = useState<number>(TIME - Date.now());
useEffect(() => {
@@ -31,14 +31,14 @@ export function Giveaway({ skeleton }: MaybeSkeletonComponentProps) {
string = (
<>
in{" "}
revealed in{" "}
<Code highContrast>
{hours}h {minutes}m {seconds}s
</Code>
</>
);
} else {
string = "has ended. Come again later!";
string = "have been revealed.";
}
return (
@@ -47,7 +47,7 @@ export function Giveaway({ skeleton }: MaybeSkeletonComponentProps) {
color="gray"
highContrast
style={{ display: "content" }}
href="https://discord.gg/njQdBhNS?event=1446208951136813117"
href="https://preview.blitzkit.app/tanks"
underline="none"
>
<Flex
@@ -56,17 +56,17 @@ export function Giveaway({ skeleton }: MaybeSkeletonComponentProps) {
py="4"
style={{
borderRadius: "var(--radius-3)",
backgroundImage: "url(https://i.imgur.com/LRYmSkg.png)",
backgroundImage: "url(https://i.imgur.com/xgngxoy.png)",
backgroundSize: "cover",
backgroundPosition: "center",
boxSizing: "border-box",
boxShadow: "0 0 var(--space-7) var(--blue-a5)",
boxShadow: "0 0 var(--space-7) var(--white-a2)",
}}
align="center"
gap="5"
>
<img
src="https://i.imgur.com/mvmJQgB.png"
src="https://i.imgur.com/kXV3r3P.png"
style={{
height: "min(15vw, 5rem)",
filter: "drop-shadow(0.5rem 0.5rem 1rem #00000080)",
@@ -75,11 +75,11 @@ export function Giveaway({ skeleton }: MaybeSkeletonComponentProps) {
<Flex direction="column">
<Heading size={{ initial: "4", sm: "6" }}>
Music Box giveaway {!skeleton && string}{" "}
Tier XIs {!skeleton && string}{" "}
{skeleton && <InlineSkeleton width="10rem" />}
</Heading>
<Text size={{ initial: "2", sm: "3" }}>
Join the BlitzKit Discord server event
Get ready to go to the Tankopedia!
</Text>
</Flex>
</Flex>
Loaded 100 of 205 files, more files were not shown because too many files have changed in this diff. Show more