Compare commits

..
8 Commits
58 changed files with 1567 additions and 2539 deletions

No files matched your search

+1 -1
View File
@@ -10,7 +10,7 @@ concurrency:
jobs:
build:
name: Build bot
name: Build
runs-on: ubuntu-latest
steps:
+1 -1
View File
@@ -35,7 +35,7 @@ env:
jobs:
build:
name: Build website
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
-2
View File
@@ -75,7 +75,6 @@
"DIDS",
"donotremove",
"drei",
"Droodles",
"dvpd",
"dvpl",
"DVPLCRC",
@@ -102,7 +101,6 @@
"gtag",
"haha",
"Herdbreaker",
"Hetz",
"highp",
"iife",
"imgur",
+48 -177
View File
@@ -1,13 +1,11 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "blitzkit",
"dependencies": {
"@prisma/client": "^6.18.0",
"prisma": "^6.18.0",
"sharp": "^0.34.5",
"typescript": "^5.9.3",
},
},
@@ -30,7 +28,6 @@
"@radix-ui/themes": "^3.2.1",
"@resvg/resvg-js": "^2.6.2",
"@types/adm-zip": "^0.5.7",
"@types/bun": "^1.3.9",
"@types/lodash-es": "^4.17.12",
"@types/markdown-escape": "^1.1.3",
"@types/react": "^19.2.2",
@@ -413,15 +410,15 @@
"@dimforge/rapier3d-compat": ["@dimforge/[email protected]", "", {}, "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="],
"@discordjs/builders": ["@discordjs/[email protected]3.1", "", { "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.33", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w=="],
"@discordjs/builders": ["@discordjs/[email protected]1.3", "", { "dependencies": { "@discordjs/formatters": "^0.6.1", "@discordjs/util": "^1.1.1", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.16", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-p3kf5eV49CJiRTfhtutUCeivSyQ/l2JlKodW1ZquRwwvlOWmG9+6jFShX6x8rUiYhnP6wKI96rgN/SXMy5e5aw=="],
"@discordjs/collection": ["@discordjs/[email protected]", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="],
"@discordjs/formatters": ["@discordjs/[email protected].2", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ=="],
"@discordjs/formatters": ["@discordjs/[email protected].1", "", { "dependencies": { "discord-api-types": "^0.38.1" } }, "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg=="],
"@discordjs/rest": ["@discordjs/[email protected]", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.16", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w=="],
"@discordjs/util": ["@discordjs/util@1.2.0", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg=="],
"@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="],
"@discordjs/ws": ["@discordjs/[email protected]", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="],
@@ -445,7 +442,7 @@
"@emmetio/stream-reader-utils": ["@emmetio/[email protected]", "", {}, "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A=="],
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
"@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="],
@@ -517,53 +514,49 @@
"@img/colour": ["@img/[email protected]", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="],
"@img/sharp-darwin-arm64": ["@img/[email protected].5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
"@img/sharp-darwin-arm64": ["@img/[email protected].4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.3" }, "os": "darwin", "cpu": "arm64" }, "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA=="],
"@img/sharp-darwin-x64": ["@img/[email protected].5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
"@img/sharp-darwin-x64": ["@img/[email protected].4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.3" }, "os": "darwin", "cpu": "x64" }, "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg=="],
"@img/sharp-libvips-darwin-arm64": ["@img/[email protected].4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
"@img/sharp-libvips-darwin-arm64": ["@img/[email protected].3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw=="],
"@img/sharp-libvips-darwin-x64": ["@img/[email protected].4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
"@img/sharp-libvips-darwin-x64": ["@img/[email protected].3", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA=="],
"@img/sharp-libvips-linux-arm": ["@img/[email protected].4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
"@img/sharp-libvips-linux-arm": ["@img/[email protected].3", "", { "os": "linux", "cpu": "arm" }, "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA=="],
"@img/sharp-libvips-linux-arm64": ["@img/[email protected].4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
"@img/sharp-libvips-linux-arm64": ["@img/[email protected].3", "", { "os": "linux", "cpu": "arm64" }, "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ=="],
"@img/sharp-libvips-linux-ppc64": ["@img/[email protected].4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
"@img/sharp-libvips-linux-ppc64": ["@img/[email protected].3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg=="],
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x[email protected].4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm[email protected].3", "", { "os": "linux", "cpu": "arm64" }, "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm[email protected].4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x[email protected].3", "", { "os": "linux", "cpu": "x64" }, "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linux[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
"@img/sharp-linux-arm": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.3" }, "os": "linux", "cpu": "arm" }, "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA=="],
"@img/sharp-linux-arm": ["@img/[email protected].5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm[email protected].5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc[email protected].4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.3" }, "os": "linux", "cpu": "ppc64" }, "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ=="],
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.3" }, "os": "linux", "cpu": "s390x" }, "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw=="],
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv[email protected].5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x[email protected].4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA=="],
"@img/sharp-linux-x64": ["@img/[email protected].5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl[email protected].4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.4", "", { "dependencies": { "@emnapi/runtime": "^1.5.0" }, "cpu": "none" }, "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x[email protected].5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm[email protected].4", "", { "os": "win32", "cpu": "arm64" }, "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA=="],
"@img/sharp-wasm32": ["@img/sharp-wasm[email protected].5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia[email protected].4", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm[email protected].5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
"@img/sharp-win32-ia32": ["@img/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
"@img/sharp-win32-x64": ["@img/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x[email protected].4", "", { "os": "win32", "cpu": "x64" }, "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig=="],
"@jridgewell/gen-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
@@ -775,7 +768,7 @@
"@radix-ui/rect": ["@radix-ui/[email protected]", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
"@radix-ui/themes": ["@radix-ui/themes@3.3.0", "", { "dependencies": { "@radix-ui/colors": "^3.0.0", "classnames": "^2.3.2", "radix-ui": "^1.1.3", "react-remove-scroll-bar": "^2.3.8" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I0/h2CRNTpYNB7Mi3xFIvSsQq5a108d7kK8dTO5zp5b9HR5QJXKag6B8tjpz2ITkVYkFdkGk45doNkSr7OxwNw=="],
"@radix-ui/themes": ["@radix-ui/themes@3.2.1", "", { "dependencies": { "@radix-ui/colors": "^3.0.0", "classnames": "^2.3.2", "radix-ui": "^1.1.3", "react-remove-scroll-bar": "^2.3.8" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-WJL2YKAGItkunwm3O4cLTFKCGJTfAfF6Hmq7f5bCo1ggqC9qJQ/wfg/25AAN72aoEM1yqXZQ+pslsw48AFR0Xg=="],
"@react-three/drei": ["@react-three/[email protected]", "", { "dependencies": { "@babel/runtime": "^7.26.0", "@mediapipe/tasks-vision": "0.10.17", "@monogrid/gainmap-js": "^3.0.6", "@use-gesture/react": "^10.3.1", "camera-controls": "^3.1.0", "cross-env": "^7.0.3", "detect-gpu": "^5.0.56", "glsl-noise": "^0.0.0", "hls.js": "^1.5.17", "maath": "^0.10.8", "meshline": "^3.3.1", "stats-gl": "^2.2.8", "stats.js": "^0.17.0", "suspend-react": "^0.1.3", "three-mesh-bvh": "^0.8.3", "three-stdlib": "^2.35.6", "troika-three-text": "^0.52.4", "tunnel-rat": "^0.1.2", "use-sync-external-store": "^1.4.0", "utility-types": "^3.11.0", "zustand": "^5.0.1" }, "peerDependencies": { "@react-three/fiber": "^9.0.0", "react": "^19", "react-dom": "^19", "three": ">=0.159" }, "optionalPeers": ["react-dom"] }, "sha512-ZSFwRlRaa4zjtB7yHO6Q9xQGuyDCzE7whXBhum92JslcMRC3aouivp0rAzszcVymIoJx6PXmibyP+xr+zKdwLg=="],
@@ -933,7 +926,7 @@
"@types/babel__traverse": ["@types/[email protected]", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
"@types/bun": ["@types/[email protected].9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
"@types/bun": ["@types/[email protected].1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
"@types/bytebuffer": ["@types/[email protected]", "", { "dependencies": { "@types/long": "^3.0.0", "@types/node": "*" } }, "sha512-lV4YLiolMdD4upDmr4vnfiwV/FN9Jg33eNTSFMkHqyMKqTIAn5TmFTYsARwXwUXeFU7yzRpECmWV1SOhIvdkRQ=="],
@@ -977,7 +970,7 @@
"@types/progress": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-iadjw02vte8qWx7U0YM++EybBha2CQLPGu9iJ97whVgJUT5Zq9MjAPYUnbfRI2Kpehimf1QjFJYxD0t8nqzu5w=="],
"@types/react": ["@types/[email protected].14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
"@types/react": ["@types/[email protected].2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
"@types/react-dom": ["@types/[email protected]", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="],
@@ -1149,7 +1142,7 @@
"buffer-from": ["[email protected]", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"bun-types": ["[email protected].9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
"bun-types": ["[email protected].1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
"bytebuffer": ["[email protected]", "", { "dependencies": { "long": "~3" } }, "sha512-IuzSdmADppkZ6DlpycMkm8l9zeEq16fWtLvunEwFiYciR/BHo4E8/xs5piFquG+Za8OWmMqHF8zuRviz2LHvRQ=="],
@@ -1269,7 +1262,7 @@
"cssesc": ["[email protected]", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"csv-parse": ["[email protected]", "", {}, "sha512-CEE+jwpgLn+MmtCpVcPtiCZpVtB6Z2OKPTr34pycYYoL7sxdOkXDdQ4lRiw6ioC0q6BLqhc6cKweCVvral8yhw=="],
@@ -1319,7 +1312,7 @@
"detect-gpu": ["[email protected]", "", { "dependencies": { "webgl-constants": "^1.1.1" } }, "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w=="],
"detect-libc": ["[email protected].2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"detect-libc": ["[email protected].1", "", {}, "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw=="],
"detect-node-es": ["[email protected]", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
@@ -1335,9 +1328,9 @@
"diff": ["[email protected]", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="],
"discord-api-types": ["[email protected].39", "", {}, "sha512-XRdDQvZvID1XvcFftjSmd4dcmMi/RL/jSy5sduBDAvCGFcNFHThdIQXCEBDZFe52lCNEzuIL0QJoKYAmRmxLUA=="],
"discord-api-types": ["[email protected].26", "", {}, "sha512-xpmPviHjIJ6dFu1eNwNDIGQ3N6qmPUUYFVAx/YZ64h7ZgPkTcKjnciD8bZe8Vbeji7yS5uYljyciunpq0J5NSw=="],
"discord.js": ["[email protected]5.1", "", { "dependencies": { "@discordjs/builders": "^1.13.0", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.2", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.2.0", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.33", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g=="],
"discord.js": ["[email protected]2.1", "", { "dependencies": { "@discordjs/builders": "^1.11.2", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.1", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.16", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-3k+Kisd/v570Jr68A1kNs7qVhNehDwDJAPe4DZ2Syt+/zobf9zEcuYFvsfIaAOgCa0BiHMfOOKQY4eYINl0z7w=="],
"dlv": ["[email protected]", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
@@ -1349,7 +1342,7 @@
"domutils": ["[email protected]", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
"dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
"dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="],
"dprint-node": ["[email protected]", "", { "dependencies": { "detect-libc": "^1.0.3" } }, "sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg=="],
@@ -1753,7 +1746,7 @@
"lodash": ["[email protected]", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash-es": ["[email protected]3", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="],
"lodash-es": ["[email protected]1", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="],
"lodash.debounce": ["[email protected]", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
@@ -2073,7 +2066,7 @@
"rc9": ["[email protected]", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
"react": ["[email protected].4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
"react": ["[email protected].0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
"react-dom": ["[email protected]", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
@@ -2169,7 +2162,7 @@
"safer-buffer": ["[email protected]", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"satori": ["[email protected].4", "", { "dependencies": { "@shuding/opentype.js": "1.4.0-beta.0", "css-background-parser": "^0.1.0", "css-box-shadow": "1.0.0-3", "css-gradient-parser": "^0.0.17", "css-to-react-native": "^3.0.0", "emoji-regex-xs": "^2.0.1", "escape-html": "^1.0.3", "linebreak": "^1.1.0", "parse-css-color": "^0.2.1", "postcss-value-parser": "^4.2.0", "yoga-layout": "^3.2.1" } }, "sha512-HanEzgXHlX3fzpGgxPoR3qI7FDpc/B+uE/KplzA6BkZGlWMaH98B/1Amq+OBF1pYPlGNzAXPYNHlrEVBvRBnHQ=="],
"satori": ["[email protected].3", "", { "dependencies": { "@shuding/opentype.js": "1.4.0-beta.0", "css-background-parser": "^0.1.0", "css-box-shadow": "1.0.0-3", "css-gradient-parser": "^0.0.17", "css-to-react-native": "^3.0.0", "emoji-regex-xs": "^2.0.1", "escape-html": "^1.0.3", "linebreak": "^1.1.0", "parse-css-color": "^0.2.1", "postcss-value-parser": "^4.2.0", "yoga-layout": "^3.2.1" } }, "sha512-T3DzWNmnrfVmk2gCIlAxLRLbGkfp3K7TyRva+Byyojqu83BNvnMeqVeYRdmUw4TKCsyH4RiQ/KuF/I4yEzgR5A=="],
"sax": ["[email protected]", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="],
@@ -2177,7 +2170,7 @@
"seek-bzip": ["[email protected]", "", { "dependencies": { "commander": "^2.8.1" }, "bin": { "seek-bunzip": "bin/seek-bunzip", "seek-table": "bin/seek-bzip-table" } }, "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ=="],
"semver": ["[email protected].4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"semver": ["[email protected].2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"send": ["[email protected]", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
@@ -2193,7 +2186,7 @@
"setprototypeof": ["[email protected]", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"sharp": ["[email protected].5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
"sharp": ["[email protected].4", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.0", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.4", "@img/sharp-darwin-x64": "0.34.4", "@img/sharp-libvips-darwin-arm64": "1.2.3", "@img/sharp-libvips-darwin-x64": "1.2.3", "@img/sharp-libvips-linux-arm": "1.2.3", "@img/sharp-libvips-linux-arm64": "1.2.3", "@img/sharp-libvips-linux-ppc64": "1.2.3", "@img/sharp-libvips-linux-s390x": "1.2.3", "@img/sharp-libvips-linux-x64": "1.2.3", "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", "@img/sharp-libvips-linuxmusl-x64": "1.2.3", "@img/sharp-linux-arm": "0.34.4", "@img/sharp-linux-arm64": "0.34.4", "@img/sharp-linux-ppc64": "0.34.4", "@img/sharp-linux-s390x": "0.34.4", "@img/sharp-linux-x64": "0.34.4", "@img/sharp-linuxmusl-arm64": "0.34.4", "@img/sharp-linuxmusl-x64": "0.34.4", "@img/sharp-wasm32": "0.34.4", "@img/sharp-win32-arm64": "0.34.4", "@img/sharp-win32-ia32": "0.34.4", "@img/sharp-win32-x64": "0.34.4" } }, "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA=="],
"shebang-command": ["[email protected]", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
@@ -2623,22 +2616,14 @@
"@blitzkit/app/typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw=="],
"@blitzkit/bot/prisma": ["[email protected]9.2", "", { "dependencies": { "@prisma/config": "6.19.2", "@prisma/engines": "6.19.2" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-XTKeKxtQElcq3U9/jHyxSPgiRgeYDKxWTPOf6NkXA0dNj5j40MfEsZkMbyNpwDWCUv7YBFUl7I2VK/6ALbmhEg=="],
"@blitzkit/bot/prisma": ["[email protected]7.1", "", { "dependencies": { "@prisma/config": "6.17.1", "@prisma/engines": "6.17.1" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g=="],
"@blitzkit/website/vite": ["[email protected]", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg=="],
"@discordjs/rest/@discordjs/collection": ["@discordjs/[email protected]", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
"@discordjs/rest/@discordjs/util": ["@discordjs/[email protected]", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="],
"@discordjs/rest/discord-api-types": ["[email protected]", "", {}, "sha512-xpmPviHjIJ6dFu1eNwNDIGQ3N6qmPUUYFVAx/YZ64h7ZgPkTcKjnciD8bZe8Vbeji7yS5uYljyciunpq0J5NSw=="],
"@discordjs/ws/@discordjs/collection": ["@discordjs/[email protected]", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
"@discordjs/ws/@discordjs/util": ["@discordjs/[email protected]", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="],
"@discordjs/ws/discord-api-types": ["[email protected]", "", {}, "sha512-xpmPviHjIJ6dFu1eNwNDIGQ3N6qmPUUYFVAx/YZ64h7ZgPkTcKjnciD8bZe8Vbeji7yS5uYljyciunpq0J5NSw=="],
"@octokit/endpoint/@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
"@octokit/graphql/@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
@@ -2649,8 +2634,6 @@
"@octokit/request-error/@octokit/types": ["@octokit/[email protected]", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
"@puppeteer/browsers/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"@rollup/plugin-babel/@rollup/pluginutils": ["@rollup/[email protected]", "", { "dependencies": { "@types/estree": "0.0.39", "estree-walker": "^1.0.1", "picomatch": "^2.2.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0" } }, "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg=="],
"@rollup/plugin-babel/rollup": ["[email protected]", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ=="],
@@ -2671,10 +2654,6 @@
"astro/@astrojs/internal-helpers": ["@astrojs/[email protected]", "", {}, "sha512-lDA9MqE8WGi7T/t2BMi+EAXhs4Vcvr94Gqx3q15cFEz8oFZMO4/SFBqYr/UcmNlvW+35alowkVj+w9VhLvs5Cw=="],
"astro/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"astro/sharp": ["[email protected]", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.0", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.4", "@img/sharp-darwin-x64": "0.34.4", "@img/sharp-libvips-darwin-arm64": "1.2.3", "@img/sharp-libvips-darwin-x64": "1.2.3", "@img/sharp-libvips-linux-arm": "1.2.3", "@img/sharp-libvips-linux-arm64": "1.2.3", "@img/sharp-libvips-linux-ppc64": "1.2.3", "@img/sharp-libvips-linux-s390x": "1.2.3", "@img/sharp-libvips-linux-x64": "1.2.3", "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", "@img/sharp-libvips-linuxmusl-x64": "1.2.3", "@img/sharp-linux-arm": "0.34.4", "@img/sharp-linux-arm64": "0.34.4", "@img/sharp-linux-ppc64": "0.34.4", "@img/sharp-linux-s390x": "0.34.4", "@img/sharp-linux-x64": "0.34.4", "@img/sharp-linuxmusl-arm64": "0.34.4", "@img/sharp-linuxmusl-x64": "0.34.4", "@img/sharp-wasm32": "0.34.4", "@img/sharp-win32-arm64": "0.34.4", "@img/sharp-win32-ia32": "0.34.4", "@img/sharp-win32-x64": "0.34.4" } }, "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA=="],
"babel-plugin-polyfill-corejs2/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"binarykvparser/long": ["[email protected]", "", { "bundled": true }, "sha512-ZYvPPOMqUwPoDsbJaR10iQJYnMuZhRTvHYl62ErLIEX7RgFlziSBUUvrt3OVfc47QlHHpzPZYP17g3Fv7oeJkg=="],
@@ -2723,8 +2702,6 @@
"micromatch/picomatch": ["[email protected]", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"ndarray-pixels/sharp": ["[email protected]", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.0", "semver": "^7.7.2" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.4", "@img/sharp-darwin-x64": "0.34.4", "@img/sharp-libvips-darwin-arm64": "1.2.3", "@img/sharp-libvips-darwin-x64": "1.2.3", "@img/sharp-libvips-linux-arm": "1.2.3", "@img/sharp-libvips-linux-arm64": "1.2.3", "@img/sharp-libvips-linux-ppc64": "1.2.3", "@img/sharp-libvips-linux-s390x": "1.2.3", "@img/sharp-libvips-linux-x64": "1.2.3", "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", "@img/sharp-libvips-linuxmusl-x64": "1.2.3", "@img/sharp-linux-arm": "0.34.4", "@img/sharp-linux-arm64": "0.34.4", "@img/sharp-linux-ppc64": "0.34.4", "@img/sharp-linux-s390x": "0.34.4", "@img/sharp-linux-x64": "0.34.4", "@img/sharp-linuxmusl-arm64": "0.34.4", "@img/sharp-linuxmusl-x64": "0.34.4", "@img/sharp-wasm32": "0.34.4", "@img/sharp-win32-arm64": "0.34.4", "@img/sharp-win32-ia32": "0.34.4", "@img/sharp-win32-x64": "0.34.4" } }, "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA=="],
"pac-proxy-agent/agent-base": ["[email protected]", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"pac-proxy-agent/socks-proxy-agent": ["[email protected]", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
@@ -2777,14 +2754,10 @@
"tunnel-rat/zustand": ["[email protected]", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="],
"typescript-auto-import-cache/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"unbzip2-stream/buffer": ["[email protected]", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
"unicode-trie/pako": ["[email protected]", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
"volar-service-typescript/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"vscode-json-languageservice/jsonc-parser": ["[email protected]", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
"which-builtin-type/isarray": ["[email protected]", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
@@ -2809,9 +2782,9 @@
"yaml-language-server/yaml": ["[email protected]", "", {}, "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA=="],
"@blitzkit/bot/prisma/@prisma/config": ["@prisma/[email protected]9.2", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.18.4", "empathic": "2.0.0" } }, "sha512-kadBGDl+aUswv/zZMk9Mx0C8UZs1kjao8H9/JpI4Wh4SHZaM7zkTwiKn/iFLfRg+XtOAo/Z/c6pAYhijKl0nzQ=="],
"@blitzkit/bot/prisma/@prisma/config": ["@prisma/[email protected]7.1", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.16.12", "empathic": "2.0.0" } }, "sha512-fs8wY6DsvOCzuiyWVckrVs1LOcbY4LZNz8ki4uUIQ28jCCzojTGqdLhN2Jl5lDnC1yI8/gNIKpsWDM8pLhOdwA=="],
"@blitzkit/bot/prisma/@prisma/engines": ["@prisma/[email protected]9.2", "", { "dependencies": { "@prisma/debug": "6.19.2", "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "@prisma/fetch-engine": "6.19.2", "@prisma/get-platform": "6.19.2" } }, "sha512-TTkJ8r+uk/uqczX40wb+ODG0E0icVsMgwCTyTHXehaEfb0uo80M9g1aW1tEJrxmFHeOZFXdI2sTA1j1AgcHi4A=="],
"@blitzkit/bot/prisma/@prisma/engines": ["@prisma/[email protected]7.1", "", { "dependencies": { "@prisma/debug": "6.17.1", "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", "@prisma/fetch-engine": "6.17.1", "@prisma/get-platform": "6.17.1" } }, "sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw=="],
"@octokit/endpoint/@octokit/types/@octokit/openapi-types": ["@octokit/[email protected]", "", {}, "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA=="],
@@ -2835,52 +2808,6 @@
"@rollup/plugin-replace/@rollup/pluginutils/picomatch": ["[email protected]", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"astro/sharp/@img/sharp-darwin-arm64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.3" }, "os": "darwin", "cpu": "arm64" }, "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA=="],
"astro/sharp/@img/sharp-darwin-x64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.3" }, "os": "darwin", "cpu": "x64" }, "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg=="],
"astro/sharp/@img/sharp-libvips-darwin-arm64": ["@img/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw=="],
"astro/sharp/@img/sharp-libvips-darwin-x64": ["@img/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA=="],
"astro/sharp/@img/sharp-libvips-linux-arm": ["@img/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA=="],
"astro/sharp/@img/sharp-libvips-linux-arm64": ["@img/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ=="],
"astro/sharp/@img/sharp-libvips-linux-ppc64": ["@img/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg=="],
"astro/sharp/@img/sharp-libvips-linux-s390x": ["@img/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w=="],
"astro/sharp/@img/sharp-libvips-linux-x64": ["@img/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg=="],
"astro/sharp/@img/sharp-libvips-linuxmusl-arm64": ["@img/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw=="],
"astro/sharp/@img/sharp-libvips-linuxmusl-x64": ["@img/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g=="],
"astro/sharp/@img/sharp-linux-arm": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.3" }, "os": "linux", "cpu": "arm" }, "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA=="],
"astro/sharp/@img/sharp-linux-arm64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ=="],
"astro/sharp/@img/sharp-linux-ppc64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.3" }, "os": "linux", "cpu": "ppc64" }, "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ=="],
"astro/sharp/@img/sharp-linux-s390x": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.3" }, "os": "linux", "cpu": "s390x" }, "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw=="],
"astro/sharp/@img/sharp-linux-x64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A=="],
"astro/sharp/@img/sharp-linuxmusl-arm64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA=="],
"astro/sharp/@img/sharp-linuxmusl-x64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg=="],
"astro/sharp/@img/sharp-wasm32": ["@img/[email protected]", "", { "dependencies": { "@emnapi/runtime": "^1.5.0" }, "cpu": "none" }, "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA=="],
"astro/sharp/@img/sharp-win32-arm64": ["@img/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA=="],
"astro/sharp/@img/sharp-win32-ia32": ["@img/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw=="],
"astro/sharp/@img/sharp-win32-x64": ["@img/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig=="],
"astro/sharp/detect-libc": ["[email protected]", "", {}, "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw=="],
"boxen/string-width/emoji-regex": ["[email protected]", "", {}, "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg=="],
"boxen/string-width/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
@@ -2925,60 +2852,8 @@
"favilib/sharp/@img/sharp-win32-x64": ["@img/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
"favilib/sharp/detect-libc": ["[email protected]", "", {}, "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw=="],
"favilib/sharp/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"filelist/minimatch/brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"ndarray-pixels/sharp/@img/sharp-darwin-arm64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.3" }, "os": "darwin", "cpu": "arm64" }, "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA=="],
"ndarray-pixels/sharp/@img/sharp-darwin-x64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.3" }, "os": "darwin", "cpu": "x64" }, "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg=="],
"ndarray-pixels/sharp/@img/sharp-libvips-darwin-arm64": ["@img/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw=="],
"ndarray-pixels/sharp/@img/sharp-libvips-darwin-x64": ["@img/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA=="],
"ndarray-pixels/sharp/@img/sharp-libvips-linux-arm": ["@img/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA=="],
"ndarray-pixels/sharp/@img/sharp-libvips-linux-arm64": ["@img/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ=="],
"ndarray-pixels/sharp/@img/sharp-libvips-linux-ppc64": ["@img/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg=="],
"ndarray-pixels/sharp/@img/sharp-libvips-linux-s390x": ["@img/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w=="],
"ndarray-pixels/sharp/@img/sharp-libvips-linux-x64": ["@img/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg=="],
"ndarray-pixels/sharp/@img/sharp-libvips-linuxmusl-arm64": ["@img/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw=="],
"ndarray-pixels/sharp/@img/sharp-libvips-linuxmusl-x64": ["@img/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g=="],
"ndarray-pixels/sharp/@img/sharp-linux-arm": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.3" }, "os": "linux", "cpu": "arm" }, "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA=="],
"ndarray-pixels/sharp/@img/sharp-linux-arm64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ=="],
"ndarray-pixels/sharp/@img/sharp-linux-ppc64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.3" }, "os": "linux", "cpu": "ppc64" }, "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ=="],
"ndarray-pixels/sharp/@img/sharp-linux-s390x": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.3" }, "os": "linux", "cpu": "s390x" }, "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw=="],
"ndarray-pixels/sharp/@img/sharp-linux-x64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A=="],
"ndarray-pixels/sharp/@img/sharp-linuxmusl-arm64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" }, "os": "linux", "cpu": "arm64" }, "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA=="],
"ndarray-pixels/sharp/@img/sharp-linuxmusl-x64": ["@img/[email protected]", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.3" }, "os": "linux", "cpu": "x64" }, "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg=="],
"ndarray-pixels/sharp/@img/sharp-wasm32": ["@img/[email protected]", "", { "dependencies": { "@emnapi/runtime": "^1.5.0" }, "cpu": "none" }, "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA=="],
"ndarray-pixels/sharp/@img/sharp-win32-arm64": ["@img/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA=="],
"ndarray-pixels/sharp/@img/sharp-win32-ia32": ["@img/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw=="],
"ndarray-pixels/sharp/@img/sharp-win32-x64": ["@img/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig=="],
"ndarray-pixels/sharp/detect-libc": ["[email protected]", "", {}, "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw=="],
"ndarray-pixels/sharp/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"steam-appticket/protobufjs/@types/long": ["@types/[email protected]", "", {}, "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="],
"steam-appticket/protobufjs/long": ["[email protected]", "", {}, "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="],
@@ -2993,22 +2868,18 @@
"yaml-language-server/vscode-languageserver/vscode-languageserver-protocol": ["[email protected]", "", { "dependencies": { "vscode-jsonrpc": "6.0.0", "vscode-languageserver-types": "3.16.0" } }, "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A=="],
"@blitzkit/bot/prisma/@prisma/engines/@prisma/debug": ["@prisma/[email protected]", "", {}, "sha512-lFnEZsLdFLmEVCVNdskLDCL8Uup41GDfU0LUfquw+ercJC8ODTuL0WNKgOKmYxCJVvFwf0OuZBzW99DuWmoH2A=="],
"@blitzkit/bot/prisma/@prisma/config/effect": ["[email protected]", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg=="],
"@blitzkit/bot/prisma/@prisma/engines/@prisma/engines-version": ["@prisma/engines-version@7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "", {}, "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA=="],
"@blitzkit/bot/prisma/@prisma/engines/@prisma/debug": ["@prisma/debug@6.17.1", "", {}, "sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ=="],
"@blitzkit/bot/prisma/@prisma/engines/@prisma/fetch-engine": ["@prisma/[email protected]", "", { "dependencies": { "@prisma/debug": "6.19.2", "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", "@prisma/get-platform": "6.19.2" } }, "sha512-h4Ff4Pho+SR1S8XerMCC12X//oY2bG3Iug/fUnudfcXEUnIeRiBdXHFdGlGOgQ3HqKgosTEhkZMvGM9tWtYC+Q=="],
"@blitzkit/bot/prisma/@prisma/engines/@prisma/engines-version": ["@prisma/engines-version@6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", "", {}, "sha512-17140E3huOuD9lMdJ9+SF/juOf3WR3sTJMVyyenzqUPbuH+89nPhSWcrY+Mf7tmSs6HvaO+7S+HkELinn6bhdg=="],
"@blitzkit/bot/prisma/@prisma/engines/@prisma/get-platform": ["@prisma/get-platform@6.19.2", "", { "dependencies": { "@prisma/debug": "6.19.2" } }, "sha512-PGLr06JUSTqIvztJtAzIxOwtWKtJm5WwOG6xpsgD37Rc84FpfUBGLKz65YpJBGtkRQGXTYEFie7pYALocC3MtA=="],
"@blitzkit/bot/prisma/@prisma/engines/@prisma/fetch-engine": ["@prisma/fetch-engine@6.17.1", "", { "dependencies": { "@prisma/debug": "6.17.1", "@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac", "@prisma/get-platform": "6.17.1" } }, "sha512-AYZiHOs184qkDMiTeshyJCtyL4yERkjfTkJiSJdYuSfc24m94lTNL5+GFinZ6vVz+ktX4NJzHKn1zIFzGTWrWg=="],
"astro/sharp/@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
"@blitzkit/bot/prisma/@prisma/engines/@prisma/get-platform": ["@prisma/[email protected]", "", { "dependencies": { "@prisma/debug": "6.17.1" } }, "sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg=="],
"boxen/string-width/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"favilib/sharp/@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
"ndarray-pixels/sharp/@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="],
"widest-line/string-width/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"yaml-language-server/vscode-languageserver/vscode-languageserver-protocol/vscode-jsonrpc": ["[email protected]", "", {}, "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg=="],
+16 -44
View File
@@ -1,47 +1,19 @@
# BlitzKit Credits
BlitzKit is a one man band **TrèsAbhi** but I am far from solo. Helpers, in the order of contribution over time:
BlitzKit is a one man band **TrèsAbhi** but I am far from solo.
### 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.
| Role | People |
| ------------------------------ | ------------------------------ |
| Bot verification | S0AP |
| General testing | Prince_NA |
| Bot advertisement platform | S0AP |
| Bot rendering pipeline | Vovko |
| Game asset decompression | Maddox |
| Game 3D model parsing | Pyogenics |
| Game file layout | Aeson |
| 3-caliber rule edge cases | Minitelrose, ChickenMan7777 |
| Embed UX | \_34, HisRoyalFatness, Synx |
| Tankopedia pre-release testing | [OPTML], Aeson, \_34, xkdkw |
| Website advertisement platform | Aeson |
| Compare pre-release testing | [OPTML] |
| Redacted | GonnaHetzMe |
-14
View File
@@ -1,14 +0,0 @@
# BlitzKit 1.29.10
Thank you to [Mark Greene](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
- Changing tiers will break your streak
## Fixes
- [Mark Greene] module damage is now properly shown when sorting with it in the tank search
- Duplicate abilities in the tank filters are now removed
-5
View File
@@ -1,5 +0,0 @@
# BlitzKit 1.29.11
## Fixes
- The Guesser game's toolbar now scales properly with screen size
-14
View File
@@ -1,14 +0,0 @@
# BlitzKit 1.29.12
## Fixes
- BlitzKit no longer uses BlitzStars for tank averages for some commands
- BlitzKit Performance was created to completely replace BlitzStars but BlitzStars was still used for some older commands
- But since BlitzStars keeps having downtime, it has been completely removed
- Upgraded all dependencies to the latest versions
- The replay command is now properly localized across all languages
- Special Chinese characters are now properly handled
- Commands are now registered synchronously, allowing errors to reveal their exact origins
- Error handling now properly exists the process after logging the error
- The bot now uses Discord's new `clientReady` instead of the ambiguous `ready` event
- Both the armor and model extractors now use the new BlitzKit `SteamVFS`
-14
View File
@@ -1,14 +0,0 @@
# BlitzKit 1.29.9
Thank you to [Mark Greene](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 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
+2 -3
View File
@@ -1,6 +1,6 @@
{
"name": "blitzkit",
"version": "1.29.12",
"version": "1.29.8",
"license": "BlitzKit License",
"private": true,
"scripts": {
@@ -13,7 +13,7 @@
"build:catalogitemaccesssor": "cd packages/scripts && bun run build:catalogitemaccesssor",
"build:ratingleaderboard": "cd packages/scripts && bun run build:ratingleaderboard",
"build:app": "cd packages/app && bun run build",
"dev:bot": "bun run build:bot --dev && bun start:bot --dev",
"dev:bot": "bun run build:bot --dev && bun start:bot",
"start:bot": "bun dist/bot/main.js",
"dev:docs": "vitepress dev docs",
"dev:website": "cd packages/website && bun run dev",
@@ -45,7 +45,6 @@
"dependencies": {
"@prisma/client": "^6.18.0",
"prisma": "^6.18.0",
"sharp": "^0.34.5",
"typescript": "^5.9.3"
}
}
+11 -11
View File
@@ -356,9 +356,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.11.1"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
dependencies = [
"serde",
]
@@ -2105,9 +2105,9 @@ checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "num-conv"
version = "0.2.0"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-traits"
@@ -3907,30 +3907,30 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.47"
version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde_core",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.8"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
[[package]]
name = "time-macros"
version = "0.2.27"
version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
dependencies = [
"num-conv",
"time-core",
+9 -10
View File
@@ -4,26 +4,25 @@
"@blitzkit/core": "workspace:*",
"@blitzkit/i18n": "workspace:*",
"@radix-ui/colors": "^3.0.0",
"@radix-ui/themes": "^3.3.0",
"@radix-ui/themes": "^3.2.1",
"@resvg/resvg-js": "^2.6.2",
"@types/adm-zip": "^0.5.7",
"@types/bun": "^1.3.9",
"@types/lodash-es": "^4.17.12",
"@types/markdown-escape": "^1.1.3",
"@types/react": "^19.2.14",
"@types/react": "^19.2.2",
"adm-zip": "^0.5.16",
"bepaint": "^0.1.0-beta.3",
"discord.js": "^14.25.1",
"dotenv": "^17.3.1",
"discord.js": "^14.22.1",
"dotenv": "^17.2.2",
"fuzzysort": "^3.1.0",
"lodash-es": "^4.17.23",
"lodash-es": "^4.17.21",
"markdown-escape": "^2.0.0",
"pickleparser": "^0.2.1",
"prisma": "^6.19.2",
"prisma": "^6.17.1",
"protobufjs": "^7.5.4",
"react": "^19.2.4",
"satori": "^0.18.4",
"sharp": "^0.34.5"
"react": "^19.2.0",
"satori": "^0.18.3",
"sharp": "^0.34.4"
},
"private": true,
"scripts": {
+4 -4
View File
@@ -1,7 +1,7 @@
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
import { translator } from "../core/localization/translator";
import {
commandsArray,
COMMANDS_RAW,
type CommandRegistry,
} from "../events/interactionCreate";
@@ -35,7 +35,7 @@ export const aboutCommand = new Promise<CommandRegistry>(async (resolve) => {
if (subcommand === "commands") {
return `${strings.bot.commands.about.subcommands.commands.body}${(
await Promise.all(commandsArray)
await Promise.all(COMMANDS_RAW)
)
.sort((a, b) =>
(a.command.name_localizations?.[interaction.locale] ??
@@ -43,7 +43,7 @@ export const aboutCommand = new Promise<CommandRegistry>(async (resolve) => {
(b.command.name_localizations?.[interaction.locale] ??
b.command.name)
? -1
: 1,
: 1
)
.map(
(registry) =>
@@ -54,7 +54,7 @@ export const aboutCommand = new Promise<CommandRegistry>(async (resolve) => {
registry.command.description_localizations?.[
interaction.locale
] ?? registry.command.description
}`,
}`
)
.join("\n")}`;
}
+27 -32
View File
@@ -15,16 +15,11 @@ import { CommandWrapper } from "../../components/CommandWrapper";
import { NoData } from "../../components/NoData";
import { TitleBar } from "../../components/TitleBar";
import { filtersToDescription } from "../../core/blitzkit/filtersToDescription";
import {
averageDefinitions,
tankDefinitions,
} from "../../core/blitzkit/nonBlockingPromises";
import { tankDefinitions } from "../../core/blitzkit/nonBlockingPromises";
import { UserError } from "../../core/blitzkit/userError";
import {
filterStats,
type StatFilters,
} from "../../core/blitzstars/filterStats";
import { filterStats, type StatFilters } from "../../core/blitzstars/filterStats";
import { getStatsInPeriod } from "../../core/blitzstars/getStatsInPeriod";
import { blitzStarsTankAverages } from "../../core/blitzstars/tankAverages";
import type { ResolvedPeriod } from "../../core/discord/resolvePeriodFromCommand";
import type { ResolvedPlayer } from "../../core/discord/resolvePlayerFromCommand";
import { translator } from "../../core/localization/translator";
@@ -36,11 +31,11 @@ export async function renderBreakdown(
{ region, id }: ResolvedPlayer,
{ start, end, name }: ResolvedPeriod,
filters: StatFilters,
locale: Locale,
locale: Locale
) {
const { strings, unwrap } = translator(locale);
const awaitedTankDefinitions = await tankDefinitions;
const awaitedTankAverages = await averageDefinitions;
const awaitedTankAverages = await blitzStarsTankAverages;
const statsInPeriod = await getStatsInPeriod(region, id, start, end, locale);
const { filteredOrder } = await filterStats(statsInPeriod, filters);
const accountInfo = await getAccountInfo(region, id);
@@ -58,14 +53,14 @@ export async function renderBreakdown(
}
filteredOrder.forEach((id) => {
const tankAveragesEntry = awaitedTankAverages.averages[id];
const tankAveragesEntry = awaitedTankAverages[id];
const current = statsInPeriod.diff[id];
const career = tankStats.find((tank) => tank.tank_id === id)!.all;
const currentWN8 = tankAveragesEntry
? calculateWN8(tankAveragesEntry.mu, current)
? calculateWN8(tankAveragesEntry.all, current)
: undefined;
const careerWN8 = tankAveragesEntry
? calculateWN8(tankAveragesEntry.mu, career)
? calculateWN8(tankAveragesEntry.all, career)
: undefined;
orderedCurrentStats.push(current);
@@ -74,65 +69,65 @@ export async function renderBreakdown(
orderedCareerWN8.push(careerWN8);
});
tankStats.forEach((tank) => {
const tankAveragesEntry = awaitedTankAverages.averages[tank.tank_id];
const tankAveragesEntry = awaitedTankAverages[tank.tank_id];
orderedCareerWN8Full.push(
tankAveragesEntry
? calculateWN8(tankAveragesEntry.mu, tank.all)
: undefined,
? calculateWN8(tankAveragesEntry.all, tank.all)
: undefined
);
});
const currentBattles = orderedCurrentStats.reduce(
(accumulator, current) => accumulator + current.battles,
0,
0
);
const careerBattles = tankStats.reduce(
(accumulator, current) => accumulator + current.all.battles,
0,
0
);
const currentWinrate =
orderedCurrentStats.reduce(
(accumulator, current) => accumulator + current.wins,
0,
0
) / currentBattles;
const careerWinrate =
tankStats.reduce(
(accumulator, current) => accumulator + current.all.wins,
0,
0
) / careerBattles;
const currentWN8 =
orderedCurrentWN8.reduce<number>(
(accumulator, current, index) =>
accumulator + (current ?? 0) * orderedCurrentStats[index].battles,
0,
0
) /
orderedCurrentStats.reduce(
(accumulator, current, index) =>
accumulator +
(orderedCurrentWN8[index] === undefined ? 0 : current.battles),
0,
0
);
const careerWN8 =
orderedCareerWN8Full.reduce<number>(
(accumulator, current) => accumulator + (current ?? 0),
0,
0
) /
tankStats.reduce(
(accumulator, current, index) =>
accumulator +
(orderedCareerWN8Full[index] === undefined ? 0 : current.all.battles),
0,
0
);
const currentDamage =
orderedCurrentStats.reduce(
(accumulator, current) => accumulator + current.damage_dealt,
0,
0
) / currentBattles;
const careerDamage =
tankStats.reduce(
(accumulator, current) => accumulator + current.all.damage_dealt,
0,
0
) / careerBattles;
const children: React.JSX.Element[] = [];
@@ -177,7 +172,7 @@ export async function renderBreakdown(
delta: currentDamage - careerDamage,
},
]}
/>,
/>
);
filteredOrder.forEach((id, index) => {
@@ -211,7 +206,7 @@ export async function renderBreakdown(
{
title: strings.bot.commands.breakdown.body.winrate,
current: `${(100 * (current.wins / current.battles)).toFixed(
2,
2
)}%`,
career: `${(100 * (career.wins / career.battles)).toFixed(2)}%`,
delta:
@@ -235,24 +230,24 @@ export async function renderBreakdown(
{
title: strings.bot.commands.breakdown.body.damage,
current: Math.round(
current.damage_dealt / current.battles,
current.damage_dealt / current.battles
).toLocaleString(),
career: Math.round(
career.damage_dealt / career.battles,
career.damage_dealt / career.battles
).toLocaleString(),
delta:
current.damage_dealt / current.battles -
career.damage_dealt / career.battles,
},
]}
/>,
/>
);
});
}
const pages = chunk(
children,
Math.max(Math.ceil(children.length / MAX_PAGES), ROWS_PER_PAGE),
Math.max(Math.ceil(children.length / MAX_PAGES), ROWS_PER_PAGE)
);
if (filteredOrder.length > 0) {
+77 -92
View File
@@ -5,29 +5,28 @@ import {
idToRegion,
tankIcon,
TIER_ROMAN_NUMERALS,
} from "@blitzkit/core";
import { Locale } from "discord.js";
import { clamp } from "lodash-es";
import { CommandWrapper } from "../components/CommandWrapper";
import { TitleBar } from "../components/TitleBar";
import { iconPng } from "../core/blitzkit/iconPng";
} from '@blitzkit/core';
import { Locale } from 'discord.js';
import { clamp } from 'lodash-es';
import { CommandWrapper } from '../components/CommandWrapper';
import { TitleBar } from '../components/TitleBar';
import { iconPng } from '../core/blitzkit/iconPng';
import {
averageDefinitions,
mapDefinitions,
tankDefinitions,
} from "../core/blitzkit/nonBlockingPromises";
import { RenderConfiguration } from "../core/blitzkit/renderConfiguration";
import { buttonLink } from "../core/discord/buttonLink";
import { createLocalizedCommand } from "../core/discord/createLocalizedCommand";
import { localizationObject } from "../core/discord/localizationObject";
import { translator } from "../core/localization/translator";
import { type CommandRegistry } from "../events/interactionCreate";
import { theme } from "../stitches.config";
} from '../core/blitzkit/nonBlockingPromises';
import { RenderConfiguration } from '../core/blitzkit/renderConfiguration';
import { blitzStarsTankAverages } from '../core/blitzstars/tankAverages';
import { buttonLink } from '../core/discord/buttonLink';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
import { translator } from '../core/localization/translator';
import { type CommandRegistry } from '../events/interactionCreate';
import { theme } from '../stitches.config';
type WotInspectorReplaySubmission =
| { status: "error"; data: unknown; error: { message: string } }
| { status: 'error'; data: unknown; error: { message: string } }
| {
status: "ok";
status: 'ok';
data: {
download_url: string;
view_url: string;
@@ -210,9 +209,9 @@ function stat(value: string | number, flex = 2) {
<div
style={{
flex,
display: "flex",
alignItems: "center",
justifyContent: "center",
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<span style={{ fontSize: 16 }}>{value}</span>
@@ -228,15 +227,15 @@ async function playerListing(
recordingPlayer = false,
) {
const { unwrap } = translator(locale);
const awaitedTankAverages = await averageDefinitions;
const awaitedTankAverages = await blitzStarsTankAverages;
const awaitedTankDefinitions = await tankDefinitions;
const tank = awaitedTankDefinitions.tanks[player.vehicle_descr];
const blockAccent = recordingPlayer ? "_amber" : "";
const blockAccent = recordingPlayer ? '_amber' : '';
const healthAccent = protagonistTeam
? recordingPlayer
? "_amber"
: "_green"
: "_red";
? '_amber'
: '_green'
: '_red';
// our turret ids are always off by 1 or 2 lol so imma just grab the closest
// TODO: 💀
@@ -246,11 +245,11 @@ async function playerListing(
)[0];
const totalHealth = turret.health + tank.health;
const healthLeft = clamp(player.hitpoints_left / totalHealth, 0, 1);
const tankAveragesEntry = awaitedTankAverages.averages[player.vehicle_descr];
const tankAveragesEntry = awaitedTankAverages[player.vehicle_descr];
const wn8 =
tankAveragesEntry === undefined
? -1
: calculateWN8(tankAveragesEntry.mu, {
: calculateWN8(tankAveragesEntry.all, {
battles: 1,
damage_dealt: player.damage_made,
dropped_capture_points: player.base_defend_points,
@@ -263,8 +262,8 @@ async function playerListing(
<div
key={player.entity_id}
style={{
display: "flex",
alignItems: "center",
display: 'flex',
alignItems: 'center',
gap: 8,
backgroundColor: theme.colors[`appBackground2${blockAccent}`],
padding: 8,
@@ -272,11 +271,11 @@ async function playerListing(
>
<div
style={{
display: "flex",
display: 'flex',
gap: 8,
flex: 1,
overflow: "hidden",
alignItems: "center",
overflow: 'hidden',
alignItems: 'center',
}}
>
<img
@@ -285,22 +284,22 @@ async function playerListing(
style={{
width: 32,
height: 32,
objectFit: "contain",
objectFit: 'contain',
}}
/>
<div
style={{
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
// align
}}
>
<div
style={{
display: "flex",
display: 'flex',
gap: 4,
alignItems: "center",
alignItems: 'center',
}}
>
<span
@@ -325,23 +324,23 @@ async function playerListing(
style={{
fontSize: 16,
color: theme.colors[`textLowContrast${blockAccent}`],
whiteSpace: "nowrap",
whiteSpace: 'nowrap',
}}
>
{player.name}
{player.clan_tag ? ` [${player.clan_tag}]` : ""}
{player.clan_tag ? ` [${player.clan_tag}]` : ''}
</span>
</div>
</div>
<div
style={{
display: "flex",
display: 'flex',
flex: 1,
}}
>
{stat(player.damage_made.toLocaleString())}
{stat(wn8 === -1 ? "--" : Math.round(wn8).toLocaleString())}
{stat(wn8 === -1 ? '--' : Math.round(wn8).toLocaleString())}
{stat(player.exp.toLocaleString())}
{stat(player.enemies_destroyed, 1)}
</div>
@@ -350,15 +349,15 @@ async function playerListing(
style={{
borderRadius: 2,
backgroundColor: theme.colors[`componentInteractive${healthAccent}`],
display: "flex",
display: 'flex',
width: 4,
height: "100%",
transform: "scaleY(-1)",
height: '100%',
transform: 'scaleY(-1)',
}}
>
<div
style={{
width: "100%",
width: '100%',
height: `${healthLeft * 100}%`,
borderRadius: 2,
backgroundColor: theme.colors[`solidBackground${healthAccent}`],
@@ -374,8 +373,8 @@ function Header() {
<div
style={{
padding: 8,
display: "flex",
alignItems: "center",
display: 'flex',
alignItems: 'center',
gap: 8,
}}
>
@@ -383,16 +382,16 @@ function Header() {
<div
style={{
display: "flex",
display: 'flex',
flex: 1,
}}
>
<div
style={{
flex: 2,
display: "flex",
alignItems: "center",
justifyContent: "center",
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<span
@@ -407,9 +406,9 @@ function Header() {
<div
style={{
flex: 2,
display: "flex",
alignItems: "center",
justifyContent: "center",
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<span
@@ -424,9 +423,9 @@ function Header() {
<div
style={{
flex: 2,
display: "flex",
alignItems: "center",
justifyContent: "center",
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<span
@@ -441,9 +440,9 @@ function Header() {
<div
style={{
flex: 1,
display: "flex",
alignItems: "center",
justifyContent: "center",
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<span
@@ -461,42 +460,28 @@ function Header() {
}
export const replayCommand = new Promise<CommandRegistry>((resolve) => {
const { strings } = translator(Locale.EnglishUS);
resolve({
command: createLocalizedCommand("replay").addAttachmentOption((option) =>
option
.setName(strings.bot.commands.replay.options.file.name)
.setNameLocalizations(
localizationObject(
(strings) => strings.bot.commands.replay.options.file.name,
undefined,
true,
),
)
.setDescription(strings.bot.commands.replay.options.file.description)
.setDescriptionLocalizations(
localizationObject(
(strings) => strings.bot.commands.replay.options.file.description,
),
)
.setRequired(true),
command: createLocalizedCommand('replay').addAttachmentOption((option) =>
option.setName('file').setDescription('Replay file').setRequired(true),
),
async handler(interaction) {
const awaitedMapDefinitions = await mapDefinitions;
const file = interaction.options.getAttachment("file")!;
const file = interaction.options.getAttachment('file')!;
// const file = {
// url: 'https://cdn.discordapp.com/ephemeral-attachments/1232434891652861972/1232781496016834581/20240328_1530__TresAbhi_M60_1157057448155406708.wotbreplay?ex=662ab4bc&is=6629633c&hm=c784cd10e80232350ddaf291fa987b879c4d0a8083b78298c9eee4fa0d507f6c&',
// };
const submissionData = (await fetch(
`https://wotinspector.com/api/replay/upload/?url=${encodeURIComponent(
file.url,
)}`,
).then((response) => response.json())) as WotInspectorReplaySubmission;
if (submissionData.status === "error") {
if (submissionData.status === 'error') {
return `# Uh oh! WoT Inspector didn't like that.\n\nThere was an error processing your replay. Get help on [the official Discord server](https://discord.gg/nDt7AjGJQH).`;
}
const replayId = submissionData.data.download_url.split("/").at(-1)!;
const replayId = submissionData.data.download_url.split('/').at(-1)!;
const replayData = (await fetch(
`https://api.wotinspector.com/v2/blitz/replays/${replayId}/`,
).then((response) => response.json())) as WotInspectorReplay;
@@ -508,7 +493,7 @@ export const replayCommand = new Promise<CommandRegistry>((resolve) => {
await getClanAccountInfo(
idToRegion(replayData.protagonist),
replayData.protagonist,
["clan"],
['clan'],
)
)?.clan;
const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined;
@@ -521,22 +506,22 @@ export const replayCommand = new Promise<CommandRegistry>((resolve) => {
image={clanImage}
description={`${
replayData.protagonist_team === replayData.winner_team
? "Victory"
: "Defeat"
? 'Victory'
: 'Defeat'
} • ${awaitedMapDefinitions.maps[replayData.map_id].name}`}
/>
<div
style={{
display: "flex",
display: 'flex',
gap: 8,
}}
>
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
gap: 4,
}}
>
@@ -562,8 +547,8 @@ export const replayCommand = new Promise<CommandRegistry>((resolve) => {
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
display: 'flex',
flexDirection: 'column',
gap: 4,
}}
>
@@ -584,8 +569,8 @@ export const replayCommand = new Promise<CommandRegistry>((resolve) => {
</div>
</div>
</CommandWrapper>,
buttonLink(submissionData.data.view_url, "WoT Inspector"),
buttonLink(submissionData.data.download_url, "Download"),
buttonLink(submissionData.data.view_url, 'WoT Inspector'),
buttonLink(submissionData.data.download_url, 'Download'),
];
},
});
@@ -1,13 +1,11 @@
import {
fetchAverageDefinitions,
fetchDiscoveredIdsDefinitions,
fetchGameDefinitions,
fetchMapDefinitions,
fetchTankDefinitions,
fetchTankNames,
} from "@blitzkit/core";
} from '@blitzkit/core';
export const averageDefinitions = fetchAverageDefinitions();
export const tankDefinitions = fetchTankDefinitions();
export const gameDefinitions = fetchGameDefinitions();
export const tankNames = fetchTankNames();
+18 -26
View File
@@ -1,37 +1,29 @@
import { usersDatabase } from "../db/users";
import { client } from "../discord/client";
import { usersDatabase } from '../db/users';
import { client } from '../discord/client';
const EXIT_EVENTS = ["SIGINT", "SIGTERM", "SIGUSR1", "SIGUSR2"];
const EXIT_EVENTS = [
'beforeExit',
'exit',
'SIGINT',
'SIGTERM',
'SIGUSR1',
'SIGUSR2',
];
let cleaningUp = false;
async function cleanup() {
console.log('Cleaning up...');
async function cleanup(signal: string) {
if (cleaningUp) return;
cleaningUp = true;
await Promise.all([usersDatabase.$disconnect(), client.destroy()]);
console.log(`Cleaning up... (${signal})`);
try {
await Promise.all([usersDatabase.$disconnect(), client.destroy()]);
} catch (err) {
console.error("Cleanup error:", err);
}
console.log("Gracefully exiting...");
console.log('Gracefully exiting...');
EXIT_EVENTS.forEach((event) => process.off(event, cleanup));
process.exit(0);
}
export function registerProcesses() {
process.on("uncaughtException", (err) => {
console.error("uncaughtException:", err);
});
process.on('uncaughtException', console.error);
client.on('error', console.error);
process.on("unhandledRejection", (err) => {
console.error("unhandledRejection:", err);
});
client.on("error", console.error);
EXIT_EVENTS.forEach((event) => process.on(event, () => cleanup(event)));
EXIT_EVENTS.forEach((event) => process.on(event, cleanup));
}
+11 -15
View File
@@ -3,10 +3,8 @@ import type { SupplementaryStats } from "@blitzkit/core/src/blitz/getAccountInfo
import { calculateWN8 } from "@blitzkit/core/src/statistics/calculateWN8";
import type { BlitzStats } from "@blitzkit/core/src/statistics/compositeStats";
import { sumAllStats } from "@blitzkit/core/src/statistics/sumAllStats";
import {
averageDefinitions,
tankDefinitions,
} from "../blitzkit/nonBlockingPromises";
import { tankDefinitions } from "../blitzkit/nonBlockingPromises";
import { blitzStarsTankAverages } from "./tankAverages";
export interface StatFilters {
nation?: string;
@@ -18,13 +16,13 @@ export interface StatFilters {
export async function filterStats(
{ diff, order }: DiffedTankStats,
filters: StatFilters,
filters: StatFilters
) {
// tank, if provided, takes priority over all other filters
if (filters.tank) filters = { tank: filters.tank };
const awaitedTankDefinitions = await tankDefinitions;
const awaitedTankAverages = await averageDefinitions;
const awaitedTankAverages = await blitzStarsTankAverages;
const filteredOrder = order.filter((id) => {
const entry = awaitedTankDefinitions.tanks[id];
@@ -46,28 +44,26 @@ export async function filterStats(
const stats = sumAllStats(filteredOrder.map((id) => diff[id]));
const battlesOfTanksWithAverages = filteredOrder.reduce<number>(
(accumulator, id) =>
awaitedTankAverages.averages[id]
? accumulator + diff[id].battles
: accumulator,
0,
awaitedTankAverages[id] ? accumulator + diff[id].battles : accumulator,
0
);
const battlesOfTanksWithTankDefinition = filteredOrder.reduce<number>(
(accumulator, id) =>
awaitedTankDefinitions.tanks[id]
? accumulator + diff[id].battles
: accumulator,
0,
0
);
const supplementary = {
WN8:
filteredOrder.reduce<number>(
(accumulator, id) =>
awaitedTankAverages.averages[id]
awaitedTankAverages[id]
? accumulator +
calculateWN8(awaitedTankAverages.averages[id].mu, diff[id]) *
calculateWN8(awaitedTankAverages[id].all, diff[id]) *
diff[id].battles
: accumulator,
0,
0
) / battlesOfTanksWithAverages,
tier:
filteredOrder.reduce<number>(
@@ -76,7 +72,7 @@ export async function filterStats(
? accumulator +
awaitedTankDefinitions.tanks[id]!.tier * diff[id].battles
: accumulator,
0,
0
) / battlesOfTanksWithTankDefinition,
} satisfies SupplementaryStats;
@@ -0,0 +1,101 @@
import { BlitzStats } from '@blitzkit/core';
import { context } from '@blitzkit/core/src/blitzkit/context';
interface SpecialStats {
winrate: number;
damageRatio: number;
kdr: number;
damagePerBattle: number;
killsPerBattle: number;
hitsPerBattle: number;
spotsPerBattle: number;
wpm: number;
dpm: number;
kpm: number;
hitRate: number;
survivalRate: number;
}
export type Percentiles = [
number,
number,
number,
number,
number,
number,
number,
number,
number,
];
export interface SpecialPercentiles {
winRate: Percentiles;
damageRatio: Percentiles;
kdr: Percentiles;
damagePerBattle: Percentiles;
killsPerBattle: Percentiles;
hitsPerBattle: Percentiles;
spotsPerBattle: Percentiles;
wpm: Percentiles;
dpm: Percentiles;
kpm: Percentiles;
hitRate: Percentiles;
survivalRate: Percentiles;
}
export interface MeanSd {
winrateMean: number;
winrateSd: number;
hitRateMean: number;
hitRateSd: number;
survivalRateMean: number;
survivalRateSd: number;
damageRatioMean: number;
damageRatioSd: number;
kdrMean: number;
kdrSd: number;
dpbMean: number;
dpbSd: number;
kpbMean: number;
kpbSd: number;
hpbMean: number;
hpbSd: number;
spbMean: number;
spbSd: number;
}
export interface IndividualTankAverage {
_id: number;
battle_life_time: number;
total_battle_life_time: number;
number_of_players: number;
tank_id: number;
all: BlitzStats;
special: SpecialStats;
percentiles: SpecialPercentiles;
percentiles_player_count: number;
meanSd: MeanSd;
shotEff: number;
last_update: number;
}
export type BlitzStarsTankAverages = Record<number, IndividualTankAverage>;
export const blitzStarsTankAverages = fetch(
'https://www.blitzstars.com/api/tankaverages.json',
)
.then((response) => response.json())
.then((json) => {
if (context === 'server') {
const partialTankAverages: BlitzStarsTankAverages = {};
(json as IndividualTankAverage[]).forEach((individualTankAverage) => {
partialTankAverages[individualTankAverage.tank_id] =
individualTankAverage;
});
return partialTankAverages;
} else {
return json as BlitzStarsTankAverages;
}
});
+5 -5
View File
@@ -1,9 +1,9 @@
import { Client, GatewayIntentBits } from "discord.js";
import { interactionCreate } from "../../events/interactionCreate";
import { ready } from "../../events/ready";
import { Client, GatewayIntentBits } from 'discord.js';
import { interactionCreate } from '../../events/interactionCreate';
import { ready } from '../../events/ready';
export const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
})
.on("clientReady", ready)
.on("interactionCreate", interactionCreate);
.on('ready', ready)
.on('interactionCreate', interactionCreate);
@@ -22,11 +22,11 @@ export function createLocalizedCommand(
command: string,
sub?:
| CreateLocalizedCommandSubcommand[]
| CreateLocalizedCommandSubcommandGroup[],
| CreateLocalizedCommandSubcommandGroup[]
) {
const commandPathItem = command.replaceAll(
"-",
"_",
"_"
) as keyof BlitzKitStrings["bot"]["commands"];
const { strings } = translator(Locale.EnglishUS);
const slashCommand = new SlashCommandBuilder()
@@ -35,14 +35,14 @@ export function createLocalizedCommand(
localizationObject(
(strings) => strings.bot.commands[commandPathItem].name,
undefined,
true,
),
true
)
)
.setDescription(strings.bot.commands[commandPathItem].description)
.setDescriptionLocalizations(
localizationObject(
(strings) => strings.bot.commands[commandPathItem].description,
),
(strings) => strings.bot.commands[commandPathItem].description
)
);
if (sub) {
@@ -56,7 +56,7 @@ export function createLocalizedCommand(
}
).subcommands[subItem.subcommand].name,
undefined,
true,
true
);
const subcommandDescriptionLocalizations = localizationObject(
(strings) =>
@@ -64,7 +64,7 @@ export function createLocalizedCommand(
strings.bot.commands[commandPathItem] as unknown as {
subcommands: Record<string, { description: string }>;
}
).subcommands[subItem.subcommand].description,
).subcommands[subItem.subcommand].description
);
slashCommand.addSubcommand((option) => {
@@ -78,7 +78,7 @@ export function createLocalizedCommand(
strings.bot.commands[commandPathItem] as unknown as {
subcommands: Record<string, { description: string }>;
}
).subcommands[subItem.subcommand].description,
).subcommands[subItem.subcommand].description
)
.setDescriptionLocalizations(subcommandDescriptionLocalizations);
});
@@ -91,7 +91,7 @@ export function createLocalizedCommand(
}
).groups[subItem.group].name,
undefined,
true,
true
);
const subcommandDescriptionLocalizations = localizationObject(
(strings) =>
@@ -99,7 +99,7 @@ export function createLocalizedCommand(
strings.bot.commands[commandPathItem] as unknown as {
groups: Record<string, { description: string }>;
}
).groups[subItem.group].description,
).groups[subItem.group].description
);
slashCommand.addSubcommandGroup((option) => {
@@ -113,7 +113,7 @@ export function createLocalizedCommand(
strings.bot.commands[commandPathItem] as unknown as {
groups: Record<string, { description: string }>;
}
).groups[subItem.group].description,
).groups[subItem.group].description
)
.setDescriptionLocalizations(subcommandDescriptionLocalizations);
});
@@ -1,10 +1,10 @@
import { AutocompleteInteraction, CacheType } from "discord.js";
import { commands } from "..";
import { AutocompleteInteraction, CacheType } from 'discord.js';
import { commands } from '..';
export async function handleAutocomplete(
interaction: AutocompleteInteraction<CacheType>,
) {
const command = commands[interaction.commandName];
const command = (await commands)[interaction.commandName];
if (!command) {
console.error(`No command matching ${interaction.commandName} was found.`);
@@ -1,13 +1,13 @@
import { ButtonInteraction, CacheType } from "discord.js";
import { commands } from "..";
import { normalizeInteractionReturnable } from "../../../core/discord/normalizeInteractionReturnable";
import { ButtonInteraction, CacheType } from 'discord.js';
import { commands } from '..';
import { normalizeInteractionReturnable } from '../../../core/discord/normalizeInteractionReturnable';
export async function handleButton(interaction: ButtonInteraction<CacheType>) {
await interaction.deferUpdate();
const url = new URL(`https://example.com/${interaction.customId}`);
const commandName = url.pathname.split("/")[1];
const button = commands[commandName]?.button;
const commandName = url.pathname.split('/')[1];
const button = (await commands)[commandName]?.button;
if (!button) {
throw new Error(`Button handler not found for "${commandName}"`);
@@ -16,7 +16,7 @@ export async function handleButton(interaction: ButtonInteraction<CacheType>) {
if (interaction.message.interaction?.user.id !== interaction.user.id) {
interaction.followUp({
ephemeral: true,
content: "You are not the author of this command.",
content: 'You are not the author of this command.',
});
return;
}
@@ -17,11 +17,12 @@ import { translator } from "../../../core/localization/translator";
import type { Writeable } from "../../../types/writable";
export async function handleChatInputCommand(
interaction: ChatInputCommandInteraction<CacheType>,
interaction: ChatInputCommandInteraction<CacheType>
) {
await interaction.deferReply();
const registry = commands[interaction.commandName];
const awaitedCommands = await commands;
const registry = awaitedCommands[interaction.commandName];
try {
const returnable = await registry.handler(interaction);
@@ -35,7 +36,7 @@ export async function handleChatInputCommand(
if (typeof object === "string") {
chunks.push([object]);
} else chunks.at(-1)?.push(object!);
},
}
);
let index = 0;
@@ -58,7 +59,7 @@ export async function handleChatInputCommand(
} else if (psa.data.type === "image") {
if (!reply.files) reply.files = [];
(reply.files as Writeable<typeof reply.files>).push(
new AttachmentBuilder(psa.data.image),
new AttachmentBuilder(psa.data.image)
);
reply.content = reply.content ?? psa.data.title;
}
@@ -71,7 +72,7 @@ export async function handleChatInputCommand(
(
reply.components[0] as ActionRowBuilder<ButtonBuilder>
).addComponents(
psa.data.links.map(({ title, url }) => buttonLink(url, title)),
psa.data.links.map(({ title, url }) => buttonLink(url, title))
);
}
}
@@ -102,7 +103,7 @@ export async function handleChatInputCommand(
} else if (psa.data.type === "image") {
if (!followUp.files) followUp.files = [];
(followUp.files as Writeable<typeof followUp.files>).push(
new AttachmentBuilder(psa.data.image),
new AttachmentBuilder(psa.data.image)
);
followUp.content = followUp.content ?? psa.data.title;
}
@@ -115,7 +116,7 @@ export async function handleChatInputCommand(
(
followUp.components[0] as ActionRowBuilder<ButtonBuilder>
).addComponents(
psa.data.links.map(({ title, url }) => buttonLink(url, title)),
psa.data.links.map(({ title, url }) => buttonLink(url, title))
);
}
@@ -132,7 +133,7 @@ export async function handleChatInputCommand(
new ButtonBuilder()
.setLabel(strings.bot.common.errors.get_help)
.setURL("https://discord.gg/nDt7AjGJQH")
.setStyle(ButtonStyle.Link),
.setStyle(ButtonStyle.Link)
),
];
if (error instanceof UserError) {
@@ -72,13 +72,13 @@ interface CommandRegistryImplicit extends CommandRegistryBase {
interface CommandRegistryExplicit extends CommandRegistryBase {
handlesInteraction?: false;
handler: (
interaction: ChatInputCommandInteraction<CacheType>,
interaction: ChatInputCommandInteraction<CacheType>
) => InteractionReturnable;
}
export type CommandRegistry = CommandRegistryImplicit | CommandRegistryExplicit;
export const commandsArray: Promise<CommandRegistry>[] = [
export const COMMANDS_RAW: Promise<CommandRegistry>[] = [
// auctionCommand,
permissionsCommand,
debugCommand,
@@ -101,28 +101,34 @@ export const commandsArray: Promise<CommandRegistry>[] = [
replayCommand,
];
export const commands: Record<string, CommandRegistry> = {};
const commandsFulfilled: CommandRegistry[] = [];
let index = 0;
for (const registry of await Promise.allSettled(commandsArray)) {
if (registry.status === "rejected") {
console.warn(`Command ${index} rejected`, registry.reason);
continue;
}
commandsFulfilled.push(registry.value);
commands[registry.value.command.name] = registry.value;
index++;
}
export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => {
return rawCommands.reduce<Record<string, CommandRegistry>>(
(commands, registry, index) => {
if (registry.status === "rejected") {
console.warn(
`Command ${index} failed to load; skipping...`,
registry.reason
);
return commands;
}
return { ...commands, [registry.value.command.name]: registry.value };
},
{}
);
});
const rest = new REST().setToken(assertSecret(import.meta.env.DISCORD_TOKEN));
const body = commandsFulfilled.map((registry) => registry.command.toJSON());
const applicationCommandsRoute = Routes.applicationCommands(
assertSecret(import.meta.env.DISCORD_CLIENT_ID),
);
await rest.put(applicationCommandsRoute, { body });
commands.then((awaitedCommands) => {
const body = Object.values(awaitedCommands).map((registry) =>
registry.command.toJSON()
);
rest.put(
Routes.applicationCommands(assertSecret(import.meta.env.DISCORD_CLIENT_ID)),
{ body }
);
});
export async function interactionCreate(interaction: Interaction<CacheType>) {
if (interaction.isAutocomplete()) {
+9 -9
View File
@@ -1,9 +1,9 @@
import { assertSecret } from "@blitzkit/core";
import { ActivityType, ShardingManager } from "discord.js";
import { assertSecret } from '@blitzkit/core';
import { ActivityType, ShardingManager } from 'discord.js';
import {
discoveredIdsDefinitions,
tankDefinitions,
} from "./core/blitzkit/nonBlockingPromises";
} from './core/blitzkit/nonBlockingPromises';
let iteration = 0;
const interval = setInterval(async () => {
@@ -11,15 +11,15 @@ const interval = setInterval(async () => {
console.log(`Attempting launch on iteration ${iteration}`);
const manager = new ShardingManager("dist/bot/workers/bot.js", {
const manager = new ShardingManager('dist/bot/workers/bot.js', {
token: assertSecret(import.meta.env.DISCORD_TOKEN),
});
const shards = await manager
.on("shardCreate", (shard) => {
.on('shardCreate', (shard) => {
clearInterval(interval);
console.log(`🟡 Launching shard ${shard.id}`);
shard.on("ready", () => console.log(`🟢 Launched shard ${shard.id}`));
shard.on('ready', () => console.log(`🟢 Launched shard ${shard.id}`));
})
.spawn();
@@ -29,7 +29,7 @@ const interval = setInterval(async () => {
let users = 0;
for (const [, shard] of shards) {
const guilds = (await shard.fetchClientValue("guilds.cache")) as any;
const guilds = (await shard.fetchClientValue('guilds.cache')) as any;
servers += guilds.length;
@@ -66,7 +66,7 @@ const interval = setInterval(async () => {
for (const guild of client.guilds.cache.values()) {
try {
const botMember = await guild.members.fetch(client.user.id);
await botMember.setNickname("BlitzKit");
await botMember.setNickname('BlitzKit');
} catch {}
}
},
@@ -74,7 +74,7 @@ const interval = setInterval(async () => {
);
} catch (error) {
console.warn(
"An error occurred when setting up presence, ignoring to keep the process alive",
'An error occurred when setting up presence, ignoring to keep the process alive',
error,
);
}
+3 -3
View File
@@ -1,6 +1,6 @@
import { assertSecret } from "@blitzkit/core";
import { registerProcesses } from "../core/blitzkit/processes";
import { client } from "../core/discord/client";
import { assertSecret } from '@blitzkit/core';
import { registerProcesses } from '../core/blitzkit/processes';
import { client } from '../core/discord/client';
console.log(`🟡 Launching bot ${client.shard?.ids[0]}`);
@@ -4,54 +4,63 @@ import {
GunDefinition,
Provision,
TankDefinition,
} from "../protos";
} from '../protos';
export function checkConsumableProvisionInclusivity(
consumableProvision: Consumable | Provision,
tank: TankDefinition,
gun: GunDefinition
gun: GunDefinition,
) {
const included = consumableProvision.include?.every((rule) => {
switch (rule.filter_type!.$case) {
case "tiers":
return (
rule.filter_type!.value.min <= tank.tier &&
tank.tier <= rule.filter_type!.value.max
);
const included =
!consumableProvision.game_mode_exclusive &&
consumableProvision.include?.every((rule) => {
switch (rule.filter_type!.$case) {
case 'tiers':
return (
rule.filter_type!.value.min <= tank.tier &&
tank.tier <= rule.filter_type!.value.max
);
case "ids":
return rule.filter_type!.value.ids.includes(tank.id);
case 'ids':
return rule.filter_type!.value.ids.includes(tank.id);
case "nations":
return rule.filter_type!.value.nations.includes(tank.nation);
case 'nations':
return rule.filter_type!.value.nations.includes(tank.nation);
case "categories":
throw new SyntaxError("Category filtering found in include rule");
}
});
const excluded = consumableProvision.exclude?.some((rule) => {
switch (rule.filter_type!.$case) {
case "tiers":
return (
rule.filter_type!.value.min <= tank.tier &&
tank.tier <= rule.filter_type!.value.max
);
case 'categories':
throw new SyntaxError('Category filtering found in include rule');
}
});
const excluded =
consumableProvision.game_mode_exclusive ||
consumableProvision.exclude?.some((rule) => {
switch (rule.filter_type!.$case) {
case 'tiers':
return (
rule.filter_type!.value.min <= tank.tier &&
tank.tier <= rule.filter_type!.value.max
);
case "ids":
return rule.filter_type!.value.ids.includes(tank.id);
case 'ids':
return rule.filter_type!.value.ids.includes(tank.id);
case "nations":
return rule.filter_type!.value.nations.includes(tank.nation);
case 'nations':
return rule.filter_type!.value.nations.includes(tank.nation);
case "categories":
return rule.filter_type!.value.categories.some((category) => {
switch (category) {
case ConsumableTankCategoryFilterCategory.CLIP:
return gun.gun_type!.$case !== "regular";
}
});
}
});
case 'categories':
return rule.filter_type!.value.categories.some((category) => {
switch (category) {
case ConsumableTankCategoryFilterCategory.CLIP:
return gun.gun_type!.$case !== 'regular';
}
});
}
});
return consumableProvision.include?.length > 0 && included && !excluded;
return (
!consumableProvision.game_mode_exclusive &&
consumableProvision.include?.length > 0 &&
included &&
!excluded
);
}
+1 -1
View File
@@ -35,7 +35,7 @@ for (const locale in stringsRaw) {
const localeStrings = stringsRaw[locale];
STRINGS[locale] = merge(
stringsRaw[locales.default],
localeStrings,
localeStrings
) as BlitzKitStrings;
}
+5 -22
View File
@@ -15,8 +15,7 @@
"hp": "{value}hp",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/s",
"plus": "+{value}"
"deg_s": "{value}°/s"
},
"gun_types": {
@@ -27,8 +26,8 @@
"shells": {
"ap": "AP",
"ap_cr": "APCR",
"hc": "HEAT",
"apcr": "APCR",
"heat": "HEAT",
"he": "HE"
},
@@ -359,14 +358,7 @@
"commands": {
"replay": {
"name": "replay",
"description": "Display replay results",
"options": {
"file": {
"name": "file",
"description": "Replay file"
}
}
"description": "Display replay results"
},
"research": {
@@ -767,17 +759,11 @@
"count_singular": "1 tank",
"count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}",
"clear": "Clear",
"tier": "Tier {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "No tanks found.",
"search_again": "Try searching again.",
"clear_filters": "Try clearing filters."
},
"sort": {
"meta.none": "none",
"survivability.health": "health",
@@ -937,10 +923,7 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
},
"clear": "Clear",
"select_all": "Select all",
"tier": "Tier {tier}"
}
},
"tankopedia": {
+4 -12
View File
@@ -13,8 +13,7 @@
"hp": "{value}hp",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/s",
"plus": "+{value}"
"deg_s": "{value}°/s"
},
"gun_types": {
"auto_loader": "Auto cargador",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "AP",
"ap_cr": "APCR",
"hc": "HEAT",
"apcr": "APCR",
"heat": "AEAC",
"he": "AE"
},
"player_count_period": {
@@ -669,10 +668,6 @@
"count_singular": "1 tanque",
"count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}",
"clear": "Limpiar",
"tier": "Tier {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "No se encontraron tanques.",
"search_again": "Intenta buscar de nuevo.",
@@ -825,10 +820,7 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancelar",
"reveal": "Reveal"
},
"clear": "Limpiar",
"select_all": "Seleccionar todo",
"tier": "Tier {tier}"
}
},
"tankopedia": {
"search": "Search a stat...",
+90 -98
View File
@@ -1,7 +1,7 @@
{
"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}.",
"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": "Le renard brun et rapide saute par-dessus le chien paresseux."
},
"common": {
@@ -10,11 +10,10 @@
"m": "{value}m",
"mm": "{value}mm",
"deg": "{value}°",
"hp": "{value}pv",
"hp": "{value}hp",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/s",
"plus": "+{value}"
"deg_s": "{value}°/s"
},
"gun_types": {
"auto_loader": "Chargement auto",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "AP",
"ap_cr": "APCR",
"hc": "HEAT",
"apcr": "APCR",
"heat": "HEAT",
"he": "HE"
},
"player_count_period": {
@@ -187,11 +186,11 @@
"bot": {
"common": {
"errors": {
"clan_not_found": "# Impossible de trouver le clan\nImpossible de trouver «{search} ». Essayez de sélectionner un clan dans le résultat de la recherche.",
"player_not_found": "# Impossible de trouver l'utilisateur\nJe n'ai pas pu trouver l'utilisateur \"{search}\". Essayez de choisir un utilisateur dans le résultat de la recherche, en tapant un nom d'utilisateur valide, ou en utilisant la commande `/link`.",
"clan_not_found": "# Could not find clan\nCouldn't find \"{search}\". Try selecting a clan from the search result.",
"player_not_found": "# Could not find user\nI couldn't find user \"{search}\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.",
"player_not_linked": "# Utilisez `/link` pour commencer\nJe ne sais pas qui vous êtes, mais vous pouvez me le dire aujourd'hui !",
"tank_name_not_found": "# Char introuvable\nImpossible de trouver le char sous le nom \"{search}\".",
"tank_id_not_found": "# Char introuvable\nImpossible de trouver le char avec l'ID \"{search}\". Essayez de taper le nom du char au lieu d'un nombre.",
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"{search}\".",
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"{search}\". Try typing the name of the tank instead of a number.",
"no_tank_stats": "# Aucune statistique de tank disponible\nCe joueur n'a pas de statistiques disponibles. Ce n'est peut-être pas le joueur que vous cherchez.",
"get_help": "Aidez le serveur Discord",
"uncaught_error": "# Blitzkit a rencontré une erreur !\nNous sommes désolés de cela. N'hésitez pas à [rejoindre le serveur officiel Discord](https://discord.gg/nDt7AjGJQH) pour obtenir de l'aide.",
@@ -285,19 +284,19 @@
"name": "rang",
"description": "Rang",
"choices": {
"tier": "Rang {tier_}1 - {tier_}2"
"tier": "Tier {tier_1} - {tier_2}"
}
}
},
"periods": {
"career": "Carrière",
"today": "Aujourd'hui",
"days": "{days} jours",
"custom": "{day_1} - {day_2} jours"
"days": "{days} days",
"custom": "{day_1} - {day_2} days"
},
"filters": {
"none": "Aucun filtre",
"tier": "Rang {tier}"
"tier": "Tier {tier}"
},
"no_data": {
"battles_in_period": "",
@@ -340,19 +339,19 @@
"body": {
"subtitle": "Recherche",
"total": "Total",
"research": "Recherche : {cost}",
"upgrades": "Améliorations : {cost}",
"purchase": "Achat : {cost}",
"equipment": "Équipement : {cost}",
"research": "Research: {cost}",
"upgrades": "Upgrades: {cost}",
"purchase": "Purchase: {cost}",
"equipment": "Equipment: {cost}",
"estimation": "# Ceci est une estimation !\n\nCette liste peut inclure des chars déjà recherchés en raison de l'information inexacte fournie par Wargaming. Essayez de fournir un `char de départ` depuis la ligne lorsque vous utilisez la commande si vous le pouvez."
},
"errors": {
"non_tech_tree": "# {tank} n'est pas dans l'arbre technologique\n\nVous ne pouvez pas rechercher un char premium/de collection.",
"tier_1": "# {tank} est un char de rang I\n\nLe char de rang 1 sont déverrouillé gratuitement pour tous les joueurs.",
"already_researched": "# {tank} est déjà recherché\n\nEssayez de fournir un `char de départ` en utilisant la commande pour voir la progression d'une lignée.",
"start_end_equal": "# {tank} a été fourni deux fois\n\nAssurez-vous que `starting-tank` et `target-tank` sont différents.",
"tanks_not_on_line": "{tank_}1 et {tank_}2 ne sont pas sur la même ligne \n\nAssurez-vous que `starting-tank` et `target-tank` sont sur la même ligne d'arbre technologique.",
"unordered": "# {tank_1} niveau supérieur à {tank_2}\n\nAssurez-vous de fournir {tank_2} comme le `starting-tank` à la place car c'est le char de niveau inférieur."
"non_tech_tree": "# {tank} is not on the tech tree\n\nYou cannot research a premium/collector tank.",
"tier_1": "# {tank} is a tier I\n\nTier I tanks are always unlocked for free for all players.",
"already_researched": "# {tank} is already researched\n\nTry providing a `starting-tank` when using the command to view the totals for a line you have already researched.",
"start_end_equal": "# {tank} was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
"tanks_not_on_line": "# {tank_1} and {tank_2} are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
"unordered": "# {tank_1} higher tiered to {tank_2}\n\nMake sure to provide {tank_2} as the `starting-tank` instead as it is the lower tiered tank."
}
},
"about": {
@@ -395,7 +394,7 @@
"wn8": "WN8",
"winrate": "Taux de victoires",
"damage": "Dégâts",
"unknown_tank": "Char {id} inconnu"
"unknown_tank": "Unknown tank {id}"
}
},
"debug": {
@@ -431,12 +430,12 @@
"options": {
"threshold": {
"name": "seuil",
"description": "Le nombre de jours inactifs (par défaut: {days} jours)"
"description": "The number of days inactive (default: {days} days)"
}
},
"body": {
"subtitle": "Inactif depuis plus de {days} jours",
"listing": "{days} jours"
"subtitle": "Inactive for {days}+ days",
"listing": "{days} days"
}
},
"full_stats": {
@@ -479,7 +478,7 @@
"body": {
"accounts_linked": "Comptes liés",
"no_role_permissions": {
"title": "La vérification de {name} a échouée",
"title": "{name} failed to verify",
"description": "Je n'ai pas l'autorisation de gérer les rôles."
}
},
@@ -494,11 +493,11 @@
"options": {
"limit": {
"name": "limite",
"description": "Nombre maximum de résultats du clan (par défaut: {limit})"
"description": "The max number of clan results (default: {limit})"
}
},
"body": {
"title": "Recherche de clan {search} dans la région {region}",
"title": "Clan search for {search} in region {region}",
"no_results": "Pas de clan trouvé"
}
},
@@ -508,11 +507,11 @@
"options": {
"limit": {
"name": "limite",
"description": "Le nombre maximum de résultats de joueur (par défaut: {limit})"
"description": "The max number of player results (default: {limit})"
}
},
"body": {
"title": "Recherche de {search} dans la région {region}",
"title": "Player search for {search} in region {region}",
"no_results": "Pas de joueur de trouvé"
}
},
@@ -532,13 +531,13 @@
"options": {
"limit": {
"name": "limite",
"description": "Combien de voisins afficher (par défaut : {count})"
"description": "How many neighbors to display (default: {count})"
},
"season": {
"name": "saison",
"description": "Numéro de saison (défaut : courant)",
"choices": {
"current": "{season} (actuel)"
"current": "{season} (current)"
}
},
"league": {
@@ -547,12 +546,12 @@
}
},
"body": {
"top_players": "Top {count} joueurs",
"no_participation": "# {player} n'a pas joué de cotées dans la saison {season}\n\nCe joueur n'a pas participé à cette saison ou n'a pas dépassé l'étalonnage.",
"top_players": "Top {count} players",
"no_participation": "# {player} didn't play rating in season {season}\n\nThis player did not participate in this season or did not get past calibration.",
"no_ongoing_season": "Pas de saison en cours\n\nIl n'y a pas de saison de notation en cours actuellement.",
"deleted_player": "{id} supprimé",
"deleted_player": "Deleted {id}",
"neighbors": "évaluer les voisins",
"more": "+ {count} plus"
"more": "+ {count} more"
}
},
"today": {
@@ -567,7 +566,7 @@
"name": "chars possédés",
"description": "Affiche tous les chars d'un niveau",
"body": {
"title": "# Les chars du palier {tier} possédés par {name}"
"title": "# {name}'s owned tier {tier} tanks\n\n"
}
},
"permissions": {
@@ -603,96 +602,92 @@
"name": "info-joueur",
"description": "Informations de base sur un joueur",
"body": {
"title": "Informations de {name}",
"nickname": "Pseudo",
"title": "{name}'s information",
"nickname": "Nickname",
"battles": "Batailles",
"winrate": "Taux de victoires",
"account_id": "Account ID",
"created": "Créé",
"last_battle": "Dernière bataille"
"created": "Created",
"last_battle": "Last battle"
}
},
"rating": {
"name": "côtée",
"description": "Statistiques des batailles cotées du joueur",
"description": "Rating player's statistics",
"subcommands": {
"today": {
"name": "aujourd'hui",
"description": "Statistiques des batailles cotées d'aujourd'hui"
"description": "Today's rating statistics"
},
"season": {
"name": "saison",
"description": "Statistiques des batailles cotées de cette saison"
"description": "This season's rating statistics"
}
},
"body": {
"subtitle": {
"today": "Statistiques de batailles cotées d'aujourd'hui",
"season": "Statistiques de batailles cotées cette saison"
"today": "Today's rating statistics",
"season": "This season's rating statistics"
}
},
"errors": {
"no_participation": "# {name} n’a pas joué en cotée\n\nCe joueur n’a pas participé au mode cotée cette saison.",
"unrecorded_stats": "# Statistiques actuellement indisponibles\n\nWargaming rencontre peut-être une interruption de service.\nSi une saison est en cours, les statistiques devraient être disponibles sous 24 heures."
"no_participation": "# {name} hasn't played rating\n\nThis player hasn't participated in the rating gamemode this season.",
"unrecorded_stats": "# Statistics currently unavailable\n\nWargaming may be currently be facing server downtime. If there is an on-going season, statistics should be available withing 24 hours."
}
}
}
},
"website": {
"redirect": {
"title": "301 : déplacé définitivement",
"body": "Cette page a été déplacée. Redirection en cours…"
"title": "301: moved permanently",
"body": "This page has moved. Redirecting..."
},
"settings": {
"title": "Paramètres",
"description": "Modifier les paramètres de BlitzKit",
"dev_mode": "Mode développement",
"language": "Langue"
"title": "Settings",
"description": "Change BlitzKit settings",
"dev_mode": "Development mode",
"language": "Language"
},
"common": {
"player_search": {
"hint": "Recherche de joueurs...",
"no_results": "Aucun résultat",
"searching": "Recherche..."
"hint": "Search players...",
"no_results": "No results",
"searching": "Searching..."
},
"copy_button": {
"copied": "Copié !"
"copied": "Copied!"
},
"tank_search": {
"select_dialog_title": "Recherche de chars",
"swap_dialog_title": "Échanger le char",
"clear_filters": "Supprimer les filtres",
"login": "Connectez-vous pour utiliser ceci",
"search_bar_hint": "Recherche des tanks...",
"recent": "Vu récemment",
"select_all": "Sélectionner tout",
"count_singular": "1 char",
"count_plural": "{count} chars",
"sorting_by": "Trier par {name}",
"clear": "Effacer",
"tier": "Rang {tier}",
"passive": "{name} (Passif)",
"active": "{name} (actif)",
"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}",
"no_tanks_found": {
"body": "Aucun tank trouvé.",
"search_again": "Essayez de chercher à nouveau.",
"clear_filters": "Essayez de supprimer les filtres."
"body": "No tanks found.",
"search_again": "Try searching again.",
"clear_filters": "Try clearing filters."
},
"sort": {
"meta.none": "aucun",
"survivability.health": "santé",
"survivability.viewRange": "portée de vue",
"survivability.camouflageStill": "camouflage à l’arrêt",
"survivability.camouflageMoving": "camouflage en movement",
"survivability.camouflageShooting": "camouflage en tirant",
"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": "longueur",
"fire.dpm": "DPM standard",
"fire.dpmPremium": "DPM premium",
"fire.reload": "rechargement",
"fire.caliber": "calibration",
"fire.standardPenetration": "pénétration standard",
"fire.premiumPenetration": "pénétration premium",
"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",
@@ -817,7 +812,7 @@
"skip": "Skip",
"guess": "Guess",
"next": "Next",
"no_results": "Aucun résultat"
"no_results": "No results"
},
"stats": "{correct} / {total} guessed correctly",
"help": {
@@ -825,10 +820,7 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
},
"clear": "Clear",
"select_all": "Sélectionner tout",
"tier": "Rang {tier}"
}
},
"tankopedia": {
"search": "Search a stat...",
@@ -1211,8 +1203,8 @@
"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": "Recherche de joueurs...",
"searching": "Recherche...",
"hint": "Search players...",
"searching": "Searching...",
"no_results": "Pas de joueur de trouvé"
},
"controls": {
+10 -18
View File
@@ -13,8 +13,7 @@
"hp": "{value}hp",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/s",
"plus": "+{value}"
"deg_s": "{value}°/s"
},
"gun_types": {
"auto_loader": "自動装填砲",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "AP",
"ap_cr": "APCR",
"hc": "HEAT",
"apcr": "APCR",
"heat": "HEAT",
"he": "HE"
},
"player_count_period": {
@@ -187,11 +186,11 @@
"bot": {
"common": {
"errors": {
"clan_not_found": "",
"player_not_found": "# ユーザーが見つかりませんでした\nユーザー「{search}」が見つかりませんでした。検索結果からユーザーを選択するか、有効なユーザー名を入力するか、/link コマンドを使用してください。",
"clan_not_found": "# Could not find clan\nCouldn't find \"{search}\". Try selecting a clan from the search result.",
"player_not_found": "# Could not find user\nI couldn't find user \"{search}\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.",
"player_not_linked": "# `/link`を使用して始めましょう\nはじめまして!あなたが誰か、私に教えてください。",
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"{search}\".",
"tank_id_not_found": "# 車両が見つかりません\nID「{search}」の戦車が見つかりませんでした。数字ではなく戦車の名前を入力してみてください。",
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"{search}\". Try typing the name of the tank instead of a number.",
"no_tank_stats": "# 利用可能な車輌統計がありません\nこのプレイヤーには利用可能な統計がありません。あなたが探しているプレイヤーではない可能性があります。",
"get_help": "Discordサーバーに参加する",
"uncaught_error": "# BlitzKitがエラーに遭遇しました!\n申し訳ありません。お気軽に[公式Discordサーバー](https://discord.gg/nDt7AjGJQH)にお問い合わせください。",
@@ -395,7 +394,7 @@
"wn8": "WN8",
"winrate": "勝率",
"damage": "ダメージ",
"unknown_tank": "不明な車両{id}"
"unknown_tank": "Unknown tank {id}"
}
},
"debug": {
@@ -560,7 +559,7 @@
"description": "Today's played tanks and statistics"
},
"stats": {
"name": "",
"name": "stats",
"description": "Quick statistics for a period"
},
"owned_tanks": {
@@ -669,10 +668,6 @@
"count_singular": "1 輌",
"count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}",
"clear": "クリア",
"tier": "Tier {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "該当する車輌が存在しません。",
"search_again": "もう一度検索してください。",
@@ -783,7 +778,7 @@
"tank": "車輌名",
"last_played": "Last played",
"battles": "Career battles",
"unrecorded": "",
"unrecorded": "--",
"wins": "勝利"
},
"large_warning": {
@@ -825,10 +820,7 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
},
"clear": "クリア",
"select_all": "全て選択",
"tier": "Tier {tier}"
}
},
"tankopedia": {
"search": "Search a stat...",
+8 -16
View File
@@ -13,8 +13,7 @@
"hp": "{value}hp",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/s",
"plus": "+{value}"
"deg_s": "{value}°/s"
},
"gun_types": {
"auto_loader": "Auto loader",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "AP",
"ap_cr": "APCR",
"hc": "HEAT",
"apcr": "APCR",
"heat": "HEAT",
"he": "OB"
},
"player_count_period": {
@@ -493,7 +492,7 @@
"description": "Search clans",
"options": {
"limit": {
"name": "",
"name": "limit",
"description": "The max number of clan results (default: {limit})"
}
},
@@ -507,7 +506,7 @@
"description": "Search players",
"options": {
"limit": {
"name": "",
"name": "limit",
"description": "The max number of player results (default: {limit})"
}
},
@@ -531,7 +530,7 @@
},
"options": {
"limit": {
"name": "",
"name": "limit",
"description": "How many neighbors to display (default: {count})"
},
"season": {
@@ -669,13 +668,9 @@
"count_singular": "1 tank",
"count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}",
"clear": "Clear",
"tier": "Tier {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "No tanks found.",
"search_again": "",
"search_again": "Try searching again.",
"clear_filters": "Try clearing filters."
},
"sort": {
@@ -825,10 +820,7 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
},
"clear": "Clear",
"select_all": "Select all",
"tier": "Tier {tier}"
}
},
"tankopedia": {
"search": "Search a stat...",
+4 -12
View File
@@ -13,8 +13,7 @@
"hp": "{value}hp",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/s",
"plus": "+{value}"
"deg_s": "{value}°/s"
},
"gun_types": {
"auto_loader": "Autocarregador",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "AP",
"ap_cr": "APCR",
"hc": "HEAT",
"apcr": "APCR",
"heat": "HEAT",
"he": "HE"
},
"player_count_period": {
@@ -669,10 +668,6 @@
"count_singular": "1 tanque",
"count_plural": "{count} tanks",
"sorting_by": "Sorting by {name}",
"clear": "Limpar",
"tier": "Tier {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "Nenhum tanque encontrado.",
"search_again": "Tente pesquisar novamente.",
@@ -825,10 +820,7 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancelar",
"reveal": "Reveal"
},
"clear": "Limpar",
"select_all": "Selecionar todos",
"tier": "Tier {tier}"
}
},
"tankopedia": {
"search": "Search a stat...",
+14 -22
View File
@@ -13,8 +13,7 @@
"hp": "{value}оп",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}°/с",
"plus": "+{value}"
"deg_s": "{value}°/с"
},
"gun_types": {
"auto_loader": "Барабан",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "ББ",
"ap_cr": "БП",
"hc": "КС",
"apcr": "БП",
"heat": "КС",
"he": "ОФ"
},
"player_count_period": {
@@ -39,7 +38,7 @@
"branches": {
"local": "dev",
"dev": "бета",
"opentest": "общий тест",
"opentest": "открытый тест",
"preview": "превью"
},
"locales": {
@@ -297,7 +296,7 @@
},
"filters": {
"none": "Без фильтров",
"tier": "{tier} уровень"
"tier": " {tier} уровня"
},
"no_data": {
"battles_in_period": "За этот период боёв не было",
@@ -669,10 +668,6 @@
"count_singular": "1 танк",
"count_plural": "Танков: {count}",
"sorting_by": "Сортировать по {name}",
"clear": "Очистить",
"tier": "{tier} уровень",
"passive": "{name} (Пассивный)",
"active": "{name} (Активный)",
"no_tanks_found": {
"body": "Танков не найдено.",
"search_again": "Попробуйте повторить поиск.",
@@ -819,16 +814,13 @@
"next": "Следующий",
"no_results": "Ничего не найденo"
},
"stats": "{correct} / {total} правильно, серия: {streak}",
"stats": "{correct} / {total} correct, {streak} streak",
"help": {
"title": "Частично раскрыть танк?",
"description": "Показывает танк не показывая название. Этот раунд будет считаться проигранным и прекратит серию!",
"title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Отмена",
"reveal": "Раскрыть"
},
"clear": "Очистить",
"select_all": "Выбрать всё",
"tier": "{tier} уровень"
"reveal": "Reveal"
}
},
"tankopedia": {
"search": "Поиск характеристик...",
@@ -1149,12 +1141,12 @@
"upgrade": "Топ"
},
"provisions": {
"clear": "Убрать",
"clear_all": "Убрать со всех"
"clear": "Clear this tank",
"clear_all": "Clear all tanks"
},
"consumables": {
"clear": "Убрать",
"clear_all": "Убрать со всех"
"clear": "Clear this tank",
"clear_all": "Clear all tanks"
}
}
},
+70 -78
View File
@@ -13,8 +13,7 @@
"hp": "{value}од",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}град/c",
"plus": "+{value}"
"deg_s": "{value}град/c"
},
"gun_types": {
"auto_loader": "Магазинна система заряджання",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "ББ",
"ap_cr": "APCR",
"hc": "HEAT",
"apcr": "БП",
"heat": "КС",
"he": "ОФ"
},
"player_count_period": {
@@ -79,11 +78,11 @@
"10": "Супер винятковий"
},
"nations": {
"ussr": "С.Р.С.Р.",
"ussr": "СРСР",
"germany": "Німеччина",
"usa": "С.Ш.А.",
"usa": "ЗША",
"china": "Китай",
"uk": "Сполучене Королівство ",
"uk": "Велика Британія",
"france": "Франція",
"japan": "Японія",
"european": "Європейська нація",
@@ -121,7 +120,7 @@
"3": "ПТ-САУ"
},
"tree_type": {
"-1": "не розпізнаний",
"-1": "не розпізнано",
"0": "Звичайний",
"1": "Преміум",
"2": "Колекційний"
@@ -341,18 +340,18 @@
"subtitle": "Дослідження",
"total": "Загалом",
"research": "Дослідження: {cost}",
"upgrades": "Прокачування: {cost}",
"upgrades": "Upgrades: {cost}",
"purchase": "Вартість: {cost}",
"equipment": "Спорядження: {cost}",
"estimation": "# Це оцінка!\n\nЦей список може включати вже досліджені танки через неточну інформацію для вантажу. Спробуйте використати \"початковий танк\" з рядка за допомогою команди."
},
"errors": {
"non_tech_tree": "#{tank} відсутній в дереві дослідження\n\nВи не можете дослідити преміум/колеційний танк.",
"tier_1": "#{tank} І рівня \n\nТанки І рівня завжди розблоковані для всіх гравців безкоштовно",
"already_researched": "# {tank} вже було досліджено\n\nСпробуйте використати `початковий-танк` коли використовуєте команду для перегляду загальної гілки, яку ви вже дослідили",
"start_end_equal": "#{tank} було введено двічі \n\nПереконайтесь що `початковий-танк` та `ціль-танк` різні",
"tanks_not_on_line": "# {tank_1} та {tank_2} не в одній гілці\n\nПереконайтесь що `початковий-танк` та `ціль-танк` знаходяться в єдиній гілці дерева дослідження",
"unordered": "# {tank_1} вище рівнем відносно {tank_2}\n\nОбов’язково вкажіть {tank_2} як `початковий-танк`, оскільки це танк нижчого рівня."
"non_tech_tree": "# {tank} is not on the tech tree\n\nYou cannot research a premium/collector tank.",
"tier_1": "# {tank} is a tier I\n\nTier I tanks are always unlocked for free for all players.",
"already_researched": "# {tank} is already researched\n\nTry providing a `starting-tank` when using the command to view the totals for a line you have already researched.",
"start_end_equal": "# {tank} was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
"tanks_not_on_line": "# {tank_1} and {tank_2} are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
"unordered": "# {tank_1} higher tiered to {tank_2}\n\nMake sure to provide {tank_2} as the `starting-tank` instead as it is the lower tiered tank."
}
},
"about": {
@@ -395,7 +394,7 @@
"wn8": "WN8",
"winrate": "Відсоток перемог",
"damage": "Шкода",
"unknown_tank": "Невідомий танк {id}"
"unknown_tank": "Unknown tank {id}"
}
},
"debug": {
@@ -431,11 +430,11 @@
"options": {
"threshold": {
"name": "поріг",
"description": "Кількість днів неактивності (за замовчуванням: {days} днів)"
"description": "The number of days inactive (default: {days} days)"
}
},
"body": {
"subtitle": "Неактивний {days}+ днів",
"subtitle": "Inactive for {days}+ days",
"listing": "{days} днів"
}
},
@@ -479,7 +478,7 @@
"body": {
"accounts_linked": "Прив'язані акаунти",
"no_role_permissions": {
"title": "{name} не вдалося перевірити",
"title": "{name} failed to verify",
"description": "У мене немає дозволу керувати ролями."
}
},
@@ -494,11 +493,11 @@
"options": {
"limit": {
"name": "ліміт",
"description": "Максимальна кількість результатів клану (за замовчуванням: {limit})"
"description": "The max number of clan results (default: {limit})"
}
},
"body": {
"title": "Пошук клану для {search} в регіоні {region}",
"title": "Clan search for {search} in region {region}",
"no_results": "Жодного клану не знайдено"
}
},
@@ -508,11 +507,11 @@
"options": {
"limit": {
"name": "ліміт",
"description": "Максимальна кількість результатів гравця (за замовчуванням: {limit})"
"description": "The max number of player results (default: {limit})"
}
},
"body": {
"title": "Пошук гравця для {search} у регіоні {region}",
"title": "Player search for {search} in region {region}",
"no_results": "Гравця не знайдено"
}
},
@@ -521,8 +520,8 @@
"description": "Статистика рейтингових боїв",
"subcommands": {
"neighbors": {
"name": "сусіди",
"description": "Сусідуючі гравці в лідерборді"
"name": "neighbors",
"description": "Neighboring players on the leaderboard"
},
"league": {
"name": "ліга",
@@ -532,32 +531,32 @@
"options": {
"limit": {
"name": "ліміт",
"description": "Скільки сусідів відобразити (за замовчуванням: {count})"
"description": "How many neighbors to display (default: {count})"
},
"season": {
"name": "сезон",
"description": "Базова інформація про гравця",
"choices": {
"current": "{season} (поточний)"
"current": "{season} (current)"
}
},
"league": {
"name": "ліга",
"description": "Ліга для відображення"
"description": "The league to display"
}
},
"body": {
"top_players": "Топ {count} гравців",
"no_participation": "# {player} не грав у рейтингу в сезоні {season}\n\nЦей гравець не брав участь у цьому сезоні або не проходив калібрування",
"no_ongoing_season": "# Немає поточного сезону\n\nВ даний час немає сезону рейтингу.",
"top_players": "Top {count} players",
"no_participation": "# {player} didn't play rating in season {season}\n\nThis player did not participate in this season or did not get past calibration.",
"no_ongoing_season": "# No ongoing season\n\nThere isn't a currently running rating season.",
"deleted_player": "",
"neighbors": "Сусіди по рейтингу",
"more": "+ {count} ще"
"neighbors": "Rating neighbors",
"more": "+ {count} more"
}
},
"today": {
"name": "сьогодні",
"description": "Сьогодні зіграні танки та статистика"
"description": "Today's played tanks and statistics"
},
"stats": {
"name": "статистика",
@@ -567,43 +566,43 @@
"name": "отримані танки",
"description": "Показує всі ваші танки даного рівня",
"body": {
"title": "# {name} отриманих рівнів {tier} танків\n\n"
"title": "# {name}'s owned tier {tier} tanks\n\n"
}
},
"permissions": {
"name": "дозволи",
"description": "Перевіряє чи бот має необхідні дозволи для роботи",
"description": "Checks wether the bot has the required permissions to function",
"body": {
"title": "# Дозволи\n\n{body}",
"view_channels": "Перегляньте канали: необхідне для знаходження повідомлення, на якому використовувалася кнопка оновлення",
"read_message_history": "Прочитайте історію повідомлень: потрібно знайти повідомлення для оновлення",
"attach_files": "Закріпіть файли: потрібно для додавання зображень до команд на основі зображень",
"external_emojis": "Зовнішні емодзі: потрібні для додавання емодзі національних прапорів та класів техніки"
"title": "# Permissions\n\n{body}",
"view_channels": "View channels: needed to find the message the refresh button was used on",
"read_message_history": "Read message history: needed to find the message to refresh",
"attach_files": "Attach files: needed to add images to image-based commands",
"external_emojis": "External emojis: needed to add national flags and tank class emojis"
}
},
"ping": {
"name": "",
"description": "Перевірити чи бот живий",
"description": "Check wether the bot is alive.",
"body": {
"pong": "Понг🏓",
"pong_with_time": "Понг🏓 - {time}мс"
"pong": "Pong 🏓",
"pong_with_time": "Pong 🏓 - {time}ms"
},
"subcommands": {
"blitzkit": {
"name": "BlitzKit",
"name": "blitzkit",
"description": "Ping BlitzKit"
},
"wotb": {
"name": "WoTB",
"name": "wotb",
"description": "Ping World of Tanks Blitz"
}
}
},
"player_info": {
"name": "інформація про гравця",
"name": "player-info",
"description": "Базова інформація про гравця",
"body": {
"title": "інформація про {name}",
"title": "{name}'s information",
"nickname": "Нік",
"battles": "Бої",
"winrate": "Відсоток перемог",
@@ -614,33 +613,33 @@
},
"rating": {
"name": "рейтинг",
"description": "Статистика рейтингових боїв гравця ",
"description": "Rating player's statistics",
"subcommands": {
"today": {
"name": "сьогодні",
"description": "Сьогоднішня статистика рейтингових боїв"
"description": "Today's rating statistics"
},
"season": {
"name": "сезон",
"description": "Статистика рейтингових боїв поточного сезону"
"description": "This season's rating statistics"
}
},
"body": {
"subtitle": {
"today": "Сьогоднішня статистика рейтингових боїв",
"season": "Статистика рейтингових боїв даного сезону"
"today": "Today's rating statistics",
"season": "This season's rating statistics"
}
},
"errors": {
"no_participation": "# {name} не грав рейтингові бої\n\nЦей гравець не брав участь у рейтинговому режимі цього сезону.",
"unrecorded_stats": "# Статистика на цей момент недоступна\n\nWargaming наразі зіткнулась з технічними несправностями. Якщо зараз проходить сезон, статистика буде доступною в найближчі 24 години."
"no_participation": "# {name} hasn't played rating\n\nThis player hasn't participated in the rating gamemode this season.",
"unrecorded_stats": "# Statistics currently unavailable\n\nWargaming may be currently be facing server downtime. If there is an on-going season, statistics should be available withing 24 hours."
}
}
}
},
"website": {
"redirect": {
"title": "301: переміщено назавжди",
"title": "301: moved permanently",
"body": "This page has moved. Redirecting..."
},
"settings": {
@@ -669,10 +668,6 @@
"count_singular": "1 танк",
"count_plural": "Танків: {count} ",
"sorting_by": "Відсортувати за {name}",
"clear": "Clear",
"tier": "Рівень {tier}",
"passive": "{name} (Passive)",
"active": "{name} (Active)",
"no_tanks_found": {
"body": "Нічого не знайдено.",
"search_again": "Спробуйте шукати заново.",
@@ -825,10 +820,7 @@
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "Cancel",
"reveal": "Reveal"
},
"clear": "Clear",
"select_all": "Вибрати все",
"tier": "Рівень {tier}"
}
},
"tankopedia": {
"search": "Search a stat...",
@@ -837,33 +829,33 @@
"description": "Танкова енциклопедія",
"seo_description": "Характеристики, броня та інше для всіх танків WoT Blitz",
"tank_seo_title": "{name} - Tier {tier} {nationality} {class} | {website} Tankopedia | World of Tanks Blitz (WoTB)",
"tank_seo_description": "{name} броня, характеристики, обладнання, амуніція і т.д. {dpm} шкода на хвилину, {alpha} разова шкода, {penetration}мм пробиття, {speed} км/год максимальна швидкість, {health} міцність.",
"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"
},
"assault": {
"title": "Штурмові снаряди"
"title": "Assault shells"
},
"rotator": {
"hull": "Корпус",
"turret": "Башта",
"hard": "Жорстко",
"hull": "Hull",
"turret": "Turret",
"hard": "Hard",
"medium": "Середній",
"soft": "Ніжно",
"effective": "Ефективний"
"soft": "Soft",
"effective": "Effective"
},
"ricochet": {
"angle": "Кут",
"angle": "Angle",
"normalization": "Нормалізація",
"effective_angle": "Ефективний кут",
"effective_angle": "Effective angle",
"armor": "Броня",
"no_ricochet": "Фугасні снаряди не рикошетять"
"no_ricochet": "Explosive shells don't ricochet"
},
"view_range": {
"camo": "Камуфляж",
"ghost_factory": "Промзона",
"dead_rail": "Ешелон"
"camo": "Camo",
"ghost_factory": "Ghost Factory",
"dead_rail": "Dead Rail"
},
"flexibility": {
"yaw": "Yaw",
+9 -17
View File
@@ -13,8 +13,7 @@
"hp": "{value} 生命值",
"x": "x{value}",
"percentage": "{value}%",
"deg_s": "{value}度°/秒",
"plus": "+{value}"
"deg_s": "{value}度°/秒"
},
"gun_types": {
"auto_loader": "弹夹型坦克",
@@ -23,8 +22,8 @@
},
"shells": {
"ap": "穿甲弹",
"ap_cr": "硬式复合穿甲弹",
"hc": "反战车高爆弹",
"apcr": "硬式复合穿甲弹",
"heat": "反战车高爆弹",
"he": "高爆弹"
},
"player_count_period": {
@@ -669,10 +668,6 @@
"count_singular": "1辆战车",
"count_plural": "{count} 辆坦克",
"sorting_by": "按 {name} 排序",
"clear": "清除",
"tier": "{tier} 阶",
"passive": "{name} (被动)",
"active": "{name} (主动)",
"no_tanks_found": {
"body": "找不到战车。",
"search_again": "请尝试重新搜索。",
@@ -821,14 +816,11 @@
},
"stats": "猜对了 {correct} / {total}",
"help": {
"title": "显示坦克?",
"description": "显示坦克而不显示名字。这轮会被视为不正确,并打破你的连续记录!",
"title": "Partially reveal tank?",
"description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!",
"cancel": "取消",
"reveal": "显示"
},
"clear": "清除",
"select_all": "全选",
"tier": "{tier} 阶"
"reveal": "Reveal"
}
},
"tankopedia": {
"search": "搜索属性……",
@@ -1150,11 +1142,11 @@
},
"provisions": {
"clear": "清除",
"clear_all": "清除所有坦克"
"clear_all": "Clear all tanks"
},
"consumables": {
"clear": "清除",
"clear_all": "清除所有坦克"
"clear_all": "Clear all tanks"
}
}
},
+138 -134
View File
@@ -1557,85 +1557,87 @@ export async function definitions() {
const includeRaw = consumable.vehicleFilter?.include.vehicle;
const excludeRaw = consumable.vehicleFilter?.exclude?.vehicle;
if (includeRaw) {
entry.include = [];
if (!entry.game_mode_exclusive) {
if (includeRaw) {
entry.include = [];
if ("minLevel" in includeRaw) {
entry.include.push({
filter_type: {
$case: "tiers",
value: {
min: includeRaw.minLevel,
max: includeRaw.maxLevel,
if ("minLevel" in includeRaw) {
entry.include.push({
filter_type: {
$case: "tiers",
value: {
min: includeRaw.minLevel,
max: includeRaw.maxLevel,
},
},
},
});
} else if ("name" in includeRaw) {
entry.include.push({
filter_type: {
$case: "ids",
value: {
ids: includeRaw.name.split(/ +/).map((key) => {
return tankStringIdMap[key];
}),
});
} else if ("name" in includeRaw) {
entry.include.push({
filter_type: {
$case: "ids",
value: {
ids: includeRaw.name.split(/ +/).map((key) => {
return tankStringIdMap[key];
}),
},
},
},
});
} else throw new SyntaxError("Unhandled include type");
});
} else throw new SyntaxError("Unhandled include type");
if (consumable.vehicleFilter?.include.nations) {
entry.include.push({
filter_type: {
$case: "nations",
value: {
nations: consumable.vehicleFilter.include.nations.split(" "),
if (consumable.vehicleFilter?.include.nations) {
entry.include.push({
filter_type: {
$case: "nations",
value: {
nations: consumable.vehicleFilter.include.nations.split(" "),
},
},
},
});
});
}
}
}
if (excludeRaw) {
entry.exclude = [];
if (excludeRaw) {
entry.exclude = [];
if ("name" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "ids",
value: {
ids: excludeRaw.name.split(/ +/).map((key) => {
return tankStringIdMap[key];
}),
if ("name" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "ids",
value: {
ids: excludeRaw.name.split(/ +/).map((key) => {
return tankStringIdMap[key];
}),
},
},
},
});
} else if ("extendedTags" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "categories",
value: {
categories: excludeRaw.extendedTags
.split(" ")
.map(
(item) =>
blitzTankFilterDefinitionCategoryToBlitzkit[
item as BlitzTankFilterDefinitionCategory
]
),
});
} else if ("extendedTags" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "categories",
value: {
categories: excludeRaw.extendedTags
.split(" ")
.map(
(item) =>
blitzTankFilterDefinitionCategoryToBlitzkit[
item as BlitzTankFilterDefinitionCategory
]
),
},
},
},
});
} else throw new SyntaxError("Unhandled exclude type");
});
} else throw new SyntaxError("Unhandled exclude type");
if (consumable.vehicleFilter?.exclude?.nations) {
entry.exclude!.push({
filter_type: {
$case: "nations",
value: {
nations: consumable.vehicleFilter.exclude.nations.split(" "),
if (consumable.vehicleFilter?.exclude?.nations) {
entry.exclude!.push({
filter_type: {
$case: "nations",
value: {
nations: consumable.vehicleFilter.exclude.nations.split(" "),
},
},
},
});
});
}
}
}
});
@@ -1655,85 +1657,87 @@ export async function definitions() {
const includeRaw = provision.vehicleFilter?.include.vehicle;
const excludeRaw = provision.vehicleFilter?.exclude?.vehicle;
if (includeRaw) {
entry.include = [];
if (!entry.game_mode_exclusive) {
if (includeRaw) {
entry.include = [];
if ("minLevel" in includeRaw) {
entry.include.push({
filter_type: {
$case: "tiers",
value: {
min: includeRaw.minLevel,
max: includeRaw.maxLevel,
if ("minLevel" in includeRaw) {
entry.include.push({
filter_type: {
$case: "tiers",
value: {
min: includeRaw.minLevel,
max: includeRaw.maxLevel,
},
},
},
});
} else if ("name" in includeRaw) {
entry.include.push({
filter_type: {
$case: "ids",
value: {
ids: includeRaw.name.split(/ +/).map((key) => {
return tankStringIdMap[key];
}),
});
} else if ("name" in includeRaw) {
entry.include.push({
filter_type: {
$case: "ids",
value: {
ids: includeRaw.name.split(/ +/).map((key) => {
return tankStringIdMap[key];
}),
},
},
},
});
} else throw new SyntaxError("Unhandled include type");
});
} else throw new SyntaxError("Unhandled include type");
if (provision.vehicleFilter?.include.nations) {
entry.include.push({
filter_type: {
$case: "nations",
value: {
nations: provision.vehicleFilter.include.nations.split(" "),
if (provision.vehicleFilter?.include.nations) {
entry.include.push({
filter_type: {
$case: "nations",
value: {
nations: provision.vehicleFilter.include.nations.split(" "),
},
},
},
});
});
}
}
}
if (excludeRaw) {
entry.exclude = [];
if (excludeRaw) {
entry.exclude = [];
if ("name" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "ids",
value: {
ids: excludeRaw.name
.split(/ +/)
.map((key) => tankStringIdMap[key]),
if ("name" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "ids",
value: {
ids: excludeRaw.name
.split(/ +/)
.map((key) => tankStringIdMap[key]),
},
},
},
});
} else if ("extendedTags" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "categories",
value: {
categories: excludeRaw.extendedTags
.split(" ")
.map(
(item) =>
blitzTankFilterDefinitionCategoryToBlitzkit[
item as BlitzTankFilterDefinitionCategory
]
),
});
} else if ("extendedTags" in excludeRaw) {
entry.exclude!.push({
filter_type: {
$case: "categories",
value: {
categories: excludeRaw.extendedTags
.split(" ")
.map(
(item) =>
blitzTankFilterDefinitionCategoryToBlitzkit[
item as BlitzTankFilterDefinitionCategory
]
),
},
},
},
});
} else throw new SyntaxError("Unhandled exclude type");
});
} else throw new SyntaxError("Unhandled exclude type");
if (provision.vehicleFilter?.exclude?.nations) {
entry.exclude!.push({
filter_type: {
$case: "nations",
value: {
nations: provision.vehicleFilter.exclude.nations.split(" "),
if (provision.vehicleFilter?.exclude?.nations) {
entry.exclude!.push({
filter_type: {
$case: "nations",
value: {
nations: provision.vehicleFilter.exclude.nations.split(" "),
},
},
},
});
});
}
}
}
+4 -4
View File
@@ -44,9 +44,9 @@ for (const module of await readdir(`${modulesRoot}/@resvg`)) {
.map((file) =>
cp(
`${modulesRoot}/@resvg/${module}/${file}`,
`${distRoot}/workers/${file}`,
),
),
`${distRoot}/workers/${file}`
)
)
);
}
@@ -54,7 +54,7 @@ if (!dev) {
await cp(`${projectDir}/prisma`, `${distRoot}/prisma`, { recursive: true });
for (const file of await readdir(`${modulesRoot}/prisma`).then((files) =>
files.filter((file) => file.endsWith(".node")),
files.filter((file) => file.endsWith(".node"))
)) {
cp(`${modulesRoot}/prisma/${file}`, `${distRoot}/prisma/${file}`);
}
+11 -11
View File
@@ -6,7 +6,7 @@ import {
} from "@blitzkit/core";
import { Accessor, Document, Node, Scene } from "@gltf-transform/core";
import { times } from "lodash-es";
import { SteamVFS } from "../vfs/steam";
import { SteamVFS } from "../vfs/vfs";
import {
vertexAttributeGLTFName,
vertexAttributeGltfVectorSizes,
@@ -16,10 +16,10 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
const sc2Path = `Data/3d/Tanks/CollisionMeshes/${fileName}.sc2`;
const scgPath = `Data/3d/Tanks/CollisionMeshes/${fileName}.scg`;
const sc2 = new Sc2ReadStream(
(await vfs.file(sc2Path)).buffer as ArrayBuffer,
(await vfs.file(sc2Path)).buffer as ArrayBuffer
).sc2();
const scg = new ScgReadStream(
(await vfs.file(scgPath)).buffer as ArrayBuffer,
(await vfs.file(scgPath)).buffer as ArrayBuffer
).scg();
const document = new Document();
const scene = document.createScene();
@@ -29,7 +29,7 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
hierarchies.forEach((hierarchy) => {
const components = times(
hierarchy.components.count,
(index) => hierarchy.components[index.toString().padStart(4, "0")],
(index) => hierarchy.components[index.toString().padStart(4, "0")]
);
components.forEach((component) => {
@@ -43,7 +43,7 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
if (!polygonGroup) {
console.warn(
`Missing polygon group ${batch["rb.datasource"]} (${hierarchy.name}); skipping...`,
`Missing polygon group ${batch["rb.datasource"]} (${hierarchy.name}); skipping...`
);
break;
@@ -73,7 +73,7 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
VertexAttribute.TANGENT,
attributes
.get(VertexAttribute.TANGENT)!
.map((tangent) => [...tangent, 1]),
.map((tangent) => [...tangent, 1])
);
}
@@ -94,7 +94,7 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
hardJointIndices.forEach((hardJointIndex) => {
const node = document.createNode(
`${hierarchy.name}_armor_${hardJointIndex}`,
`${hierarchy.name}_armor_${hardJointIndex}`
);
const mesh = document.createMesh(batch["##name"]);
const indicesAccessor = document
@@ -104,9 +104,9 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
new Uint16Array(
polygonGroup.indices.filter(
(index) =>
vertexHardJointIndices.get(index) === hardJointIndex,
),
),
vertexHardJointIndices.get(index) === hardJointIndex
)
)
)
.setBuffer(buffer);
const primitive = document
@@ -127,7 +127,7 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
default:
throw new TypeError(
`Unhandled component type: ${component["comp.typename"]}`,
`Unhandled component type: ${component["comp.typename"]}`
);
}
});
@@ -9,7 +9,7 @@ import { Document, Material, Node, Scene } from "@gltf-transform/core";
import { dedup, prune } from "@gltf-transform/functions";
import { times } from "lodash-es";
import { dirname } from "path";
import { SteamVFS } from "../../vfs/steam";
import { SteamVFS } from "../../vfs/vfs";
import { readBaseColor } from "../readBaseColor";
import { readNormal } from "../readNormal";
import { readRoughnessMetallic } from "../readRoughnessMetallic";
@@ -29,10 +29,10 @@ export async function extractModel(vfs: SteamVFS, path: string) {
const sc2Path = `Data/3d/${path}.sc2`;
const scgPath = `Data/3d/${path}.scg`;
const sc2 = new Sc2ReadStream(
(await vfs.file(sc2Path)).buffer as ArrayBuffer,
(await vfs.file(sc2Path)).buffer as ArrayBuffer
).sc2();
const scg = new ScgReadStream(
(await vfs.file(scgPath)).buffer as ArrayBuffer,
(await vfs.file(scgPath)).buffer as ArrayBuffer
).scg();
const document = new Document();
const scene = document.createScene();
@@ -72,9 +72,9 @@ export async function extractModel(vfs: SteamVFS, path: string) {
}`,
textures.miscMap
? `Data/3d/${dirname(path)}/${textures.miscMap}`
: undefined,
),
),
: undefined
)
)
);
if (node.configCount) {
@@ -103,9 +103,9 @@ export async function extractModel(vfs: SteamVFS, path: string) {
.setMimeType("image/webp")
.setImage(
await readRoughnessMetallic(
`Data/3d/${dirname(path)}/${textures.baseRMMap}`,
),
),
`Data/3d/${dirname(path)}/${textures.baseRMMap}`
)
)
);
}
@@ -121,9 +121,9 @@ export async function extractModel(vfs: SteamVFS, path: string) {
`Data/3d/${dirname(path)}/${
textures.baseNormalMap ?? textures.normalmap
}`,
isBase,
),
),
isBase
)
)
);
}
@@ -157,7 +157,7 @@ export async function extractModel(vfs: SteamVFS, path: string) {
const node = document.createNode(hierarchy.name);
const components = times(
hierarchy.components.count,
(index) => hierarchy.components[index.toString().padStart(4, "0")],
(index) => hierarchy.components[index.toString().padStart(4, "0")]
);
components.forEach((component) => {
@@ -194,7 +194,7 @@ export async function extractModel(vfs: SteamVFS, path: string) {
}
if (polygonGroup === undefined) {
throw new Error(
`Missing polygon group ${batch["rb.datasource"]}`,
`Missing polygon group ${batch["rb.datasource"]}`
);
}
@@ -255,7 +255,7 @@ export async function extractModel(vfs: SteamVFS, path: string) {
default: {
if (ERROR_ON_UNKNOWN_COMPONENT) {
throw new Error(
`Unhandled component type: ${component["comp.typename"]}`,
`Unhandled component type: ${component["comp.typename"]}`
);
}
}
@@ -1,6 +1,6 @@
import type { Consumable } from "@blitzkit/core";
import { Flex, type FlexProps } from "@radix-ui/themes";
import { ConsumableButton } from "./ModuleButtons/ConsumableButton";
import type { Consumable } from '@blitzkit/core';
import { Flex, type FlexProps } from '@radix-ui/themes';
import { ConsumableButton } from './ModuleButtons/ConsumableButton';
type ConsumablesManagerProps = FlexProps & {
consumables: Consumable[];
@@ -27,8 +27,6 @@ export function ConsumablesManager({
return (
<Flex wrap="wrap" gap="2" {...props}>
{consumables.map((consumable) => {
if (consumable.game_mode_exclusive) return null;
const isSelected = selected.some((id) => id === consumable.id);
return (
+55 -139
View File
@@ -9,17 +9,13 @@ import {
ArrowRightIcon,
EyeOpenIcon,
MagnifyingGlassIcon,
MixerVerticalIcon,
PaperPlaneIcon,
StarFilledIcon,
TrashIcon,
} from "@radix-ui/react-icons";
import {
AlertDialog,
Box,
Button,
Card,
DropdownMenu,
Flex,
IconButton,
Spinner,
@@ -27,7 +23,7 @@ import {
TextField,
} from "@radix-ui/themes";
import fuzzysort from "fuzzysort";
import { debounce, times } from "lodash-es";
import { debounce } from "lodash-es";
import { useCallback, useEffect, useRef, useState } from "react";
import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions";
import { awaitableTankNames } from "../core/awaitables/tankNames";
@@ -57,7 +53,6 @@ export function Guesser() {
const [searching, setSearching] = useState(false);
const [results, setResults] = useState<TankDefinition[] | null>(null);
const [selected, setSelected] = useState<TankDefinition | null>(null);
const tiers = Guess.use((state) => state.tiers);
const requestSearch = useCallback(() => {
setSelected(null);
@@ -102,60 +97,58 @@ export function Guesser() {
width="100%"
p="4"
maxWidth="25rem"
gap="3"
gap="4"
>
<Box position="relative">
{results !== null && (
<Card variant="classic">
<Box py="2" px="3">
{results.length === 0 && (
<Flex justify="center">
<Text color="gray">
{strings.website.tools.guess.search.no_results}
</Text>
</Flex>
)}
{results !== null && (
<Card variant="classic">
<Box py="2" px="3">
{results.length === 0 && (
<Flex justify="center">
<Text color="gray">
{strings.website.tools.guess.search.no_results}
</Text>
</Flex>
)}
<SearchResults.Root>
{results.map((result) => {
const Icon = classIcons[result.class];
<SearchResults.Root>
{results.map((result) => {
const Icon = classIcons[result.class];
return (
<SearchResults.Item
key={result.id}
onClick={() => {
if (!input.current) return;
return (
<SearchResults.Item
key={result.id}
onClick={() => {
if (!input.current) return;
input.current.value = unwrap(result.name);
setSelected(result);
setResults(null);
}}
text={unwrap(result.name)}
prefix={
<img
style={{ width: "1em", height: "1em" }}
src={asset(`flags/circle/${result.nation}.webp`)}
/>
}
discriminator={
<Flex
align="center"
gap="1"
width="38px"
justify="center"
>
<Icon width="1em" height="1em" />
{TIER_ROMAN_NUMERALS[result.tier]}
</Flex>
}
/>
);
})}
</SearchResults.Root>
</Box>
</Card>
)}
</Box>
input.current.value = unwrap(result.name);
setSelected(result);
setResults(null);
}}
text={unwrap(result.name)}
prefix={
<img
style={{ width: "1em", height: "1em" }}
src={asset(`flags/circle/${result.nation}.webp`)}
/>
}
discriminator={
<Flex
align="center"
gap="1"
width="38px"
justify="center"
>
<Icon width="1em" height="1em" />
{TIER_ROMAN_NUMERALS[result.tier]}
</Flex>
}
/>
);
})}
</SearchResults.Root>
</Box>
</Card>
)}
<Flex justify="center" style={{ userSelect: "none" }}>
<Text>
@@ -167,85 +160,14 @@ export function Guesser() {
</Text>
</Flex>
<Flex gap={{ initial: "2", sm: "3" }}>
<DropdownMenu.Root modal={false}>
<DropdownMenu.Trigger>
<IconButton
size={{ initial: "2", sm: "3" }}
variant="surface"
color="gray"
>
<MixerVerticalIcon />
</IconButton>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{times(10, (index) => {
const tier = 10 - index;
const isSelected = tiers.includes(tier);
return (
<DropdownMenu.CheckboxItem
checked={isSelected}
key={tier}
onClick={(event) => {
event.preventDefault();
Guess.mutate((draft) => {
if (isSelected) {
if (draft.tiers.length === 1) return;
draft.tiers = draft.tiers.filter((t) => t !== tier);
} else {
draft.tiers = [...draft.tiers, tier];
}
draft.streak = 0;
});
}}
>
{literals(strings.website.tools.guess.tier, {
tier: TIER_ROMAN_NUMERALS[tier],
})}
</DropdownMenu.CheckboxItem>
);
})}
<DropdownMenu.Separator />
<DropdownMenu.Item
onClick={(event) => {
event.preventDefault();
Guess.mutate((draft) => {
draft.tiers = Guess.initial.tiers;
});
}}
>
<StarFilledIcon /> {strings.website.tools.guess.select_all}
</DropdownMenu.Item>
<DropdownMenu.Item
color="red"
onClick={(event) => {
event.preventDefault();
Guess.mutate((draft) => {
draft.tiers = [];
});
}}
>
<TrashIcon /> {strings.website.tools.guess.clear}
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<Flex gap="3">
<TextField.Root
disabled={guessState !== GuessState.NotGuessed}
ref={input}
onChange={requestSearch}
style={{ flex: 1, maxWidth: "14rem" }}
style={{ flex: 1 }}
placeholder={strings.website.tools.guess.search.placeholder}
size={{ initial: "2", sm: "3" }}
size="3"
variant="classic"
>
<TextField.Slot>
@@ -256,7 +178,7 @@ export function Guesser() {
<AlertDialog.Root>
<AlertDialog.Trigger>
<IconButton
size={{ initial: "2", sm: "3" }}
size="3"
disabled={guessState !== GuessState.NotGuessed}
>
<EyeOpenIcon />
@@ -296,7 +218,7 @@ export function Guesser() {
</AlertDialog.Root>
<Button
size={{ initial: "2", sm: "3" }}
size="3"
color={
guessState === GuessState.NotGuessed && selected === null
? "red"
@@ -316,13 +238,7 @@ export function Guesser() {
draft.streak = correct ? draft.streak + 1 : 0;
});
} else {
const filteredIds = ids.filter((id) => {
const tank = tankDefinitions.tanks[Number(id)];
return tiers.includes(tank.tier);
});
const id = Number(
filteredIds[Math.floor(Math.random() * filteredIds.length)]
);
const id = Number(ids[Math.floor(Math.random() * ids.length)]);
const tank = tankDefinitions.tanks[id];
Guess.mutate((draft) => {
@@ -1,3 +1,26 @@
<script>
import locales from "@blitzkit/i18n/locales.json";
import { getBestLocale } from "../core/i18n/getBestLocale";
const isAPI = window.location.pathname.startsWith("/api");
const isI18nPath = locales.supported.some(({ locale }) =>
window.location.pathname.startsWith(`/${locale}/`)
);
const bestLocale = getBestLocale();
const currentPath = locales.supported.find(({ locale }) =>
window.location.pathname.startsWith(`/${locale}/`)
);
if (
!isAPI && // not an api, duh!
localStorage.getItem("preferred-locale") !== null && // no preference, so probably google's crawler
bestLocale !== currentPath?.locale // we need to redirect
) {
window.location.pathname = isI18nPath
? window.location.pathname.replace(
`/${currentPath?.locale}`,
bestLocale === undefined ? "" : `/${bestLocale}`
)
: `/${bestLocale}${window.location.pathname}`;
}
</script>
@@ -1,6 +1,5 @@
import { Flex } from "@radix-ui/themes";
import { awaitableProvisionDefinitions } from "../core/awaitables/provisionDefinitions";
import { ProvisionButton } from "./ModuleButtons/ProvisionButton";
import { Flex } from '@radix-ui/themes';
import { ProvisionButton } from './ModuleButtons/ProvisionButton';
interface ProvisionsManagerProps {
provisions: number[];
@@ -9,8 +8,6 @@ interface ProvisionsManagerProps {
onChange?: (provisions: number[]) => void;
}
const provisionDefinitions = await awaitableProvisionDefinitions;
export function ProvisionsManager({
provisions,
selected,
@@ -18,18 +15,15 @@ export function ProvisionsManager({
disabled,
}: ProvisionsManagerProps) {
return (
<Flex wrap="wrap" gap="2" justify={{ initial: "center", sm: "start" }}>
{provisions.map((id) => {
const isSelected = selected.includes(id);
const provision = provisionDefinitions.provisions[id];
if (provision.game_mode_exclusive) return null;
<Flex wrap="wrap" gap="2" justify={{ initial: 'center', sm: 'start' }}>
{provisions.map((provision) => {
const isSelected = selected.includes(provision);
return (
<ProvisionButton
key={id}
key={provision}
disabled={disabled && !isSelected}
provision={id}
provision={provision}
selected={isSelected}
onClick={() => {
if (!onChange) return;
@@ -37,9 +31,9 @@ export function ProvisionsManager({
const draft = [...selected];
if (isSelected) {
draft.splice(draft.indexOf(id), 1);
draft.splice(draft.indexOf(provision), 1);
} else {
draft.push(id);
draft.push(provision);
}
onChange(draft);
@@ -41,7 +41,7 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
case 'fire.damage':
return shell0.armor_damage.toFixed(0);
case 'fire.moduleDamage':
return shell0.module_damage.toFixed(0);
return shell0.armor_damage.toFixed(0);
case 'fire.dispersionMoving':
return tracks.dispersion_move.toFixed(3);
case 'fire.dispersionStill':
File diff suppressed because it is too large. Load diff
@@ -429,7 +429,7 @@ export const TankSearch = memo<TankSearchProps>(
}, [tankFilters, tankopediaSort]);
return (
<Flex direction="column" gap="2" flexGrow="1" {...props}>
<Flex direction="column" gap="4" flexGrow="1" {...props}>
<SearchBar
skeleton={skeleton}
topResult={tanks?.[0]}
@@ -1,10 +1,10 @@
import { asset } from "@blitzkit/core";
import { Box, Flex, Heading, Text } from "@radix-ui/themes";
import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions";
import { awaitableGameDefinitions } from "../../core/awaitables/gameDefinitions";
import { awaitableProvisionDefinitions } from "../../core/awaitables/provisionDefinitions";
import { useLocale } from "../../hooks/useLocale";
import { Duel } from "../../stores/duel";
import { asset } from '@blitzkit/core';
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions';
import { awaitableGameDefinitions } from '../../core/awaitables/gameDefinitions';
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
import { useLocale } from '../../hooks/useLocale';
import { Duel } from '../../stores/duel';
const [gameDefinitions, consumableDefinitions, provisionDefinitions] =
await Promise.all([
@@ -39,22 +39,22 @@ export function GameModeSection() {
overflow="hidden"
direction="column"
style={{
borderRadius: "var(--radius-3)",
borderRadius: 'var(--radius-3)',
background: `url(${asset(
`icons/game_mode_banners/${id}.webp`
`icons/game_mode_banners/${id}.webp`,
)})`,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
>
<Box
flexGrow="1"
style={{
background: `url(${asset(
`icons/game_mode_banners/${id}.webp`
`icons/game_mode_banners/${id}.webp`,
)})`,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
/>
@@ -64,8 +64,8 @@ export function GameModeSection() {
height="98px"
flexShrink="0"
style={{
backdropFilter: "blur(4rem)",
WebkitBackdropFilter: "blur(4rem)",
backdropFilter: 'blur(4rem)',
WebkitBackdropFilter: 'blur(4rem)',
}}
align="center"
justify="center"
@@ -83,9 +83,9 @@ export function GameModeSection() {
<Flex align="center" gap="1">
<img
style={{
width: "1.5em",
height: "1.5em",
objectFit: "contain",
width: '1.5em',
height: '1.5em',
objectFit: 'contain',
}}
src={asset(`icons/provisions/${id}.webp`)}
alt={unwrap(provisions.name)}
@@ -95,7 +95,6 @@ export function GameModeSection() {
</Text>
);
})}
{gameDefinitions.roles[value].consumables.map((id) => {
const consumable = consumableDefinitions.consumables[id];
@@ -104,9 +103,9 @@ export function GameModeSection() {
<Flex align="center" gap="1">
<img
style={{
width: "1.5em",
height: "1.5em",
objectFit: "contain",
width: '1.5em',
height: '1.5em',
objectFit: 'contain',
}}
src={asset(`icons/consumables/${id}.webp`)}
alt={unwrap(consumable.name)}
@@ -108,10 +108,10 @@ export function CustomShellButton() {
{strings.common.shells.ap}
</Select.Item>
<Select.Item value={`${ShellType.APCR}`}>
{strings.common.shells.ap_cr}
{strings.common.shells.apcr}
</Select.Item>
<Select.Item value={`${ShellType.HEAT}`}>
{strings.common.shells.hc}
{strings.common.shells.heat}
</Select.Item>
<Select.Item value={`${ShellType.HE}`}>
{strings.common.shells.he}
@@ -1,17 +1,9 @@
import type { TankDefinition } from "@blitzkit/core";
import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
import { times } from "lodash-es";
import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions";
import type { TankFilters } from "../../stores/tankFilters";
import { awaitableGameDefinitions } from "../awaitables/gameDefinitions";
import { awaitableProvisionDefinitions } from "../awaitables/provisionDefinitions";
const SHELLS = times(3, (index) => index);
const consumableDefinitions = await awaitableConsumableDefinitions;
const provisionDefinitions = await awaitableProvisionDefinitions;
const gameDefinitions = await awaitableGameDefinitions;
export async function filterTank(
filters: TankFilters,
tank: TankDefinition,
@@ -48,42 +40,6 @@ export async function filterTank(
gun.shells[index].type === filters.shells[index]
)
)
) &&
(filters.consumables.length === 0 ||
filters.consumables.every((consumable) =>
tank.turrets.some((turret) =>
turret.guns.some((gun) =>
checkConsumableProvisionInclusivity(
consumableDefinitions.consumables[consumable],
tank,
gun
)
)
)
)) &&
(filters.provisions.length === 0 ||
filters.provisions.every((provision) =>
tank.turrets.some((turret) =>
turret.guns.some((gun) =>
checkConsumableProvisionInclusivity(
provisionDefinitions.provisions[provision],
tank,
gun
)
)
)
)) &&
(filters.abilities.length === 0 ||
Object.values(tank.roles).some((id) =>
gameDefinitions.roles[id as unknown as number].consumables.some(
(consumable) => filters.abilities.includes(consumable)
)
)) &&
(filters.powers.length === 0 ||
Object.values(tank.roles).some((id) =>
gameDefinitions.roles[id as unknown as number].provisions.some(
(consumable) => filters.powers.includes(consumable)
)
))
)
);
}
@@ -19,9 +19,9 @@ import {
type TankDefinition,
type TrackDefinition,
type TurretDefinition,
} from "@blitzkit/core";
import { coefficient } from "@blitzkit/core/src/blitzkit/coefficient";
import type { EquipmentMatrix } from "../../stores/duel";
} from '@blitzkit/core';
import { coefficient } from '@blitzkit/core/src/blitzkit/coefficient';
import type { EquipmentMatrix } from '../../stores/duel';
export type TankCharacteristics = ReturnType<typeof tankCharacteristics>;
export type TankCharacteristicsKey = keyof TankCharacteristics;
@@ -76,7 +76,7 @@ export function tankCharacteristics(
tankModelDefinition: ModelDefinition;
equipmentDefinitions: EquipmentDefinitions;
provisionDefinitions: ProvisionDefinitions;
}
},
) {
const preset = equipmentDefinitions.presets[tank.equipment_preset];
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
@@ -86,7 +86,7 @@ export function tankCharacteristics(
return preset.slots.some((slot, index) => {
const choice = equipmentMatrix[Math.floor(index / 3)][index % 3];
if (choice === 0) return false;
const equipped = slot[choice === -1 ? "left" : "right"];
const equipped = slot[choice === -1 ? 'left' : 'right'];
return equipped === id;
});
}
@@ -138,7 +138,7 @@ export function tankCharacteristics(
provisionDefinitions.provisions[provision].crew
? total + provisionDefinitions.provisions[provision].crew! / 100
: total,
0
0,
) + (hasImprovedVentilation ? 0.08 : 0);
const commanderMastery = 1 + provisionCrewBonus;
const loaderMastery =
@@ -158,19 +158,19 @@ export function tankCharacteristics(
coefficient(
[applyReactiveArmor && shell.type !== ShellType.HE, -0.27],
[applyDynamicArmor, -0.1],
[applySpallLiner && shell.type === ShellType.HE, -0.2]
[applySpallLiner && shell.type === ShellType.HE, -0.2],
);
const assaultDamageCoefficient =
gun.assault_ranges && gun.assault_ranges.types.includes(shell.type)
? gun.assault_ranges.ranges.find(
({ distance }) => distance >= assaultDistance
)?.factor ?? 0
? (gun.assault_ranges.ranges.find(
({ distance }) => distance >= assaultDistance,
)?.factor ?? 0)
: 1;
const moduleDamageCoefficient = coefficient([hasTungsten, 0.15]);
const reloadCoefficient =
(coefficient([hasGunRammer, -0.05]) *
coefficient([true, degressiveStat(loaderMastery)])) /
coefficient([hasAdrenaline && gun.gun_type!.$case === "regular", 0.17]);
coefficient([hasAdrenaline && gun.gun_type!.$case === 'regular', 0.17]);
const penetrationCoefficient = coefficient([
hasCalibratedShells,
resolvePenetrationCoefficient(true, shell.type) - 1,
@@ -178,16 +178,16 @@ export function tankCharacteristics(
const healthCoefficient = coefficient(
[hasSandbagArmor, 0.03],
[hasEnhancedSandbagArmor, 0.06],
[hasImprovedAssembly, 0.04]
[hasImprovedAssembly, 0.04],
);
const shellVelocityCoefficient = coefficient(
[hasSupercharger, 0.35],
[hasImprovedGunPowder, 0.3]
[hasImprovedGunPowder, 0.3],
);
const aimTimeCoefficient =
coefficient(
[hasEnhancedGunLayingDrive, -0.15],
[hasReticleCalibration, -0.3]
[hasReticleCalibration, -0.3],
) * coefficient([true, degressiveStat(gunnerMastery)]);
const dispersionStillCoefficient =
coefficient([hasRefinedGun, -0.15], [hasReticleCalibration, -0.3]) *
@@ -198,19 +198,19 @@ export function tankCharacteristics(
]);
const dispersionHullTraverseCoefficient = coefficient(
[hasVerticalStabilizer, -0.15],
[true, -crewSkills.smooth_turn / 100]
[true, -crewSkills.smooth_turn / 100],
);
const dispersionAfterShotCoefficient = coefficient(
[hasVerticalStabilizer, -0.15],
[true, -crewSkills.soft_recoil / 100]
[true, -crewSkills.soft_recoil / 100],
);
const dispersionMovingCoefficient = coefficient(
[hasVerticalStabilizer, -0.15],
[true, -crewSkills.smooth_driving / 100]
[true, -crewSkills.smooth_driving / 100],
);
const dispersionTurretTraverseCoefficient = coefficient(
[hasVerticalStabilizer, -0.15],
[true, -crewSkills.smooth_turret * (2 / 100)]
[true, -crewSkills.smooth_turret * (2 / 100)],
);
const enginePowerCoefficient = coefficient(
[
@@ -225,7 +225,7 @@ export function tankCharacteristics(
[hasStandardFuel, 0.03],
[hasImprovedFuel, 0.1],
[hasGearOil, 0.03],
[hasImprovedGearOil, 0.06]
[hasImprovedGearOil, 0.06],
);
const turretTraverseCoefficient =
coefficient([hasStandardFuel, 0.03], [hasImprovedFuel, 0.1]) *
@@ -234,41 +234,36 @@ export function tankCharacteristics(
coefficient(
[hasImprovedControl, 0.11],
[hasImprovedEnginePowerBoost, 0.01],
[true, crewSkills.virtuoso / 100]
[true, crewSkills.virtuoso / 100],
) * coefficient([true, progressiveStat(driverMastery)]);
const resistanceHardCoefficient =
coefficient([hasImprovedSuspension, -0.2]) *
coefficient([true, degressiveStat(driverMastery)]);
const resistanceMediumCoefficient =
coefficient([hasImprovedSuspension, -0.15]) *
coefficient([true, degressiveStat(driverMastery)]);
const resistanceSoftCoefficient =
coefficient([hasImprovedSuspension, -0.3]) *
coefficient([true, degressiveStat(driverMastery)]);
const resistanceHardCoefficient = coefficient([hasImprovedSuspension, -0.2]);
const resistanceMediumCoefficient = coefficient([
hasImprovedSuspension,
-0.15,
]);
const resistanceSoftCoefficient = coefficient([hasImprovedSuspension, -0.3]);
const viewRangeCoefficient =
coefficient([
hasImprovedOptics,
tank.class === TankClass.HEAVY
? 0.06
: tank.class === TankClass.MEDIUM
? 0.08
: tank.class === TankClass.LIGHT
? 0.11
: 0.04,
? 0.08
: tank.class === TankClass.LIGHT
? 0.11
: 0.04,
]) * coefficient([true, progressiveStat(commanderMastery)]);
const fireChanceCoefficient = coefficient([hasProtectiveKit, -0.2]);
const speedForwardsSum = sum(
[hasImprovedEnginePowerBoost, 4],
[hasGearOil, 2],
[hasImprovedGearOil, 4]
[hasImprovedGearOil, 4],
);
const speedBackwardsSum = sum(
[hasImprovedEnginePowerBoost, 8],
[hasGearOil, 2],
[hasImprovedGearOil, 4]
[hasImprovedGearOil, 4],
);
const camouflageCoefficientMoving = sum(
[
@@ -278,8 +273,8 @@ export function tankCharacteristics(
tank.class === TankClass.TANK_DESTROYER
? 0.04
: tank.class === TankClass.HEAVY
? 0.03
: 0.02,
? 0.03
: 0.02,
]),
],
[
@@ -287,11 +282,11 @@ export function tankCharacteristics(
tank.class === TankClass.HEAVY
? 0.02
: tank.class === TankClass.MEDIUM
? 0.04
: tank.class === TankClass.LIGHT
? 0.07
: 0.1,
]
? 0.04
: tank.class === TankClass.LIGHT
? 0.07
: 0.1,
],
);
const camouflageCoefficientStill = sum(
[
@@ -302,10 +297,10 @@ export function tankCharacteristics(
tank.class === TankClass.TANK_DESTROYER
? 0.04
: tank.class === TankClass.HEAVY
? 0.03
: 0.02,
? 0.03
: 0.02,
],
[true, crewSkills.camouflage * (3 / 100)]
[true, crewSkills.camouflage * (3 / 100)],
),
],
[
@@ -313,18 +308,18 @@ export function tankCharacteristics(
tank.class === TankClass.HEAVY
? 0.04
: tank.class === TankClass.MEDIUM
? 0.08
: tank.class === TankClass.LIGHT
? 0.14
: 0.2,
]
? 0.08
: tank.class === TankClass.LIGHT
? 0.14
: 0.2,
],
);
const size = normalizeBoundingBox(
unionBoundingBox(
tankModelDefinition.bounding_box,
turretModelDefinition.bounding_box
)
turretModelDefinition.bounding_box,
),
);
const weightKg =
tank.weight + engine.weight + track.weight + turret.weight + gun.weight;
@@ -344,14 +339,14 @@ export function tankCharacteristics(
shell,
damageCoefficient,
reloadCoefficient,
intraClipCoefficient
intraClipCoefficient,
);
const intraClip =
gun.gun_type?.$case === "regular"
gun.gun_type?.$case === 'regular'
? undefined
: gun.gun_type!.value.intra_clip * intraClipCoefficient;
const mostOptimalShellIndex =
gun.gun_type!.$case === "auto_reloader"
gun.gun_type!.$case === 'auto_reloader'
? gun.gun_type!.value.shell_reloads.reduce<null | {
index: number;
reload: number;
@@ -369,7 +364,7 @@ export function tankCharacteristics(
shell.armor_damage * damageCoefficientWithoutAssault;
const damage = shell.armor_damage * damageCoefficient;
const dpmEffective =
gun.gun_type!.$case === "auto_reloader"
gun.gun_type!.$case === 'auto_reloader'
? gun.gun_type!.value.shell_reloads[0] >
gun.gun_type!.value.shell_reloads[1] + gun.gun_type!.value.intra_clip
? ((armorDamageCoefficient * shell.armor_damage) /
@@ -394,23 +389,23 @@ export function tankCharacteristics(
gun.gun_type!.value.shell_reloads.slice(1).length
: undefined;
const shells =
gun.gun_type!.$case === "regular" ? 1 : gun.gun_type!.value.shell_count;
gun.gun_type!.$case === 'regular' ? 1 : gun.gun_type!.value.shell_count;
const shellReloads =
gun.gun_type!.$case === "auto_reloader"
gun.gun_type!.$case === 'auto_reloader'
? gun.gun_type!.value.shell_reloads.map(
(reload) => reload * reloadCoefficient
(reload) => reload * reloadCoefficient,
)
: undefined;
const shellReload =
gun.gun_type!.$case === "auto_reloader"
gun.gun_type!.$case === 'auto_reloader'
? undefined
: (gun.gun_type!.$case === "regular"
: (gun.gun_type!.$case === 'regular'
? gun.gun_type!.value.reload
: gun.gun_type!.value.clip_reload) * reloadCoefficient;
const caliber = shell.caliber;
const penetration = shell.penetration.near * penetrationCoefficient;
const clipDamage =
gun.gun_type!.$case === "regular"
gun.gun_type!.$case === 'regular'
? undefined
: gun.gun_type!.value.shell_count * damage;
const moduleDamage = shell.module_damage * moduleDamageCoefficient;
@@ -604,45 +599,45 @@ export const characteristicsOrder: {
items: TankCharacteristicsKey[];
}[] = [
{
name: "Firepower",
name: 'Firepower',
items: [
"gunType",
"dpm",
"shells",
"shellReload",
"shellReloads",
"intraClip",
"penetration",
"penetrationAt250m",
"damage",
"clipDamage",
"moduleDamage",
"caliber",
"shellNormalization",
"shellRicochet",
"shellVelocity",
"shellRange",
"shellCapacity",
"aimTime",
"dispersion",
"dispersionMoving",
"dispersionHullTraversing",
"dispersionTurretTraversing",
"dispersionShooting",
"dispersionGunDamaged",
"gunDepression",
"gunElevation",
"gunFrontalDepression",
"gunFrontalElevation",
"gunRearDepression",
"gunRearElevation",
"azimuthLeft",
"azimuthRight",
'gunType',
'dpm',
'shells',
'shellReload',
'shellReloads',
'intraClip',
'penetration',
'penetrationAt250m',
'damage',
'clipDamage',
'moduleDamage',
'caliber',
'shellNormalization',
'shellRicochet',
'shellVelocity',
'shellRange',
'shellCapacity',
'aimTime',
'dispersion',
'dispersionMoving',
'dispersionHullTraversing',
'dispersionTurretTraversing',
'dispersionShooting',
'dispersionGunDamaged',
'gunDepression',
'gunElevation',
'gunFrontalDepression',
'gunFrontalElevation',
'gunRearDepression',
'gunRearElevation',
'azimuthLeft',
'azimuthRight',
],
},
{
name: "Crew",
items: ["crewCount"],
name: 'Crew',
items: ['crewCount'],
},
];
@@ -5,11 +5,11 @@ export function getBestLocale() {
localStorage.getItem("preferred-locale") ??
navigator.language.split("-")[0];
const isSupported = locales.supported.some(
({ locale }) => locale === desiredLocale,
({ locale }) => locale === desiredLocale
);
const isDefault = desiredLocale === locales.default;
if (!isSupported || isDefault) return locales.default;
if (!isSupported || isDefault) return undefined;
return desiredLocale;
}
@@ -1,5 +1,5 @@
import { Box, Flex, Heading } from "@radix-ui/themes";
import { Suspense, useEffect } from "react";
import { Suspense } from "react";
import { GuessBackground } from "../../../components/GuessBackground";
import { Guesser } from "../../../components/Guesser";
import { GuessRenderer } from "../../../components/GuessRenderer";
@@ -62,31 +62,6 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) {
const name = unwrap(tank.name);
const fontSize = `min(48vh, ${55 / name.length}vw)`;
const transitionDuration = isRevealed ? "2s" : undefined;
const tiers = Guess.use((state) => state.tiers);
useEffect(() => {
const isTankAlreadyCompliant = tiers.includes(tank.tier);
if (isTankAlreadyCompliant) return;
const filteredIds = ids.filter((id) => {
const tank = tankDefinitions.tanks[Number(id)];
return tiers.includes(tank.tier);
});
if (filteredIds.length === 0) return;
const id = Number(
filteredIds[Math.floor(Math.random() * filteredIds.length)]
);
const newTank = tankDefinitions.tanks[id];
Guess.mutate((draft) => {
draft.tank = newTank;
draft.guessState = GuessState.NotGuessed;
draft.helpingReveal = false;
});
}, [tiers]);
return (
<Flex flexGrow="1" position="relative" overflow="hidden">
@@ -12,8 +12,7 @@ export const getStaticPaths = (async () => {
.map((tank) =>
parentPaths.map((parent) => ({
params: { ...parent.params, id: tank.id },
props: parent.props,
})),
}))
)
.flat();
}) satisfies GetStaticPaths;
-3
View File
@@ -1,5 +1,4 @@
import type { TankDefinition } from "@blitzkit/core";
import { times } from "lodash-es";
import { Varuna } from "varuna";
export enum GuessState {
@@ -15,7 +14,6 @@ export interface Guess {
correctGuesses: number;
streak: number;
helpingReveal: boolean;
tiers: number[];
}
export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({
@@ -25,5 +23,4 @@ export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({
correctGuesses: 0,
streak: 0,
helpingReveal: false,
tiers: times(10, (index) => index + 1),
}));
@@ -21,10 +21,6 @@ export interface TankFilters {
searching: boolean;
gunType: CaseType<GunDefinition>[];
shells: [ShellType | null, ShellType | null, ShellType | null];
consumables: number[];
provisions: number[];
abilities: number[];
powers: number[];
showTesting: boolean;
showNonTesting: boolean;
@@ -41,10 +37,6 @@ export const TankFilters = new Varuna<TankFilters>({
searching: false,
gunType: [],
shells: [null, null, null],
consumables: [],
provisions: [],
abilities: [],
powers: [],
showTesting: true,
showNonTesting: true,