mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d6bae847b | ||
|
|
cc70b3ccc3 | ||
|
|
e3bf619808 | ||
|
|
f6cb4f2518 | ||
|
|
f396d6c4a1 | ||
|
|
768c420ae4 | ||
|
|
7fba197bef | ||
|
|
b074481e1a | ||
|
|
987e35ef8f | ||
|
|
83c77b5945 | ||
|
|
e4f0120d67 | ||
|
|
2055c00154 | ||
|
|
c814230dcb | ||
|
|
2f31aadb29 | ||
|
|
3113bf3a47 | ||
|
|
70bd54e03b | ||
|
|
8863ac5fde | ||
|
|
b62009ce2b | ||
|
|
0f3d230f42 | ||
|
|
9db6b997c3 | ||
|
|
d9fcbd028b | ||
|
|
db74058075 | ||
|
|
4180736d37 | ||
|
|
382f04229e | ||
|
|
3c9ba9ec53 | ||
|
|
4848234264 | ||
|
|
2ad0fcfdf9 | ||
|
|
8f0894364b | ||
|
|
3695b8f7f4 | ||
|
|
4f8c1d416f | ||
|
|
453286677a | ||
|
|
53a49f199a | ||
|
|
fdfde46342 | ||
|
|
1d836b2053 | ||
|
|
6d448dc5c4 | ||
|
|
75d4d60b30 | ||
|
|
e7f47fd2fd | ||
|
|
50a3d1e922 | ||
|
|
e296267ccd | ||
|
|
d9aca6a3ed | ||
|
|
d07a6e954e | ||
|
|
6bcaf685f8 | ||
|
|
066f17a2f3 | ||
|
|
374d6c22f9 | ||
|
|
55927258ca | ||
|
|
bb99220497 | ||
|
|
4f6b41e264 | ||
|
|
f9c4cee30c | ||
|
|
d865043b34 | ||
|
|
a4080f21b9 | ||
|
|
853c520d8a | ||
|
|
d78f8c6ec4 | ||
|
|
318a71bd32 | ||
|
|
2458d2c80c | ||
|
|
37a72e3bb5 | ||
|
|
bc9c9ad4e7 | ||
|
|
b38c732983 | ||
|
|
415861f6e8 | ||
|
|
3124afa253 | ||
|
|
8523b9f44f | ||
|
|
71e0404890 | ||
|
|
c01fd1f4ff | ||
|
|
905ba59f4e | ||
|
|
33eea3f261 | ||
|
|
2237d460bb | ||
|
|
12e7d664e5 | ||
|
|
9e4bdcd458 | ||
|
|
03ec44c765 | ||
|
|
d9f6acea2d | ||
|
|
5b5e204cce | ||
|
|
60b2e62713 | ||
|
|
5e7620ccf4 | ||
|
|
c3a83abe5d | ||
|
|
73cb3873d9 | ||
|
|
3b429a6c05 | ||
|
|
76fe6b5e20 | ||
|
|
5c8af447b6 | ||
|
|
5a805b1cec | ||
|
|
326ae548b6 | ||
|
|
3a2e75ab02 | ||
|
|
b4f8340f49 | ||
|
|
5e0d0a3d6d | ||
|
|
4122ff5bf1 | ||
|
|
d937f62ac1 | ||
|
|
b4f0a9f1b9 | ||
|
|
68535149e3 | ||
|
|
2419934638 | ||
|
|
c0076e9ef9 | ||
|
|
1cd7438d5d | ||
|
|
ab798b722f | ||
|
|
4e513998a9 | ||
|
|
df34226a22 | ||
|
|
2f7f2362bb | ||
|
|
66a54a9996 | ||
|
|
fdcbb8f29f | ||
|
|
5ab0a12f4c | ||
|
|
d19d4e0e26 | ||
|
|
2f9791d34c | ||
|
|
cd53136dce | ||
|
|
2eadad8612 | ||
|
|
2fe3c50bef | ||
|
|
10c7e4a19d | ||
|
|
143c84f52f | ||
|
|
0c54905384 | ||
|
|
437c5e0f57 | ||
|
|
b4e4702415 | ||
|
|
4b8a154142 | ||
|
|
31a61c8a6b | ||
|
|
631af6205e | ||
|
|
a78543c60f | ||
|
|
00783749ba | ||
|
|
9aba91a773 | ||
|
|
6667e548b2 | ||
|
|
67f9147aac | ||
|
|
20f54a39d6 | ||
|
|
26d3bdaac7 | ||
|
|
dd3c86aa5b | ||
|
|
f770ba0e5e | ||
|
|
c444210294 | ||
|
|
74ac5b5ac2 | ||
|
|
7188014fbb | ||
|
|
c039862c56 | ||
|
|
3d6d18267d | ||
|
|
dced738055 | ||
|
|
e5ddbc7297 | ||
|
|
7f9a54ef85 | ||
|
|
65c52c2e82 | ||
|
|
5a34a35638 | ||
|
|
1281503de9 | ||
|
|
7a3bdd8f35 | ||
|
|
d7e556e136 | ||
|
|
0997e2b089 | ||
|
|
33c0fbbb9b | ||
|
|
bef33659e5 | ||
|
|
ede1c90f10 | ||
|
|
84dcfe4a14 | ||
|
|
7a3541439b | ||
|
|
7ab8a01fb6 | ||
|
|
88729e0eca | ||
|
|
4218626cbf | ||
|
|
08eed5ce31 | ||
|
|
63a6e47878 | ||
|
|
984dad4111 | ||
|
|
00e89cff55 | ||
|
|
2540b71fb7 | ||
|
|
cda83639b2 | ||
|
|
e4e1903a2a | ||
|
|
a5df2528f6 | ||
|
|
74c0740941 | ||
|
|
74e85f82f1 | ||
|
|
80fca2e53a | ||
|
|
4d0ca1cd1a | ||
|
|
50aeb01e0a | ||
|
|
a1a218c325 | ||
|
|
e560754bb6 | ||
|
|
dbc23c6473 | ||
|
|
a2bd62da92 | ||
|
|
c0d16b51de | ||
|
|
d68870c254 | ||
|
|
d91b46d730 | ||
|
|
0758f0d8aa | ||
|
|
eacd63d472 | ||
|
|
39240e908d | ||
|
|
2f734c237b |
No files matched your search
@@ -1,58 +0,0 @@
|
||||
name: Build Website
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: main
|
||||
workflow_dispatch:
|
||||
|
||||
schedule:
|
||||
- cron: '0 4 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
|
||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||
ALLOW_ROBOTS: true
|
||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: actions/configure-pages@v5
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Build website
|
||||
run: bun run build:website
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: dist/website
|
||||
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -13,14 +13,22 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
|
||||
jobs:
|
||||
host:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: 1.1.33
|
||||
# with:
|
||||
# bun-version: 1.1.33
|
||||
|
||||
- name: Download release assets
|
||||
run: |
|
||||
@@ -32,12 +40,11 @@ jobs:
|
||||
mkdir -p dist/bot
|
||||
unzip bot.zip -d dist/bot
|
||||
|
||||
- name: Install sharp
|
||||
run: bun init -y && bun add sharp
|
||||
|
||||
- name: Move directories
|
||||
run: mv dist/bot/prisma .
|
||||
|
||||
- name: Run bot
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
run: |
|
||||
mv dist/bot/prisma .
|
||||
bun dist/bot/main.js
|
||||
run: bun run dist/bot/main.js
|
||||
@@ -19,8 +19,10 @@ jobs:
|
||||
- name: Generate leaderboard
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
run: bun build:ratingleaderboard --region=asia
|
||||
@@ -19,8 +19,10 @@ jobs:
|
||||
- name: Generate leaderboard
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
run: bun build:ratingleaderboard --region=eu
|
||||
@@ -19,8 +19,10 @@ jobs:
|
||||
- name: Generate leaderboard
|
||||
env:
|
||||
PUBLIC_ASSET_BRANCH: main
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
run: bun build:ratingleaderboard --region=com
|
||||
@@ -0,0 +1,91 @@
|
||||
name: Website
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
schedule:
|
||||
- cron: '0 4 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
env:
|
||||
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
|
||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||
ALLOW_ROBOTS: true
|
||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
||||
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
|
||||
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
|
||||
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: actions/configure-pages@v5
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Build website
|
||||
run: bun run build:website
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: dist/website
|
||||
|
||||
deploy-github-pages:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
|
||||
name: Deploy to GitHub Pages
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
environment:
|
||||
name: Production
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
deploy-vercel:
|
||||
name: Deploy to Vercel
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: Preview
|
||||
|
||||
steps:
|
||||
# vercel begs for the .git dir so here it is my guy
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ./dist/website
|
||||
|
||||
- uses: amondnet/vercel-action@v25
|
||||
with:
|
||||
vercel-token: ${{ secrets.VERCEL_TOKEN }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
vercel-org-id: ${{ secrets.VERCEL_ORG_ID}}
|
||||
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
|
||||
working-directory: ./dist/website
|
||||
+2
-1
@@ -8,7 +8,7 @@ package-lock.json
|
||||
dist/
|
||||
dist.*
|
||||
yarn-error.log
|
||||
/test.*
|
||||
test.*
|
||||
/docs/.vitepress/dist
|
||||
/docs/.vitepress/cache
|
||||
servers.csv
|
||||
@@ -26,3 +26,4 @@ target
|
||||
# !packages/core/src/protos/*.proto
|
||||
/.vercel
|
||||
/.astro
|
||||
.vercel
|
||||
Vendored
+4
@@ -7,6 +7,8 @@
|
||||
"adsense",
|
||||
"aeson",
|
||||
"alphatest",
|
||||
"analyticsdata",
|
||||
"analyticsreporting",
|
||||
"APAC",
|
||||
"apcr",
|
||||
"applypatches",
|
||||
@@ -94,6 +96,7 @@
|
||||
"leaderboards",
|
||||
"lerp",
|
||||
"LINEARSIZE",
|
||||
"lmao",
|
||||
"loadouts",
|
||||
"loddist",
|
||||
"lods",
|
||||
@@ -186,6 +189,7 @@
|
||||
"unpickled",
|
||||
"Unregistering",
|
||||
"unrotate",
|
||||
"vercel",
|
||||
"vickers",
|
||||
"vitepress",
|
||||
"Vovko",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# BlitzKit 1.26.1
|
||||
|
||||
The auctions are here!
|
||||
|
||||
## Features
|
||||
|
||||
- Added the `/auction` command
|
||||
- No need to login or go to the official website, just use `/auction`
|
||||
- Everything built into Discord, one step at a time!
|
||||
@@ -0,0 +1,22 @@
|
||||
# BlitzKit 1.26.2
|
||||
|
||||
Two updates in a day! Why? Well, these two updates were being cooked separately in parallel and happened to have matured on the same day. Merry early Christmas!
|
||||
|
||||
## Features
|
||||
|
||||
- Highly optimized the component rerenders of the Tank Sandbox
|
||||
- Implemented Google Analytics into the homepage to more accurately show the most popular tanks
|
||||
- Optimized page load speeds and memory consumption by loading all data in parallel instead of sequentially
|
||||
|
||||
## Fixes
|
||||
|
||||
- Dynamic armor is now loaded in React Suspense to avoid race conditions
|
||||
- Re-ordered cards on the home page so that it makes a little bit more sense
|
||||
- The warnings section of the Tankopedia pages are now padded on the sides
|
||||
- The calibers in the antagonist gun selector are now rounded correctly
|
||||
- Tank class icons are now colored and sized properly again in the header
|
||||
|
||||
## Technical Changes
|
||||
|
||||
- Removed the extreme JPEG compression for the tank textures
|
||||
- Buffers of the same length are now assumed to be identical when diffing lmao I hope this doesn't go wrong
|
||||
@@ -0,0 +1,19 @@
|
||||
# BlitzKit 1.26.3
|
||||
|
||||
## Features
|
||||
|
||||
- The bot now notifies you if your username/clan search query is too long or short instead of just showing a generic no results message
|
||||
- Reduced the popular tanks list on the home page back to 8 tanks
|
||||
- `/owned-tanks` now links to the corresponding Tankopedia pages
|
||||
- Added more funky rich presence statuses
|
||||
|
||||
## Fixes
|
||||
|
||||
- The website no longer crashes when turning on Static Armor mode
|
||||
- `/stats` and other commands with tank filter commands now work properly once again
|
||||
- The swap button in the Tankopedia pages no longer crashes switching between tech tree and non-tech tree tanks
|
||||
- `/owned-tanks` and `/auction` now chunk text better to meet Discord's 2000 character limit
|
||||
- Tanks with fixes camouflages like the M60 no longer have the option to disable the camouflage of in the Compare tool
|
||||
- The pointer cursor and underscore once again appear now in the tank search in the Compare tool
|
||||
- Opaque mode in the Dynamic armor mode once again works instantly
|
||||
- The rich presence/status/bio of the bot once again counts all servers instead of just the ones the single shard is responsible for
|
||||
@@ -0,0 +1,15 @@
|
||||
# BlitzKit 1.26.4
|
||||
|
||||
## Features
|
||||
|
||||
- `/auction` now warns if a tank stock is at or below 500 and now strikes through the tank name if its out
|
||||
- Added advanced armor highlighting mode
|
||||
- Green: penetration
|
||||
- Red: blocked
|
||||
- Pink: ricochet
|
||||
- Cyan: penetration with 3 calibers rule
|
||||
- Optimized the armor highlighting for about a 15% increase in frame rate
|
||||
|
||||
## Fixes
|
||||
|
||||
- Added 5000 doses of adrenaline, incase the BOT DECIDES TO DIE AGAIN FOR NO REASON
|
||||
@@ -0,0 +1,19 @@
|
||||
# BlitzKit 1.26.5
|
||||
|
||||
¡Ahora en 6 idiomas! This version of BlitzKit adds support for 4 new languages totalling up to 6! [Please help translate on Crowdin](https://crowdin.com/project/blitzkrieg).
|
||||
|
||||
## Features
|
||||
|
||||
- Added Croatian, Russian, Ukrainian, and Chinese
|
||||
- Pre-existing languages: English, Spanish
|
||||
- Added better lighting making tanks a little less yellow and a bit more realistic
|
||||
- Added custom tank icons (some may be missing when the update drops; just give them an hour or so to update)
|
||||
- Increased the maximum recently viewed tanks list size to 14
|
||||
- Tweaked the tank name in the hero section of the Tankopedia to allow the tank to be in the center of the page
|
||||
- The headers in the Performance tool are now sticky just like the Compare tool
|
||||
|
||||
## Fixes
|
||||
|
||||
- Removed unnecessary third party script that did absolutely nothing
|
||||
- Localizations are now stricter in what they allow, reducing errors in the Discord bot
|
||||
- Added 50 gallons of tomato soup to the bot in an attempt to keep it alive
|
||||
+2
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "blitzkit",
|
||||
"private": true,
|
||||
"version": "1.26.0",
|
||||
"version": "1.26.5",
|
||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||
"type": "module",
|
||||
"types": "./types.d.ts",
|
||||
@@ -39,6 +39,7 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sharp": "^0.33.5",
|
||||
"lodash-es": "^4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"@blitzkit/core": "workspace:*",
|
||||
"satori": "^0.10.14",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"discord.js": "^14.15.3",
|
||||
"discord.js": "^14.16.3",
|
||||
"react": "^18.3.1",
|
||||
"@radix-ui/colors": "^3.0.0",
|
||||
"fuzzysort": "^2.0.4",
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import {
|
||||
BlitzTankClass,
|
||||
Region,
|
||||
regionToRegionSubdomain,
|
||||
} from '@blitzkit/core';
|
||||
import { tankDefinitions } from '../core/blitzkit/nonBlockingPromises';
|
||||
import { chunkLines } from '../core/discord/chunkLines';
|
||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||
import { translator } from '../core/localization/translator';
|
||||
import { CommandRegistry } from '../events/interactionCreate';
|
||||
|
||||
interface Auction {
|
||||
count: number;
|
||||
has_next: boolean;
|
||||
results: Result[];
|
||||
}
|
||||
|
||||
type Result =
|
||||
| {
|
||||
id?: number;
|
||||
type: 'vehicle';
|
||||
countable?: boolean;
|
||||
entity?: Entity;
|
||||
initial_count: number;
|
||||
current_count: number;
|
||||
saleable?: boolean;
|
||||
available_from?: Date;
|
||||
available_before?: Date;
|
||||
price?: Price;
|
||||
next_price: Price | null;
|
||||
available: true;
|
||||
display?: boolean;
|
||||
next_price_datetime: Date;
|
||||
next_price_timestamp: number;
|
||||
}
|
||||
| {
|
||||
available: false;
|
||||
type: 'vehicle';
|
||||
};
|
||||
|
||||
interface Entity {
|
||||
id: number;
|
||||
name: string;
|
||||
nation: string;
|
||||
subnation: string;
|
||||
use_subnation_flag: boolean;
|
||||
type_slug: BlitzTankClass;
|
||||
level: number;
|
||||
roman_level: string;
|
||||
user_string: string;
|
||||
image_url: string;
|
||||
preview_image_url: string;
|
||||
is_premium: boolean;
|
||||
is_collectible: boolean;
|
||||
}
|
||||
|
||||
interface Price {
|
||||
currency: Currency;
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface Currency {
|
||||
name: 'gold';
|
||||
count: number;
|
||||
title: 'Gold';
|
||||
image_url: string;
|
||||
sizes: unknown;
|
||||
type: 'currency';
|
||||
}
|
||||
|
||||
const WARNING_COUNT = 500;
|
||||
|
||||
export const auctionCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
resolve({
|
||||
command: createLocalizedCommand('auction'),
|
||||
|
||||
async handler(interaction) {
|
||||
const { t, translate } = translator(interaction.locale);
|
||||
const awaitedTankDefinitions = await tankDefinitions;
|
||||
const region: Region = 'com';
|
||||
const pageSize = 80;
|
||||
const saleable = true;
|
||||
const data = await fetch(
|
||||
`https://${regionToRegionSubdomain(
|
||||
region,
|
||||
)}.wotblitz.com/en/api/events/items/auction/?page_size=${
|
||||
pageSize
|
||||
}&type[]=vehicle&saleable=${saleable}`,
|
||||
).then((response) => response.json() as Promise<Auction>);
|
||||
const saleableTanks = data.results.filter((result) => result.available);
|
||||
|
||||
if (saleableTanks.length === 0) {
|
||||
return t`bot.commands.auction.errors.no_auction`;
|
||||
}
|
||||
|
||||
const nextPricesTimestamp = saleableTanks[0].next_price_timestamp;
|
||||
|
||||
const title = `# ${t`bot.commands.auction.body.title`}`;
|
||||
const subtitle = `${translate('bot.commands.auction.body.subtitle', [
|
||||
`${saleableTanks.length} / ${data.results.length}`,
|
||||
`<t:${nextPricesTimestamp}:R>`,
|
||||
'<https://wotblitz.com/auction/>',
|
||||
])}`;
|
||||
const body = saleableTanks.map((data, index) => {
|
||||
const tank = awaitedTankDefinitions.tanks[data.entity!.id];
|
||||
|
||||
if (!tank) return '';
|
||||
|
||||
const name = `[${tank.name}](<https://blitzkit.app/tools/tankopedia/${
|
||||
tank.id
|
||||
}>)`;
|
||||
const next =
|
||||
data.next_price === null
|
||||
? ''
|
||||
: `\n-# ${translate('bot.commands.auction.body.next', [
|
||||
`<:gold:1317173197082333244> ${data.next_price.value.toLocaleString(
|
||||
interaction.locale,
|
||||
)}`,
|
||||
])}`;
|
||||
const available = translate('bot.commands.auction.body.available', [
|
||||
data.current_count.toLocaleString(interaction.locale),
|
||||
data.initial_count.toLocaleString(interaction.locale),
|
||||
]);
|
||||
const isOut = data.current_count === 0;
|
||||
const outString = isOut ? '~~' : '';
|
||||
const isLow = data.current_count <= WARNING_COUNT;
|
||||
|
||||
return `${index + 1}. ${
|
||||
outString
|
||||
}${name} <:gold:1317173197082333244> ${data.price!.value.toLocaleString(
|
||||
interaction.locale,
|
||||
)}${outString}\n-#${isLow ? ' ‼️ ' : ' '}${available}${isOut ? '' : next}`;
|
||||
});
|
||||
const header = `${title}\n${subtitle}`;
|
||||
const lines = [header, ...body];
|
||||
|
||||
return chunkLines(lines, '\n\n');
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import packageJSON from '../../../../package.json' assert { type: 'json' };
|
||||
import packageJSON from '../../../../package.json';
|
||||
import { client } from '../core/discord/client';
|
||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||
import { translator } from '../core/localization/translator';
|
||||
|
||||
@@ -24,7 +24,11 @@ export const inactiveCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
return option
|
||||
.setName('threshold')
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.commands.inactive.options.threshold'),
|
||||
localizationObject(
|
||||
'bot.commands.inactive.options.threshold',
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
)
|
||||
.setDescription(
|
||||
translate('bot.commands.inactive.options.threshold.description'),
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
getAccountInfo,
|
||||
getTankStats,
|
||||
} from '@blitzkit/core';
|
||||
import { chunk } from 'lodash-es';
|
||||
import markdownEscape from 'markdown-escape';
|
||||
import {
|
||||
gameDefinitions,
|
||||
@@ -15,13 +14,12 @@ import {
|
||||
import { addTierChoices } from '../core/discord/addTierChoices';
|
||||
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
||||
import { autocompleteUsername } from '../core/discord/autocompleteUsername';
|
||||
import { chunkLines } from '../core/discord/chunkLines';
|
||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||
import { resolvePlayerFromCommand } from '../core/discord/resolvePlayerFromCommand';
|
||||
import { translator } from '../core/localization/translator';
|
||||
import { CommandRegistry } from '../events/interactionCreate';
|
||||
|
||||
const TANKS_PER_MESSAGE = 64;
|
||||
|
||||
export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
resolve({
|
||||
command: createLocalizedCommand('owned-tanks')
|
||||
@@ -61,7 +59,11 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
|
||||
const awaitedTankDefinitions = await tankDefinitions;
|
||||
const awaitedGameDefinitions = await gameDefinitions;
|
||||
const lines = tankStats
|
||||
const title = translate('bot.commands.owned_tanks.body.title', [
|
||||
markdownEscape(accountInfo.nickname),
|
||||
TIER_ROMAN_NUMERALS[tier],
|
||||
]);
|
||||
const tankList = tankStats
|
||||
.map(({ tank_id }) => awaitedTankDefinitions.tanks[tank_id])
|
||||
.filter((tank) => tank.tier === tier)
|
||||
.sort(
|
||||
@@ -73,16 +75,13 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
awaitedGameDefinitions.nations.indexOf(a.nation) -
|
||||
awaitedGameDefinitions.nations.indexOf(b.nation),
|
||||
)
|
||||
.map((tank) => `${flags[tank.nation]} ${markdownEscape(tank.name)}`);
|
||||
.map(
|
||||
(tank) =>
|
||||
`[${flags[tank.nation]} ${markdownEscape(tank.name)}](<https://blitzkit.app/tools/tankopedia/${tank.id}>)`,
|
||||
);
|
||||
const lines = [title, ...tankList];
|
||||
|
||||
return chunk(lines, TANKS_PER_MESSAGE).map((lines, index) =>
|
||||
index === 0
|
||||
? `${translate('bot.commands.owned_tanks.body.title', [
|
||||
markdownEscape(accountInfo.nickname),
|
||||
TIER_ROMAN_NUMERALS[tier],
|
||||
])}${lines.join('\n')}`
|
||||
: lines.join('\n'),
|
||||
);
|
||||
return chunkLines(lines);
|
||||
},
|
||||
|
||||
autocomplete: autocompleteUsername,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
getLeagueFromScore,
|
||||
getRatingInfo,
|
||||
getRatingNeighbors,
|
||||
imgur,
|
||||
} from '@blitzkit/core';
|
||||
import { SlashCommandSubcommandBuilder } from 'discord.js';
|
||||
import markdownEscape from 'markdown-escape';
|
||||
@@ -119,6 +120,19 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
statsB2,
|
||||
);
|
||||
const leagueTheme = LEAGUE_ACCENT[league.index];
|
||||
let rewardImage = reward
|
||||
? reward.type === 'vehicle'
|
||||
? reward.vehicle.preview_image_url
|
||||
: reward.stuff.image_url
|
||||
: undefined;
|
||||
|
||||
if (rewardImage) {
|
||||
const { ok } = await fetch(rewardImage);
|
||||
|
||||
if (!ok) {
|
||||
rewardImage = imgur('kP88vqr');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandWrapper>
|
||||
@@ -325,11 +339,7 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
height: 64,
|
||||
objectFit: 'cover',
|
||||
}}
|
||||
src={await iconPng(
|
||||
reward.type === 'vehicle'
|
||||
? reward.vehicle.preview_image_url
|
||||
: reward.stuff.image_url,
|
||||
)}
|
||||
src={await iconPng(rewardImage!)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -63,6 +63,8 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
||||
.setNameLocalizations(
|
||||
localizationObject(
|
||||
'bot.commands.rating_leaderboard.options.limit',
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
)
|
||||
.setDescription(
|
||||
@@ -87,6 +89,8 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
||||
.setNameLocalizations(
|
||||
localizationObject(
|
||||
'bot.commands.rating_leaderboard.options.season',
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
)
|
||||
.setDescription(
|
||||
@@ -148,6 +152,8 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
||||
.setNameLocalizations(
|
||||
localizationObject(
|
||||
'bot.commands.rating_leaderboard.options.league',
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
)
|
||||
.setDescription(
|
||||
|
||||
@@ -36,7 +36,11 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
option
|
||||
.setName(t`bot.commands.research.options.target_tank`)
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.commands.research.options.target_tank'),
|
||||
localizationObject(
|
||||
'bot.commands.research.options.target_tank',
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
)
|
||||
.setDescription(
|
||||
t`bot.commands.research.options.target_tank.description`,
|
||||
@@ -53,7 +57,11 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
option
|
||||
.setName(t`bot.commands.research.options.starting_tank`)
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.commands.research.options.starting_tank'),
|
||||
localizationObject(
|
||||
'bot.commands.research.options.starting_tank',
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
)
|
||||
.setDescription(
|
||||
t`bot.commands.research.options.starting_tank.description`,
|
||||
|
||||
@@ -24,7 +24,11 @@ export const searchClansCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
return option
|
||||
.setName(t`bot.commands.search_clans.options.limit`)
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.commands.search_clans.options.limit'),
|
||||
localizationObject(
|
||||
'bot.commands.search_clans.options.limit',
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
)
|
||||
.setDescription(
|
||||
translate('bot.commands.search_clans.options.limit.description', [
|
||||
|
||||
@@ -29,7 +29,11 @@ export const searchPlayersCommand = new Promise<CommandRegistry>((resolve) => {
|
||||
option
|
||||
.setName(t`bot.commands.search_players.options.limit`)
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.commands.search_players.options.limit'),
|
||||
localizationObject(
|
||||
'bot.commands.search_players.options.limit',
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
)
|
||||
.setDescription(
|
||||
translate('bot.commands.search_players.options.limit.description', [
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { searchClansAcrossRegions } from '@blitzkit/core';
|
||||
import { Locale } from 'discord.js';
|
||||
import { UserError } from '../blitzkit/userError';
|
||||
import { translator } from '../localization/translator';
|
||||
|
||||
export async function searchClansAcrossRegionsBotWrapper(
|
||||
search: string,
|
||||
locale: Locale,
|
||||
limit?: number,
|
||||
) {
|
||||
const { t } = translator(locale);
|
||||
const trimmed = search.trim();
|
||||
|
||||
if (trimmed.length < 3) {
|
||||
throw new UserError(t`bot.common.errors.query_too_small`);
|
||||
}
|
||||
|
||||
if (trimmed.length > 100) {
|
||||
throw new UserError(t`bot.common.errors.query_too_long`);
|
||||
}
|
||||
|
||||
return await searchClansAcrossRegions(trimmed, limit);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { searchPlayersAcrossRegions } from '@blitzkit/core';
|
||||
import { Locale } from 'discord.js';
|
||||
import { UserError } from '../blitzkit/userError';
|
||||
import { translator } from '../localization/translator';
|
||||
|
||||
export async function searchPlayersAcrossRegionsBotWrapper(
|
||||
search: string,
|
||||
locale: Locale,
|
||||
limit?: number,
|
||||
) {
|
||||
const { t } = translator(locale);
|
||||
const trimmed = search.trim();
|
||||
|
||||
if (trimmed.length < 3) {
|
||||
throw new UserError(t`bot.common.errors.query_too_small`);
|
||||
}
|
||||
|
||||
if (trimmed.length > 100) {
|
||||
throw new UserError(t`bot.common.errors.query_too_long`);
|
||||
}
|
||||
|
||||
return await searchPlayersAcrossRegions(trimmed, limit);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
fetchDiscoveredIdsDefinitions,
|
||||
fetchGameDefinitions,
|
||||
fetchMapDefinitions,
|
||||
fetchTankDefinitions,
|
||||
@@ -9,3 +10,4 @@ export const tankDefinitions = fetchTankDefinitions();
|
||||
export const gameDefinitions = fetchGameDefinitions();
|
||||
export const tankNames = fetchTankNames();
|
||||
export const mapDefinitions = fetchMapDefinitions();
|
||||
export const discoveredIdsDefinitions = fetchDiscoveredIdsDefinitions();
|
||||
@@ -7,7 +7,9 @@ export function addClanChoices(option: SlashCommandStringOption) {
|
||||
|
||||
return option
|
||||
.setName('clan')
|
||||
.setNameLocalizations(localizationObject('bot.common.options.clan'))
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.common.options.clan', undefined, true),
|
||||
)
|
||||
.setDescription(translate('bot.common.options.clan.description'))
|
||||
.setDescriptionLocalizations(
|
||||
localizationObject('bot.common.options.clan.description'),
|
||||
|
||||
@@ -25,7 +25,11 @@ export function addFilterOptions<
|
||||
option
|
||||
.setName(translate('bot.common.options.filters.nation'))
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.common.options.filters.nation'),
|
||||
localizationObject(
|
||||
'bot.common.options.filters.nation',
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
)
|
||||
.setDescription(
|
||||
translate('bot.common.options.filters.nation.description'),
|
||||
@@ -50,17 +54,19 @@ export function addFilterOptions<
|
||||
.addStringOption((option) => addTierChoices(option).setRequired(false))
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName(translate('bot.common.options.filters.tank_class'))
|
||||
.setName(translate('bot.common.options.filters.class'))
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.common.options.filters.tank_class'),
|
||||
localizationObject(
|
||||
'bot.common.options.filters.class',
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
)
|
||||
.setDescription(
|
||||
translate('bot.common.options.filters.tank_class.description'),
|
||||
translate('bot.common.options.filters.class.description'),
|
||||
)
|
||||
.setDescriptionLocalizations(
|
||||
localizationObject(
|
||||
'bot.common.options.filters.tank_class.description',
|
||||
),
|
||||
localizationObject('bot.common.options.filters.class.description'),
|
||||
)
|
||||
.addChoices(
|
||||
...TANK_CLASSES.map(
|
||||
@@ -78,17 +84,19 @@ export function addFilterOptions<
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName(translate('bot.common.options.filters.tree_type'))
|
||||
.setName(translate('bot.common.options.filters.type'))
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.common.options.filters.tree_type'),
|
||||
localizationObject(
|
||||
'bot.common.options.filters.type',
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
)
|
||||
.setDescription(
|
||||
translate('bot.common.options.filters.tree_type.description'),
|
||||
translate('bot.common.options.filters.type.description'),
|
||||
)
|
||||
.setDescriptionLocalizations(
|
||||
localizationObject(
|
||||
'bot.common.options.filters.tree_type.description',
|
||||
),
|
||||
localizationObject('bot.common.options.filters.type.description'),
|
||||
)
|
||||
.addChoices(
|
||||
...TANK_TYPES.map(
|
||||
|
||||
@@ -18,12 +18,20 @@ export function addPeriodSubCommands(
|
||||
) {
|
||||
const { translate } = translator(Locale.EnglishUS);
|
||||
const subcommandNameLocalizations = {
|
||||
today: localizationObject('bot.common.subcommands.today'),
|
||||
30: localizationObject('bot.common.subcommands.30'),
|
||||
60: localizationObject('bot.common.subcommands.60'),
|
||||
90: localizationObject('bot.common.subcommands.90'),
|
||||
career: localizationObject('bot.common.subcommands.career'),
|
||||
custom: localizationObject('bot.common.subcommands.custom'),
|
||||
today: localizationObject('bot.common.subcommands.today', undefined, true),
|
||||
30: localizationObject('bot.common.subcommands.30', undefined, true),
|
||||
60: localizationObject('bot.common.subcommands.60', undefined, true),
|
||||
90: localizationObject('bot.common.subcommands.90', undefined, true),
|
||||
career: localizationObject(
|
||||
'bot.common.subcommands.career',
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
custom: localizationObject(
|
||||
'bot.common.subcommands.custom',
|
||||
undefined,
|
||||
true,
|
||||
),
|
||||
};
|
||||
const subcommandDescriptionLocalizations = {
|
||||
today: localizationObject('bot.common.subcommands.today.description'),
|
||||
|
||||
@@ -12,7 +12,9 @@ export function addRegionChoices(option: SlashCommandStringOption) {
|
||||
|
||||
return option
|
||||
.setName(t`bot.common.options.region`)
|
||||
.setNameLocalizations(localizationObject('bot.common.options.region'))
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.common.options.region', undefined, true),
|
||||
)
|
||||
.setDescription(t`bot.common.options.region.description`)
|
||||
.setDescriptionLocalizations(
|
||||
localizationObject('bot.common.options.region.description'),
|
||||
|
||||
@@ -7,7 +7,9 @@ export function addTankChoices(option: SlashCommandStringOption) {
|
||||
|
||||
return option
|
||||
.setName(translate('bot.common.options.tank'))
|
||||
.setNameLocalizations(localizationObject('bot.common.options.tank'))
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.common.options.tank', undefined, true),
|
||||
)
|
||||
.setDescription(translate('bot.common.options.tank.description'))
|
||||
.setDescriptionLocalizations(
|
||||
localizationObject('bot.common.options.tank.description'),
|
||||
|
||||
@@ -13,7 +13,9 @@ export function addTierChoices(option: SlashCommandStringOption) {
|
||||
|
||||
return option
|
||||
.setName(t`bot.common.options.tier`)
|
||||
.setNameLocalizations(localizationObject('bot.common.options.tier'))
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.common.options.tier', undefined, true),
|
||||
)
|
||||
.setDescription(t`bot.common.options.tier.description`)
|
||||
.setDescriptionLocalizations(
|
||||
localizationObject('bot.common.options.tier.description'),
|
||||
|
||||
@@ -7,7 +7,9 @@ export function addUsernameChoices(option: SlashCommandStringOption) {
|
||||
|
||||
return option
|
||||
.setName(t`bot.common.options.username`)
|
||||
.setNameLocalizations(localizationObject('bot.common.options.username'))
|
||||
.setNameLocalizations(
|
||||
localizationObject('bot.common.options.username', undefined, true),
|
||||
)
|
||||
.setDescription(t`bot.common.options.username.description`)
|
||||
.setDescriptionLocalizations(
|
||||
localizationObject('bot.common.options.username.description'),
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
export function chunkLines(lines: string[], joiner = '\n', maxSize = 2000) {
|
||||
const chunks = [''];
|
||||
|
||||
lines.map((line) => {
|
||||
if (chunks.at(-1)!.length + line.length > maxSize) {
|
||||
chunks.push('');
|
||||
}
|
||||
|
||||
if (chunks.at(-1)!.length === 0) {
|
||||
chunks[chunks.length - 1] += line;
|
||||
} else {
|
||||
chunks[chunks.length - 1] += joiner + line;
|
||||
}
|
||||
});
|
||||
|
||||
return chunks;
|
||||
}
|
||||
@@ -27,7 +27,9 @@ export function createLocalizedCommand(
|
||||
const { translate } = translator(Locale.EnglishUS);
|
||||
const slashCommand = new SlashCommandBuilder()
|
||||
.setName(command)
|
||||
.setNameLocalizations(localizationObject(`bot.commands.${commandPathItem}`))
|
||||
.setNameLocalizations(
|
||||
localizationObject(`bot.commands.${commandPathItem}`, undefined, true),
|
||||
)
|
||||
.setDescription(translate(`bot.commands.${commandPathItem}.description`))
|
||||
.setDescriptionLocalizations(
|
||||
localizationObject(`bot.commands.${commandPathItem}.description`),
|
||||
@@ -38,6 +40,8 @@ export function createLocalizedCommand(
|
||||
if ('subcommand' in subItem) {
|
||||
const subcommandNameLocalizations = localizationObject(
|
||||
`bot.commands.${commandPathItem}.subcommands.${subItem.subcommand}`,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
const subcommandDescriptionLocalizations = localizationObject(
|
||||
`bot.commands.${commandPathItem}.subcommands.${subItem.subcommand}.description`,
|
||||
@@ -59,6 +63,8 @@ export function createLocalizedCommand(
|
||||
} else {
|
||||
const subcommandNameLocalizations = localizationObject(
|
||||
`bot.commands.${commandPathItem}.groups.${subItem.group}`,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
const subcommandDescriptionLocalizations = localizationObject(
|
||||
`bot.commands.${commandPathItem}.groups.${subItem.group}.description`,
|
||||
|
||||
@@ -2,13 +2,29 @@ import { Locale } from 'discord.js';
|
||||
import { SUPPORTED_LOCALES } from '../localization/strings/constants';
|
||||
import { translator } from '../localization/translator';
|
||||
|
||||
export function localizationObject(path: string, literals?: string[]) {
|
||||
const validNameRegex = /^[-_\p{L}\p{N}\p{sc=Deva}\p{sc=Thai}]{1,32}$/u;
|
||||
|
||||
export function localizationObject(
|
||||
path: string,
|
||||
literals?: string[],
|
||||
strict = false,
|
||||
) {
|
||||
return SUPPORTED_LOCALES.reduce<Partial<Record<Locale, string>>>(
|
||||
(localizations, locale) => {
|
||||
const { translate } = translator(locale);
|
||||
const translation = translate(path, literals);
|
||||
const isValid = !strict || validNameRegex.test(translation);
|
||||
|
||||
if (!isValid) {
|
||||
console.warn(
|
||||
`Invalid localization for ${locale} (${path}): ${translation}; skipping...`,
|
||||
);
|
||||
return localizations;
|
||||
}
|
||||
|
||||
return {
|
||||
...localizations,
|
||||
[locale]: translate(path, literals),
|
||||
[locale]: translation,
|
||||
};
|
||||
},
|
||||
{},
|
||||
|
||||
@@ -2,5 +2,5 @@ import { assertSecret } from '@blitzkit/core';
|
||||
import { ShardingManager } from 'discord.js';
|
||||
|
||||
export const manager = new ShardingManager('dist/bot/workers/bot.js', {
|
||||
token: assertSecret(process.env.DISCORD_TOKEN),
|
||||
token: assertSecret(import.meta.env.DISCORD_TOKEN),
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Region, searchClansAcrossRegions } from '@blitzkit/core';
|
||||
import { Region } from '@blitzkit/core';
|
||||
import { ChatInputCommandInteraction } from 'discord.js';
|
||||
import markdownEscape from 'markdown-escape';
|
||||
import { searchClansAcrossRegionsBotWrapper } from '../blitz/searchClansAcrossRegionsBotWrapper';
|
||||
import { UserError } from '../blitzkit/userError';
|
||||
import { translator } from '../localization/translator';
|
||||
|
||||
@@ -16,7 +17,10 @@ export async function resolveClanFromCommand(
|
||||
const [server, accountId] = clan.split('/');
|
||||
return { region: server as Region, id: Number(accountId) };
|
||||
} else {
|
||||
const accounts = await searchClansAcrossRegions(clan);
|
||||
const accounts = await searchClansAcrossRegionsBotWrapper(
|
||||
clan,
|
||||
interaction.locale,
|
||||
);
|
||||
|
||||
if (accounts[0]) {
|
||||
return { region: accounts[0].region, id: accounts[0].clan_id };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { idToRegion, Region, searchPlayersAcrossRegions } from '@blitzkit/core';
|
||||
import { idToRegion, Region } from '@blitzkit/core';
|
||||
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
|
||||
import markdownEscape from 'markdown-escape';
|
||||
import { searchPlayersAcrossRegionsBotWrapper } from '../../blitz/searchPlayersAcrossRegionsBotWrapper';
|
||||
import { getBlitzFromDiscord } from '../../blitzkit/getBlitzFromDiscord';
|
||||
import { UserError } from '../../blitzkit/userError';
|
||||
import { translator } from '../../localization/translator';
|
||||
@@ -23,7 +24,10 @@ export async function resolvePlayerFromCommand(
|
||||
const region = idToRegion(id);
|
||||
return { region, id } satisfies ResolvedPlayer;
|
||||
} else {
|
||||
const accounts = await searchPlayersAcrossRegions(commandUsername);
|
||||
const accounts = await searchPlayersAcrossRegionsBotWrapper(
|
||||
commandUsername,
|
||||
interaction.locale,
|
||||
);
|
||||
|
||||
if (accounts[0]) {
|
||||
return {
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
import { Locale } from 'discord.js';
|
||||
|
||||
export const SUPPORTED_LOCALES: Locale[] = [Locale.EnglishUS, Locale.SpanishES];
|
||||
export const SUPPORTED_LOCALES: Locale[] = [
|
||||
Locale.EnglishUS,
|
||||
Locale.SpanishES,
|
||||
Locale.Croatian,
|
||||
Locale.Russian,
|
||||
Locale.Ukrainian,
|
||||
Locale.ChineseCN,
|
||||
];
|
||||
@@ -13,7 +13,7 @@ export const translations = SUPPORTED_LOCALES.reduce<
|
||||
>(
|
||||
(table, locale) => ({
|
||||
...table,
|
||||
[locale]: require(`@blitzkit/core/lang/${locale}.json`),
|
||||
[locale]: require(`../../../../../core/lang/${locale}.json`),
|
||||
}),
|
||||
{},
|
||||
);
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
SlashCommandSubcommandsOnlyBuilder,
|
||||
} from 'discord.js';
|
||||
import { aboutCommand } from '../../commands/about';
|
||||
import { auctionCommand } from '../../commands/auction';
|
||||
import { breakdownCommand } from '../../commands/breakdown';
|
||||
import { debugCommand } from '../../commands/debug';
|
||||
import { evolutionCommand } from '../../commands/evolution';
|
||||
@@ -76,6 +77,7 @@ interface CommandRegistryExplicit extends CommandRegistryBase {
|
||||
export type CommandRegistry = CommandRegistryImplicit | CommandRegistryExplicit;
|
||||
|
||||
export const COMMANDS_RAW: Promise<CommandRegistry>[] = [
|
||||
auctionCommand,
|
||||
permissionsCommand,
|
||||
debugCommand,
|
||||
aboutCommand,
|
||||
@@ -115,7 +117,7 @@ export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => {
|
||||
);
|
||||
});
|
||||
|
||||
const rest = new REST().setToken(assertSecret(process.env.DISCORD_TOKEN));
|
||||
const rest = new REST().setToken(assertSecret(import.meta.env.DISCORD_TOKEN));
|
||||
|
||||
commands.then((awaitedCommands) => {
|
||||
const body = Object.values(awaitedCommands).map((registry) =>
|
||||
@@ -123,10 +125,8 @@ commands.then((awaitedCommands) => {
|
||||
);
|
||||
|
||||
rest.put(
|
||||
Routes.applicationCommands(assertSecret(process.env.DISCORD_CLIENT_ID)),
|
||||
{
|
||||
body,
|
||||
},
|
||||
Routes.applicationCommands(assertSecret(import.meta.env.DISCORD_CLIENT_ID)),
|
||||
{ body },
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,29 +1,5 @@
|
||||
import { ActivityType, Client } from 'discord.js';
|
||||
import { Client } from 'discord.js';
|
||||
|
||||
export function ready(client: Client<true>) {
|
||||
console.log(`🟢 Launched bot ${client.shard?.ids[0] ?? 'default'}`);
|
||||
|
||||
const activities: (() => string)[] = [
|
||||
() => `Living in ${client.guilds.cache.size.toLocaleString()} servers`,
|
||||
() =>
|
||||
`Serving ${client.guilds.cache
|
||||
.reduce((accumulator, guild) => accumulator + guild.memberCount, 0)
|
||||
.toLocaleString()} users`,
|
||||
() =>
|
||||
`Observing ${client.guilds.cache
|
||||
.reduce(
|
||||
(accumulator, guild) => accumulator + guild.channels.cache.size,
|
||||
0,
|
||||
)
|
||||
.toLocaleString()} channels`,
|
||||
];
|
||||
|
||||
client.user.setPresence({
|
||||
activities: [
|
||||
{
|
||||
type: ActivityType.Custom,
|
||||
name: activities[Math.floor(Math.random() * activities.length)](),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
+104
-6
@@ -1,8 +1,106 @@
|
||||
import { ActivityType } from 'discord.js';
|
||||
import {
|
||||
discoveredIdsDefinitions,
|
||||
tankDefinitions,
|
||||
} from './core/blitzkit/nonBlockingPromises';
|
||||
import { manager } from './core/discord/manager';
|
||||
|
||||
manager
|
||||
.on('shardCreate', (shard) => {
|
||||
console.log(`🟡 Launching shard ${shard.id}`);
|
||||
shard.on('ready', () => console.log(`🟢 Launched shard ${shard.id}`));
|
||||
})
|
||||
.spawn();
|
||||
async function setupShardManager() {
|
||||
console.log('Pre-manager setup starting...');
|
||||
|
||||
const readyShards = new Set();
|
||||
const totalShards = manager.totalShards;
|
||||
|
||||
const allShardsReady = new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Shard initialization timed out after 60 seconds'));
|
||||
}, 60000);
|
||||
|
||||
manager.on('shardCreate', (shard) => {
|
||||
console.log(`🟡 Launching shard ${shard.id}`);
|
||||
|
||||
shard.on('ready', () => {
|
||||
console.log(`🟢 Launched shard ${shard.id}`);
|
||||
readyShards.add(shard.id);
|
||||
|
||||
if (readyShards.size === totalShards) {
|
||||
clearTimeout(timeout);
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
|
||||
shard.on('error', (error) => {
|
||||
console.error(`❌ Shard ${shard.id} encountered an error:`, error);
|
||||
});
|
||||
|
||||
shard.on('disconnect', () => {
|
||||
console.warn(`⚠️ Shard ${shard.id} disconnected`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
console.log('Spawning shards...');
|
||||
const shards = await manager.spawn();
|
||||
console.log('Shards spawned, waiting for ready state...');
|
||||
|
||||
await allShardsReady;
|
||||
console.log('All shards ready, setting up presence...');
|
||||
|
||||
let servers = 0;
|
||||
let channels = 0;
|
||||
let users = 0;
|
||||
|
||||
for (const [, shard] of shards) {
|
||||
try {
|
||||
const guilds = (await shard.fetchClientValue('guilds.cache')) as any;
|
||||
servers += guilds.length;
|
||||
|
||||
for (const guild of guilds) {
|
||||
channels += guild.channels.length;
|
||||
users += guild.memberCount;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch statistics from shard:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
const [awaitedTankDefinitions, awaitedDiscoveredIdsDefinitions] =
|
||||
await Promise.all([tankDefinitions, discoveredIdsDefinitions]);
|
||||
|
||||
const tanks = Object.values(awaitedTankDefinitions.tanks).length;
|
||||
const usernames = awaitedDiscoveredIdsDefinitions.count;
|
||||
|
||||
await manager.broadcastEval(
|
||||
(client, { servers, users, channels, tanks, usernames }) => {
|
||||
const bios = [
|
||||
`Living in ${servers.toLocaleString()} servers`,
|
||||
`Serving ${users.toLocaleString()} users`,
|
||||
`Observing ${channels.toLocaleString()} channels`,
|
||||
`Tinkering with ${tanks.toLocaleString()} tanks`,
|
||||
`Memorizing ${usernames.toLocaleString()} usernames`,
|
||||
];
|
||||
|
||||
client.user?.setPresence({
|
||||
activities: [
|
||||
{
|
||||
type: ActivityType.Custom,
|
||||
name: bios[Math.floor(Math.random() * bios.length)],
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
{ context: { servers, channels, users, tanks, usernames } },
|
||||
);
|
||||
|
||||
console.log('Bot startup complete!');
|
||||
} catch (error) {
|
||||
console.error('Critical error during startup:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
setupShardManager().catch((error) => {
|
||||
console.error('Failed to start the bot:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -6,4 +6,8 @@ console.log(`🟡 Launching bot ${client.shard?.ids[0]}`);
|
||||
|
||||
registerProcesses();
|
||||
|
||||
client.login(assertSecret(process.env.DISCORD_TOKEN));
|
||||
console.log(`post register processes on shard ${client.shard?.ids[0]}`);
|
||||
|
||||
client.login(assertSecret(import.meta.env.DISCORD_TOKEN));
|
||||
|
||||
console.log(`post client login on shard ${client.shard?.ids[0]}`);
|
||||
@@ -7,3 +7,5 @@ parentPort?.on('message', async (svg: string) => {
|
||||
const png = svgToPng(svg);
|
||||
parentPort?.postMessage(png);
|
||||
});
|
||||
|
||||
console.log(`post render thread parent port on thread ${threadId}`);
|
||||
@@ -154,7 +154,9 @@
|
||||
"no_tank_stats": "# No tank stats available\nThis player doesn't have any stats available. This may not be the player you are looking for.",
|
||||
"get_help": "Help Discord server",
|
||||
"uncaught_error": "# BlitzKit ran into an error!\nWe're so sorry about this. Feel free to [join the official Discord server](https://discord.gg/nDt7AjGJQH) to get help.",
|
||||
"preview": "# This command is in preview\n\nYou can only access this command in [the official Discord server](https://discord.gg/nDt7AjGJQH). The command will be generally available soon."
|
||||
"preview": "# This command is in preview\n\nYou can only access this command in [the official Discord server](https://discord.gg/nDt7AjGJQH). The command will be generally available soon.",
|
||||
"query_too_small": "# Your search is too short\nYour search must be at least 3 characters long.",
|
||||
"query_too_long": "# Your search is too long\nYour search must be at most 100 characters long."
|
||||
},
|
||||
|
||||
"wn8": {
|
||||
@@ -230,13 +232,13 @@
|
||||
"$": "nation",
|
||||
"description": "Nation"
|
||||
},
|
||||
"tank_class": {
|
||||
"$": "tank-class",
|
||||
"description": "Tank class"
|
||||
"class": {
|
||||
"$": "class",
|
||||
"description": "Class"
|
||||
},
|
||||
"tree_type": {
|
||||
"$": "tree-type",
|
||||
"description": "Tree type"
|
||||
"type": {
|
||||
"$": "type",
|
||||
"description": "Type"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -291,6 +293,24 @@
|
||||
},
|
||||
|
||||
"commands": {
|
||||
"auction": {
|
||||
"$": "auction",
|
||||
"description": "Tanks available right now in the auction",
|
||||
|
||||
"body": {
|
||||
"title": "Blitz Auction",
|
||||
"subtitle": "%s available • next prices %s\nBid now: %s",
|
||||
"next": "Next price: %s",
|
||||
"available": "Available: %s / %s"
|
||||
},
|
||||
"errors": {
|
||||
"no_auction": "# No ongoing auction\n\nCurrently, there are no tanks available for purchase in the auction. If you think this is an error, please report this discrepancy in [the official Discord Server](https://discord.gg/nDt7AjGJQH)."
|
||||
},
|
||||
"actions": {
|
||||
"bid": "Bid now"
|
||||
}
|
||||
},
|
||||
|
||||
"replay": {
|
||||
"$": "replay",
|
||||
"description": "Display replay results"
|
||||
|
||||
@@ -142,7 +142,9 @@
|
||||
"no_tank_stats": "# No hay estadísticas de tanque disponibles\nEste jugador no tiene estadísticas disponibles. Este podría no ser el jugador que estás buscando.",
|
||||
"get_help": "Servidor de ayuda de Discord",
|
||||
"uncaught_error": "# ¡Blitzkrieg sufrió un error!\nLo sentimos mucho. Siéntete libre de [unirte al servidor oficial de Discord](https://discord.gg/nDt7AjGJQH) para obtener ayuda.",
|
||||
"preview": "# Este comando es una vista previa\n\nSolo puedes acceder a este comando en [el servidor oficial de Discord](https://discord.gg/nDt7AjGJQH). Generalmente este comando estará disponible pronto."
|
||||
"preview": "# Este comando es una vista previa\n\nSolo puedes acceder a este comando en [el servidor oficial de Discord](https://discord.gg/nDt7AjGJQH). Generalmente este comando estará disponible pronto.",
|
||||
"query_too_small": "# Your search is too short\nYour search must be at least 3 characters long.",
|
||||
"query_too_long": "# Your search is too long\nYour search must be at most 100 characters long."
|
||||
},
|
||||
"wn8": {
|
||||
"$": "WN8",
|
||||
@@ -211,13 +213,13 @@
|
||||
"$": "nación",
|
||||
"description": "Nación"
|
||||
},
|
||||
"tank_class": {
|
||||
"$": "clase-de-tanque",
|
||||
"description": "Clase de tanque"
|
||||
"class": {
|
||||
"$": "class",
|
||||
"description": "Class"
|
||||
},
|
||||
"tree_type": {
|
||||
"$": "tipo-de-árbol",
|
||||
"description": "Tipo de árbol"
|
||||
"type": {
|
||||
"$": "type",
|
||||
"description": "Type"
|
||||
}
|
||||
},
|
||||
"region": {
|
||||
@@ -263,6 +265,22 @@
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"auction": {
|
||||
"$": "auction",
|
||||
"description": "Tanks available right now in the auction",
|
||||
"body": {
|
||||
"title": "Blitz Auction",
|
||||
"subtitle": "%s available • next prices %s\nBid now: %s",
|
||||
"next": "Next price: %s",
|
||||
"available": "Available: %s / %s"
|
||||
},
|
||||
"errors": {
|
||||
"no_auction": "# No ongoing auction\n\nCurrently, there are no tanks available for purchase in the auction. If you think this is an error, please report this discrepancy in [the official Discord Server](https://discord.gg/nDt7AjGJQH)."
|
||||
},
|
||||
"actions": {
|
||||
"bid": "Bid now"
|
||||
}
|
||||
},
|
||||
"replay": {
|
||||
"$": "replay",
|
||||
"description": "Display replay results"
|
||||
@@ -323,9 +341,9 @@
|
||||
"$": "zonas-horarias",
|
||||
"description": "Cómo Blitzkrieg infiere y utiliza tu zona horaria"
|
||||
},
|
||||
"creators": {
|
||||
"$": "creadores",
|
||||
"description": "Personas que han trabajado en el bot"
|
||||
"credits": {
|
||||
"$": "credits",
|
||||
"description": "People who have helped BlitzKit"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+93
-75
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"common": {
|
||||
"wn8_percentile": {
|
||||
"0": "Jako loše",
|
||||
"1": "Loše",
|
||||
"2": "Ispod prosečnog",
|
||||
"3": "Prosečno",
|
||||
"4": "Nad prosečno",
|
||||
"5": "Dobro",
|
||||
"6": "Jako dobro",
|
||||
"7": "Super",
|
||||
"8": "Legendarno",
|
||||
"9": "Super Legendarno"
|
||||
"0": "Very bad",
|
||||
"1": "Bad",
|
||||
"2": "Below average",
|
||||
"3": "Average",
|
||||
"4": "Above average",
|
||||
"5": "Good",
|
||||
"6": "Very good",
|
||||
"7": "Great",
|
||||
"8": "Unicum",
|
||||
"9": "Super unicum"
|
||||
},
|
||||
"bkni_percentile": {
|
||||
"0": "Super malum",
|
||||
@@ -26,15 +26,15 @@
|
||||
"10": "Super unicum"
|
||||
},
|
||||
"nations": {
|
||||
"ussr": "S.S.S.R.",
|
||||
"germany": "Nemačka",
|
||||
"usa": "S.A.D.",
|
||||
"china": "Kina",
|
||||
"uk": "Vel. Brit.",
|
||||
"france": "Francuska",
|
||||
"ussr": "U.S.S.R.",
|
||||
"germany": "Germany",
|
||||
"usa": "U.S.A.",
|
||||
"china": "China",
|
||||
"uk": "U.K.",
|
||||
"france": "France",
|
||||
"japan": "Japan",
|
||||
"european": "Evropska Unija",
|
||||
"other": "Hibridna nacija"
|
||||
"european": "European Nation",
|
||||
"other": "Hybrid Nation"
|
||||
},
|
||||
"nations_adjectives": {
|
||||
"ussr": "U.S.S.R.",
|
||||
@@ -75,9 +75,9 @@
|
||||
},
|
||||
"regions": {
|
||||
"normal": {
|
||||
"com": "Severna Amerika",
|
||||
"eu": "Evropa",
|
||||
"asia": "Azija-Pasifik"
|
||||
"com": "North America",
|
||||
"eu": "Europe",
|
||||
"asia": "Asia-Pacific"
|
||||
},
|
||||
"short": {
|
||||
"com": "NA",
|
||||
@@ -86,11 +86,11 @@
|
||||
}
|
||||
},
|
||||
"leagues": {
|
||||
"diamond": "Dijamant",
|
||||
"platinum": "Platina",
|
||||
"gold": "Zlato",
|
||||
"silver": "Srebro",
|
||||
"bronze": "Bronza"
|
||||
"diamond": "Diamond",
|
||||
"platinum": "Platinum",
|
||||
"gold": "Gold",
|
||||
"silver": "Silver",
|
||||
"bronze": "Bronze"
|
||||
},
|
||||
"composite_stats": {
|
||||
"normalized_damage_dealt": "Damage",
|
||||
@@ -136,58 +136,60 @@
|
||||
"errors": {
|
||||
"clan_not_found": "# Could not find clan\nCouldn't find \"%s\". Try selecting a clan from the search result.",
|
||||
"player_not_found": "# Could not find user\nI couldn't find user \"%s\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.",
|
||||
"player_not_linked": "# I don't know who you are\nUse the `/link` command to get started.",
|
||||
"player_not_linked": "# Use `/link` to get started\nI don't know who you are but you can tell me today!",
|
||||
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"%s\".",
|
||||
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"%s\". Try typing the name of the tank instead of a number.",
|
||||
"no_tank_stats": "# No tank stats available\nThis player doesn't have any stats available. This may not be the player you are looking for.",
|
||||
"get_help": "Help Discord server",
|
||||
"uncaught_error": "# Blitzkrieg ran into an error!\nWe're so sorry about this. Feel free to [join the official Discord server](https://discord.gg/nDt7AjGJQH) to get help.",
|
||||
"preview": "# This command is in preview\n\nYou can only access this command in [the official Discord server](https://discord.gg/nDt7AjGJQH). The command will be generally available soon."
|
||||
"uncaught_error": "# BlitzKit ran into an error!\nWe're so sorry about this. Feel free to [join the official Discord server](https://discord.gg/nDt7AjGJQH) to get help.",
|
||||
"preview": "# This command is in preview\n\nYou can only access this command in [the official Discord server](https://discord.gg/nDt7AjGJQH). The command will be generally available soon.",
|
||||
"query_too_small": "# Your search is too short\nYour search must be at least 3 characters long.",
|
||||
"query_too_long": "# Your search is too long\nYour search must be at most 100 characters long."
|
||||
},
|
||||
"wn8": {
|
||||
"$": "WN8",
|
||||
"very_bad": "Jako loše",
|
||||
"bad": "Loše",
|
||||
"below_average": "Ispod prosečnog",
|
||||
"average": "Prosečno",
|
||||
"above_average": "Nad prosečno",
|
||||
"good": "Dobro",
|
||||
"very_good": "Jako dobro",
|
||||
"great": "Super",
|
||||
"unicum": "Legendarno",
|
||||
"super_unicum": "Super Legendarno"
|
||||
"very_bad": "Very bad",
|
||||
"bad": "Bad",
|
||||
"below_average": "Below average",
|
||||
"average": "Average",
|
||||
"above_average": "Above average",
|
||||
"good": "Good",
|
||||
"very_good": "Very good",
|
||||
"great": "Great",
|
||||
"unicum": "Unicum",
|
||||
"super_unicum": "Super unicum"
|
||||
},
|
||||
"subcommands": {
|
||||
"today": {
|
||||
"$": "danas",
|
||||
"description": "Danas"
|
||||
"$": "today",
|
||||
"description": "Today"
|
||||
},
|
||||
"30": {
|
||||
"$": "30",
|
||||
"description": "30 dana"
|
||||
"description": "30 days"
|
||||
},
|
||||
"60": {
|
||||
"$": "60",
|
||||
"description": "60 dana"
|
||||
"description": "60 days"
|
||||
},
|
||||
"90": {
|
||||
"$": "90",
|
||||
"description": "90 dana"
|
||||
"description": "90 days"
|
||||
},
|
||||
"career": {
|
||||
"$": "karijera",
|
||||
"description": "Karijera"
|
||||
"$": "career",
|
||||
"description": "Career"
|
||||
},
|
||||
"custom": {
|
||||
"$": "proizvoljno",
|
||||
"description": "Proizvoljna daljina",
|
||||
"$": "custom",
|
||||
"description": "Custom range",
|
||||
"options": {
|
||||
"start": {
|
||||
"$": "kreni",
|
||||
"$": "start",
|
||||
"description": "Start in number of days ago from today"
|
||||
},
|
||||
"end": {
|
||||
"$": "kraj",
|
||||
"$": "end",
|
||||
"description": "End in number of days ago from today"
|
||||
}
|
||||
}
|
||||
@@ -195,7 +197,7 @@
|
||||
},
|
||||
"options": {
|
||||
"clan": {
|
||||
"$": "klan",
|
||||
"$": "clan",
|
||||
"description": "The clan name or tag"
|
||||
},
|
||||
"tank": {
|
||||
@@ -211,13 +213,13 @@
|
||||
"$": "nation",
|
||||
"description": "Nation"
|
||||
},
|
||||
"tank_class": {
|
||||
"$": "tank-class",
|
||||
"description": "Tank class"
|
||||
"class": {
|
||||
"$": "class",
|
||||
"description": "Class"
|
||||
},
|
||||
"tree_type": {
|
||||
"$": "tree-type",
|
||||
"description": "Tree type"
|
||||
"type": {
|
||||
"$": "type",
|
||||
"description": "Type"
|
||||
}
|
||||
},
|
||||
"region": {
|
||||
@@ -263,6 +265,22 @@
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"auction": {
|
||||
"$": "auction",
|
||||
"description": "Tanks available right now in the auction",
|
||||
"body": {
|
||||
"title": "Blitz Auction",
|
||||
"subtitle": "%s available • next prices %s\nBid now: %s",
|
||||
"next": "Next price: %s",
|
||||
"available": "Available: %s / %s"
|
||||
},
|
||||
"errors": {
|
||||
"no_auction": "# No ongoing auction\n\nCurrently, there are no tanks available for purchase in the auction. If you think this is an error, please report this discrepancy in [the official Discord Server](https://discord.gg/nDt7AjGJQH)."
|
||||
},
|
||||
"actions": {
|
||||
"bid": "Bid now"
|
||||
}
|
||||
},
|
||||
"replay": {
|
||||
"$": "replay",
|
||||
"description": "Display replay results"
|
||||
@@ -305,44 +323,44 @@
|
||||
"commands": {
|
||||
"$": "commands",
|
||||
"description": "List of all commands and their descriptions",
|
||||
"body": "# Commands\n\nBlitzkrieg offers the following commands:\n\n"
|
||||
"body": "# Commands\n\nBlitzKit offers the following commands:\n\n"
|
||||
},
|
||||
"embeds": {
|
||||
"$": "embeds",
|
||||
"description": "Kako embed radi"
|
||||
"description": "How embeds work"
|
||||
},
|
||||
"introduction": {
|
||||
"$": "uvod",
|
||||
"description": "O bot-u"
|
||||
"$": "introduction",
|
||||
"description": "About the bot"
|
||||
},
|
||||
"invite": {
|
||||
"$": "pozovi",
|
||||
"description": "Kako pozvati robota"
|
||||
"$": "invite",
|
||||
"description": "How to invite the bot"
|
||||
},
|
||||
"timezones": {
|
||||
"$": "vremenske zone",
|
||||
"description": "Kako \"Blitzkrieg\" koristi podatke o tvojoj vremenskoj zoni"
|
||||
"$": "timezones",
|
||||
"description": "How BlitzKit infers and uses your timezone"
|
||||
},
|
||||
"creators": {
|
||||
"$": "stvarači",
|
||||
"description": "Ljudi koji su radili na robotu"
|
||||
"credits": {
|
||||
"$": "credits",
|
||||
"description": "People who have helped BlitzKit"
|
||||
}
|
||||
}
|
||||
},
|
||||
"breakdown": {
|
||||
"$": "analiza",
|
||||
"$": "breakdown",
|
||||
"description": "A period's breakdown by tanks played",
|
||||
"body": {
|
||||
"total": "Total",
|
||||
"battles": "Borbe",
|
||||
"battles": "Battles",
|
||||
"wn8": "WN8",
|
||||
"winrate": "Procenat pobede",
|
||||
"damage": "Šteta",
|
||||
"unknown_tank": "Ne poznat tenk %s"
|
||||
"winrate": "Winrate",
|
||||
"damage": "Damage",
|
||||
"unknown_tank": "Unknown tank %s"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"$": "odklanjanje-grešaka",
|
||||
"$": "debug",
|
||||
"description": "Debug information about the bot",
|
||||
"body": {
|
||||
"title": "# Debug\n\n",
|
||||
@@ -556,7 +574,7 @@
|
||||
}
|
||||
},
|
||||
"rating": {
|
||||
"$": "rating-session",
|
||||
"$": "rating",
|
||||
"description": "Rating player's statistics",
|
||||
"subcommands": {
|
||||
"today": {
|
||||
|
||||
@@ -0,0 +1,603 @@
|
||||
{
|
||||
"common": {
|
||||
"wn8_percentile": {
|
||||
"0": "Very bad",
|
||||
"1": "Bad",
|
||||
"2": "Below average",
|
||||
"3": "Average",
|
||||
"4": "Above average",
|
||||
"5": "Good",
|
||||
"6": "Very good",
|
||||
"7": "Great",
|
||||
"8": "Unicum",
|
||||
"9": "Super unicum"
|
||||
},
|
||||
"bkni_percentile": {
|
||||
"0": "Super malum",
|
||||
"1": "Malum",
|
||||
"2": "Very bad",
|
||||
"3": "Bad",
|
||||
"4": "Below average",
|
||||
"5": "Average",
|
||||
"6": "Above average",
|
||||
"7": "Good",
|
||||
"8": "Very good",
|
||||
"9": "Unicum",
|
||||
"10": "Super unicum"
|
||||
},
|
||||
"nations": {
|
||||
"ussr": "U.S.S.R.",
|
||||
"germany": "Germany",
|
||||
"usa": "U.S.A.",
|
||||
"china": "China",
|
||||
"uk": "U.K.",
|
||||
"france": "France",
|
||||
"japan": "Japan",
|
||||
"european": "European Nation",
|
||||
"other": "Hybrid Nation"
|
||||
},
|
||||
"nations_adjectives": {
|
||||
"ussr": "U.S.S.R.",
|
||||
"germany": "German",
|
||||
"usa": "U.S.A.",
|
||||
"china": "Chinese",
|
||||
"uk": "U.K.",
|
||||
"france": "French",
|
||||
"japan": "Japanese",
|
||||
"european": "European",
|
||||
"other": "Hybrid"
|
||||
},
|
||||
"tank_class_short": {
|
||||
"-1": "unrecognized",
|
||||
"0": "Light",
|
||||
"1": "Medium",
|
||||
"2": "Heavy",
|
||||
"3": "TD"
|
||||
},
|
||||
"tank_class_short_plural_lowercase": {
|
||||
"0": "lights",
|
||||
"1": "mediums",
|
||||
"2": "heavies",
|
||||
"3": "TDs"
|
||||
},
|
||||
"tank_class_medium": {
|
||||
"-1": "unrecognized",
|
||||
"0": "Light",
|
||||
"1": "Medium",
|
||||
"2": "Heavy",
|
||||
"3": "Tank destroyer"
|
||||
},
|
||||
"tree_type": {
|
||||
"-1": "unrecognized",
|
||||
"0": "Tech tree",
|
||||
"1": "Premium",
|
||||
"2": "Collector"
|
||||
},
|
||||
"regions": {
|
||||
"normal": {
|
||||
"com": "North America",
|
||||
"eu": "Europe",
|
||||
"asia": "Asia-Pacific"
|
||||
},
|
||||
"short": {
|
||||
"com": "NA",
|
||||
"eu": "EU",
|
||||
"asia": "APAC"
|
||||
}
|
||||
},
|
||||
"leagues": {
|
||||
"diamond": "Diamond",
|
||||
"platinum": "Platinum",
|
||||
"gold": "Gold",
|
||||
"silver": "Silver",
|
||||
"bronze": "Bronze"
|
||||
},
|
||||
"composite_stats": {
|
||||
"normalized_damage_dealt": "Damage",
|
||||
"normalized_damage_received": "Damage received",
|
||||
"normalized_deaths": "Deathrate",
|
||||
"normalized_frags": "Kills",
|
||||
"normalized_hits": "Hits",
|
||||
"normalized_losses": "Lossrate",
|
||||
"normalized_misses": "Misses",
|
||||
"normalized_shots": "Shots",
|
||||
"normalized_spotted": "Spots",
|
||||
"normalized_survived_battles": "Survival rate",
|
||||
"normalized_win_and_survived": "Winning and surviving rate",
|
||||
"normalized_wins": "Winrate",
|
||||
"normalized_xp": "XP",
|
||||
"normalized_battle_life_time": "Battle time",
|
||||
"cumulative_accuracy": "Accuracy",
|
||||
"cumulative_battles": "Battles",
|
||||
"cumulative_capture_points": "Total capture points",
|
||||
"cumulative_damage_dealt": "Total damage",
|
||||
"cumulative_damage_ratio": "Damage ratio",
|
||||
"cumulative_damage_received": "Total damage received",
|
||||
"cumulative_deaths": "Total deaths",
|
||||
"cumulative_dropped_capture_points": "Total dropped capture points",
|
||||
"cumulative_frags": "Total kills",
|
||||
"cumulative_hits": "Total hits",
|
||||
"cumulative_inaccuracy": "Inaccuracy",
|
||||
"cumulative_kills_to_death_ratio": "Kills to death ratio",
|
||||
"cumulative_losses": "Losses",
|
||||
"cumulative_misses": "Total misses",
|
||||
"cumulative_shots": "Total shots",
|
||||
"cumulative_spotted": "Total spots",
|
||||
"cumulative_survived_battles": "Survived battles",
|
||||
"cumulative_win_and_survived": "Won and survived battles",
|
||||
"cumulative_wins": "Wins",
|
||||
"cumulative_xp": "Total XP",
|
||||
"cumulative_wn8": "WN8",
|
||||
"cumulative_battle_life_time": "Total battle time"
|
||||
}
|
||||
},
|
||||
"bot": {
|
||||
"common": {
|
||||
"errors": {
|
||||
"clan_not_found": "# Could not find clan\nCouldn't find \"%s\". Try selecting a clan from the search result.",
|
||||
"player_not_found": "# Could not find user\nI couldn't find user \"%s\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.",
|
||||
"player_not_linked": "# Use `/link` to get started\nI don't know who you are but you can tell me today!",
|
||||
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"%s\".",
|
||||
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"%s\". Try typing the name of the tank instead of a number.",
|
||||
"no_tank_stats": "# No tank stats available\nThis player doesn't have any stats available. This may not be the player you are looking for.",
|
||||
"get_help": "Help Discord server",
|
||||
"uncaught_error": "# BlitzKit ran into an error!\nWe're so sorry about this. Feel free to [join the official Discord server](https://discord.gg/nDt7AjGJQH) to get help.",
|
||||
"preview": "# This command is in preview\n\nYou can only access this command in [the official Discord server](https://discord.gg/nDt7AjGJQH). The command will be generally available soon.",
|
||||
"query_too_small": "# Your search is too short\nYour search must be at least 3 characters long.",
|
||||
"query_too_long": "# Your search is too long\nYour search must be at most 100 characters long."
|
||||
},
|
||||
"wn8": {
|
||||
"$": "WN8",
|
||||
"very_bad": "Very bad",
|
||||
"bad": "Bad",
|
||||
"below_average": "Below average",
|
||||
"average": "Average",
|
||||
"above_average": "Above average",
|
||||
"good": "Good",
|
||||
"very_good": "Very good",
|
||||
"great": "Great",
|
||||
"unicum": "Unicum",
|
||||
"super_unicum": "Super unicum"
|
||||
},
|
||||
"subcommands": {
|
||||
"today": {
|
||||
"$": "today",
|
||||
"description": "Today"
|
||||
},
|
||||
"30": {
|
||||
"$": "30",
|
||||
"description": "30 days"
|
||||
},
|
||||
"60": {
|
||||
"$": "60",
|
||||
"description": "60 days"
|
||||
},
|
||||
"90": {
|
||||
"$": "90",
|
||||
"description": "90 days"
|
||||
},
|
||||
"career": {
|
||||
"$": "career",
|
||||
"description": "Career"
|
||||
},
|
||||
"custom": {
|
||||
"$": "custom",
|
||||
"description": "Custom range",
|
||||
"options": {
|
||||
"start": {
|
||||
"$": "start",
|
||||
"description": "Start in number of days ago from today"
|
||||
},
|
||||
"end": {
|
||||
"$": "end",
|
||||
"description": "End in number of days ago from today"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"clan": {
|
||||
"$": "clan",
|
||||
"description": "The clan name or tag"
|
||||
},
|
||||
"tank": {
|
||||
"$": "tank",
|
||||
"description": "The tank to get stats for"
|
||||
},
|
||||
"username": {
|
||||
"$": "username",
|
||||
"description": "The Blitz username of the player"
|
||||
},
|
||||
"filters": {
|
||||
"nation": {
|
||||
"$": "nation",
|
||||
"description": "Nation"
|
||||
},
|
||||
"class": {
|
||||
"$": "class",
|
||||
"description": "Class"
|
||||
},
|
||||
"type": {
|
||||
"$": "type",
|
||||
"description": "Type"
|
||||
}
|
||||
},
|
||||
"region": {
|
||||
"$": "region",
|
||||
"description": "Region"
|
||||
},
|
||||
"tier": {
|
||||
"$": "tier",
|
||||
"description": "Tier",
|
||||
"choices": {
|
||||
"tier": "Tier %s - %s"
|
||||
}
|
||||
}
|
||||
},
|
||||
"periods": {
|
||||
"career": "Career",
|
||||
"today": "Today",
|
||||
"days": "%s days",
|
||||
"custom": "%s - %s days"
|
||||
},
|
||||
"filters": {
|
||||
"none": "No filters",
|
||||
"tier": "Tier %s"
|
||||
},
|
||||
"no_data": {
|
||||
"battles_in_period": "No battles played in this period",
|
||||
"battles_in_season": "No battles played in this season",
|
||||
"players_in_period": "No players in this period",
|
||||
"tanks_found": "No tanks found"
|
||||
},
|
||||
"buttons": {
|
||||
"refresh": "Refresh"
|
||||
},
|
||||
"hero_stats": {
|
||||
"battles": "Battles",
|
||||
"winrate": "Winrate",
|
||||
"damage": "Damage",
|
||||
"damage_ratio": "Damage ratio",
|
||||
"tier": "Tier",
|
||||
"survival": "Survival",
|
||||
"accuracy": "Accuracy",
|
||||
"kills": "Kills"
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"auction": {
|
||||
"$": "auction",
|
||||
"description": "Tanks available right now in the auction",
|
||||
"body": {
|
||||
"title": "Blitz Auction",
|
||||
"subtitle": "%s available • next prices %s\nBid now: %s",
|
||||
"next": "Next price: %s",
|
||||
"available": "Available: %s / %s"
|
||||
},
|
||||
"errors": {
|
||||
"no_auction": "# No ongoing auction\n\nCurrently, there are no tanks available for purchase in the auction. If you think this is an error, please report this discrepancy in [the official Discord Server](https://discord.gg/nDt7AjGJQH)."
|
||||
},
|
||||
"actions": {
|
||||
"bid": "Bid now"
|
||||
}
|
||||
},
|
||||
"replay": {
|
||||
"$": "replay",
|
||||
"description": "Display replay results"
|
||||
},
|
||||
"research": {
|
||||
"$": "research",
|
||||
"description": "Lists the expenses in researching a line",
|
||||
"options": {
|
||||
"target_tank": {
|
||||
"$": "target-tank",
|
||||
"description": "The final tank to research in the line"
|
||||
},
|
||||
"starting_tank": {
|
||||
"$": "starting-tank",
|
||||
"description": "The starting tank in the line (inferred automatically if not provided)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"subtitle": "Research",
|
||||
"total": "Total",
|
||||
"research": "Research: %s",
|
||||
"upgrades": "Upgrades: %s",
|
||||
"purchase": "Purchase: %s",
|
||||
"equipment": "Equipment: %s",
|
||||
"estimation": "# This is an estimation!\n\nThis list may include already researched tanks because of the inaccurate information Wargaming provides. Try providing a `starting-tank` from the line when using the command if you can."
|
||||
},
|
||||
"errors": {
|
||||
"non_tech_tree": "# %s is not on the tech tree\n\nYou cannot research a premium/collector tank.",
|
||||
"tier_1": "# %s is a tier I\n\nTier I tanks are always unlocked for free for all players.",
|
||||
"already_researched": "# %s 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": "# %s was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
|
||||
"tanks_not_on_line": "# %s and %s are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
|
||||
"unordered": "# %s higher tiered to %s\n\nMake sure to provide %s as the `starting-tank` instead as it is the lower tiered tank."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"$": "about",
|
||||
"description": "All the info you need about the bot",
|
||||
"subcommands": {
|
||||
"commands": {
|
||||
"$": "commands",
|
||||
"description": "List of all commands and their descriptions",
|
||||
"body": "# Commands\n\nBlitzKit offers the following commands:\n\n"
|
||||
},
|
||||
"embeds": {
|
||||
"$": "embeds",
|
||||
"description": "How embeds work"
|
||||
},
|
||||
"introduction": {
|
||||
"$": "introduction",
|
||||
"description": "About the bot"
|
||||
},
|
||||
"invite": {
|
||||
"$": "invite",
|
||||
"description": "How to invite the bot"
|
||||
},
|
||||
"timezones": {
|
||||
"$": "timezones",
|
||||
"description": "How BlitzKit infers and uses your timezone"
|
||||
},
|
||||
"credits": {
|
||||
"$": "credits",
|
||||
"description": "People who have helped BlitzKit"
|
||||
}
|
||||
}
|
||||
},
|
||||
"breakdown": {
|
||||
"$": "breakdown",
|
||||
"description": "A period's breakdown by tanks played",
|
||||
"body": {
|
||||
"total": "Total",
|
||||
"battles": "Battles",
|
||||
"wn8": "WN8",
|
||||
"winrate": "Winrate",
|
||||
"damage": "Damage",
|
||||
"unknown_tank": "Unknown tank %s"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"$": "debug",
|
||||
"description": "Debug information about the bot",
|
||||
"body": {
|
||||
"title": "# Debug\n\n",
|
||||
"version": "Version",
|
||||
"shard": {
|
||||
"$": "Shard",
|
||||
"default": "default"
|
||||
},
|
||||
"uptime": "Uptime"
|
||||
}
|
||||
},
|
||||
"evolution": {
|
||||
"$": "evolution",
|
||||
"description": "Evolution of statistics",
|
||||
"groups": {
|
||||
"player": {
|
||||
"$": "player",
|
||||
"description": "Player statistics"
|
||||
},
|
||||
"tank": {
|
||||
"$": "tank",
|
||||
"description": "Tank statistics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inactive": {
|
||||
"$": "inactive",
|
||||
"description": "Lists all inactive players in a clan above a threshold",
|
||||
"options": {
|
||||
"threshold": {
|
||||
"$": "threshold",
|
||||
"description": "The number of days inactive (default: %s days)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"subtitle": "Inactive for %s+ days",
|
||||
"listing": "%s days"
|
||||
}
|
||||
},
|
||||
"full_stats": {
|
||||
"$": "full-stats",
|
||||
"description": "Full in-game statistics",
|
||||
"body": {
|
||||
"cards": {
|
||||
"games": {
|
||||
"$": "Games",
|
||||
"winrate": "Winrate",
|
||||
"battles": "Battles",
|
||||
"wins_losses": "Wins / losses"
|
||||
},
|
||||
"efficiency": {
|
||||
"$": "Efficiency",
|
||||
"wn8": "WN8",
|
||||
"survival": "Survival",
|
||||
"accuracy": "Accuracy"
|
||||
},
|
||||
"lethality": {
|
||||
"$": "Lethality",
|
||||
"average_damage": "Average damage",
|
||||
"damage_per_tier": "Damage per tier",
|
||||
"damage_ratio": "Damage ratio",
|
||||
"average_kills": "Average kills"
|
||||
},
|
||||
"miscellaneous": {
|
||||
"$": "Miscellaneous",
|
||||
"average_tier": "Average tier",
|
||||
"average_spots": "Average spots",
|
||||
"average_xp": "Average XP",
|
||||
"kills_to_death_ratio": "Kills to death ratio"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"link": {
|
||||
"$": "link",
|
||||
"description": "Links your Blitz and Discord account",
|
||||
"body": {
|
||||
"accounts_linked": "Accounts linked",
|
||||
"no_role_permissions": {
|
||||
"$": "%s failed to verify",
|
||||
"description": "I don't have the permission to manage roles."
|
||||
}
|
||||
},
|
||||
"embed": {
|
||||
"title": "Incorrect account?",
|
||||
"description": "Use the command again and select a username from the search results."
|
||||
}
|
||||
},
|
||||
"search_clans": {
|
||||
"$": "search-clans",
|
||||
"description": "Search clans",
|
||||
"options": {
|
||||
"limit": {
|
||||
"$": "limit",
|
||||
"description": "The max number of clan results (default: %s)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"title": "Clan search for %s in region %s",
|
||||
"no_results": "No clans found"
|
||||
}
|
||||
},
|
||||
"search_players": {
|
||||
"$": "search-players",
|
||||
"description": "Search players",
|
||||
"options": {
|
||||
"limit": {
|
||||
"$": "limit",
|
||||
"description": "The max number of player results (default: %s)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"title": "Player search for %s in region %s",
|
||||
"no_results": "No players found"
|
||||
}
|
||||
},
|
||||
"rating_leaderboard": {
|
||||
"$": "rating-leaderboard",
|
||||
"description": "Rating battles statistics",
|
||||
"subcommands": {
|
||||
"neighbors": {
|
||||
"$": "neighbors",
|
||||
"description": "Neighboring players on the leaderboard"
|
||||
},
|
||||
"league": {
|
||||
"$": "league",
|
||||
"description": "Top players of a league"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"limit": {
|
||||
"$": "limit",
|
||||
"description": "How many neighbors to display (default: %s)"
|
||||
},
|
||||
"season": {
|
||||
"$": "season",
|
||||
"description": "Season number (default: current)",
|
||||
"choices": {
|
||||
"current": "%s (current)"
|
||||
}
|
||||
},
|
||||
"league": {
|
||||
"$": "league",
|
||||
"description": "The league to display"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"top_players": "Top %s players",
|
||||
"no_participation": "# %s didn't play rating in season %s\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": "Deleted %s",
|
||||
"neighbors": "Rating neighbors",
|
||||
"more": "+ %s more"
|
||||
}
|
||||
},
|
||||
"today": {
|
||||
"$": "today",
|
||||
"description": "Today's played tanks and statistics"
|
||||
},
|
||||
"stats": {
|
||||
"$": "stats",
|
||||
"description": "Quick statistics for a period"
|
||||
},
|
||||
"owned_tanks": {
|
||||
"$": "owned-tanks",
|
||||
"description": "Shows all own tanks in a tier",
|
||||
"body": {
|
||||
"title": "# %s's owned tier %s tanks\n\n"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"$": "permissions",
|
||||
"description": "Checks wether the bot has the required permissions to function",
|
||||
"body": {
|
||||
"title": "# Permissions\n\n%s",
|
||||
"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": {
|
||||
"$": "ping",
|
||||
"description": "Check wether the bot is alive.",
|
||||
"body": {
|
||||
"pong": "Pong 🏓",
|
||||
"pong_with_time": "Pong 🏓 - %sms"
|
||||
},
|
||||
"subcommands": {
|
||||
"blitzkit": {
|
||||
"$": "blitzkit",
|
||||
"description": "Ping BlitzKit"
|
||||
},
|
||||
"wotb": {
|
||||
"$": "wotb",
|
||||
"description": "Ping World of Tanks Blitz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"player_info": {
|
||||
"$": "player-info",
|
||||
"description": "Basic information about a player",
|
||||
"body": {
|
||||
"title": "%s's information",
|
||||
"nickname": "Nickname",
|
||||
"battles": "Battles",
|
||||
"winrate": "Winrate",
|
||||
"account_id": "Account ID",
|
||||
"created": "Created",
|
||||
"last_battle": "Last battle"
|
||||
}
|
||||
},
|
||||
"rating": {
|
||||
"$": "rating",
|
||||
"description": "Rating player's statistics",
|
||||
"subcommands": {
|
||||
"today": {
|
||||
"$": "today",
|
||||
"description": "Today's rating statistics"
|
||||
},
|
||||
"season": {
|
||||
"$": "season",
|
||||
"description": "This season's rating statistics"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"subtitle": {
|
||||
"today": "Today's rating statistics",
|
||||
"season": "This season's rating statistics"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"no_participation": "# %s 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": {}
|
||||
}
|
||||
@@ -0,0 +1,603 @@
|
||||
{
|
||||
"common": {
|
||||
"wn8_percentile": {
|
||||
"0": "Very bad",
|
||||
"1": "Bad",
|
||||
"2": "Below average",
|
||||
"3": "Average",
|
||||
"4": "Above average",
|
||||
"5": "Good",
|
||||
"6": "Very good",
|
||||
"7": "Great",
|
||||
"8": "Unicum",
|
||||
"9": "Super unicum"
|
||||
},
|
||||
"bkni_percentile": {
|
||||
"0": "Super malum",
|
||||
"1": "Malum",
|
||||
"2": "Very bad",
|
||||
"3": "Bad",
|
||||
"4": "Below average",
|
||||
"5": "Average",
|
||||
"6": "Above average",
|
||||
"7": "Good",
|
||||
"8": "Very good",
|
||||
"9": "Unicum",
|
||||
"10": "Super unicum"
|
||||
},
|
||||
"nations": {
|
||||
"ussr": "U.S.S.R.",
|
||||
"germany": "Germany",
|
||||
"usa": "U.S.A.",
|
||||
"china": "China",
|
||||
"uk": "U.K.",
|
||||
"france": "France",
|
||||
"japan": "Japan",
|
||||
"european": "European Nation",
|
||||
"other": "Hybrid Nation"
|
||||
},
|
||||
"nations_adjectives": {
|
||||
"ussr": "U.S.S.R.",
|
||||
"germany": "German",
|
||||
"usa": "U.S.A.",
|
||||
"china": "Chinese",
|
||||
"uk": "U.K.",
|
||||
"france": "French",
|
||||
"japan": "Japanese",
|
||||
"european": "European",
|
||||
"other": "Hybrid"
|
||||
},
|
||||
"tank_class_short": {
|
||||
"-1": "unrecognized",
|
||||
"0": "Light",
|
||||
"1": "Medium",
|
||||
"2": "Heavy",
|
||||
"3": "TD"
|
||||
},
|
||||
"tank_class_short_plural_lowercase": {
|
||||
"0": "lights",
|
||||
"1": "mediums",
|
||||
"2": "heavies",
|
||||
"3": "TDs"
|
||||
},
|
||||
"tank_class_medium": {
|
||||
"-1": "unrecognized",
|
||||
"0": "Light",
|
||||
"1": "Medium",
|
||||
"2": "Heavy",
|
||||
"3": "Tank destroyer"
|
||||
},
|
||||
"tree_type": {
|
||||
"-1": "unrecognized",
|
||||
"0": "Tech tree",
|
||||
"1": "Premium",
|
||||
"2": "Collector"
|
||||
},
|
||||
"regions": {
|
||||
"normal": {
|
||||
"com": "North America",
|
||||
"eu": "Europe",
|
||||
"asia": "Asia-Pacific"
|
||||
},
|
||||
"short": {
|
||||
"com": "NA",
|
||||
"eu": "EU",
|
||||
"asia": "APAC"
|
||||
}
|
||||
},
|
||||
"leagues": {
|
||||
"diamond": "Diamond",
|
||||
"platinum": "Platinum",
|
||||
"gold": "Gold",
|
||||
"silver": "Silver",
|
||||
"bronze": "Bronze"
|
||||
},
|
||||
"composite_stats": {
|
||||
"normalized_damage_dealt": "Damage",
|
||||
"normalized_damage_received": "Damage received",
|
||||
"normalized_deaths": "Deathrate",
|
||||
"normalized_frags": "Kills",
|
||||
"normalized_hits": "Hits",
|
||||
"normalized_losses": "Lossrate",
|
||||
"normalized_misses": "Misses",
|
||||
"normalized_shots": "Shots",
|
||||
"normalized_spotted": "Spots",
|
||||
"normalized_survived_battles": "Survival rate",
|
||||
"normalized_win_and_survived": "Winning and surviving rate",
|
||||
"normalized_wins": "Winrate",
|
||||
"normalized_xp": "XP",
|
||||
"normalized_battle_life_time": "Battle time",
|
||||
"cumulative_accuracy": "Accuracy",
|
||||
"cumulative_battles": "Battles",
|
||||
"cumulative_capture_points": "Total capture points",
|
||||
"cumulative_damage_dealt": "Total damage",
|
||||
"cumulative_damage_ratio": "Damage ratio",
|
||||
"cumulative_damage_received": "Total damage received",
|
||||
"cumulative_deaths": "Total deaths",
|
||||
"cumulative_dropped_capture_points": "Total dropped capture points",
|
||||
"cumulative_frags": "Total kills",
|
||||
"cumulative_hits": "Total hits",
|
||||
"cumulative_inaccuracy": "Inaccuracy",
|
||||
"cumulative_kills_to_death_ratio": "Kills to death ratio",
|
||||
"cumulative_losses": "Losses",
|
||||
"cumulative_misses": "Total misses",
|
||||
"cumulative_shots": "Total shots",
|
||||
"cumulative_spotted": "Total spots",
|
||||
"cumulative_survived_battles": "Survived battles",
|
||||
"cumulative_win_and_survived": "Won and survived battles",
|
||||
"cumulative_wins": "Wins",
|
||||
"cumulative_xp": "Total XP",
|
||||
"cumulative_wn8": "WN8",
|
||||
"cumulative_battle_life_time": "Total battle time"
|
||||
}
|
||||
},
|
||||
"bot": {
|
||||
"common": {
|
||||
"errors": {
|
||||
"clan_not_found": "# Could not find clan\nCouldn't find \"%s\". Try selecting a clan from the search result.",
|
||||
"player_not_found": "# Could not find user\nI couldn't find user \"%s\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.",
|
||||
"player_not_linked": "# Use `/link` to get started\nI don't know who you are but you can tell me today!",
|
||||
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"%s\".",
|
||||
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"%s\". Try typing the name of the tank instead of a number.",
|
||||
"no_tank_stats": "# No tank stats available\nThis player doesn't have any stats available. This may not be the player you are looking for.",
|
||||
"get_help": "Help Discord server",
|
||||
"uncaught_error": "# BlitzKit ran into an error!\nWe're so sorry about this. Feel free to [join the official Discord server](https://discord.gg/nDt7AjGJQH) to get help.",
|
||||
"preview": "# This command is in preview\n\nYou can only access this command in [the official Discord server](https://discord.gg/nDt7AjGJQH). The command will be generally available soon.",
|
||||
"query_too_small": "# Your search is too short\nYour search must be at least 3 characters long.",
|
||||
"query_too_long": "# Your search is too long\nYour search must be at most 100 characters long."
|
||||
},
|
||||
"wn8": {
|
||||
"$": "WN8",
|
||||
"very_bad": "Very bad",
|
||||
"bad": "Bad",
|
||||
"below_average": "Below average",
|
||||
"average": "Average",
|
||||
"above_average": "Above average",
|
||||
"good": "Good",
|
||||
"very_good": "Very good",
|
||||
"great": "Great",
|
||||
"unicum": "Unicum",
|
||||
"super_unicum": "Super unicum"
|
||||
},
|
||||
"subcommands": {
|
||||
"today": {
|
||||
"$": "today",
|
||||
"description": "Today"
|
||||
},
|
||||
"30": {
|
||||
"$": "30",
|
||||
"description": "30 days"
|
||||
},
|
||||
"60": {
|
||||
"$": "60",
|
||||
"description": "60 days"
|
||||
},
|
||||
"90": {
|
||||
"$": "90",
|
||||
"description": "90 days"
|
||||
},
|
||||
"career": {
|
||||
"$": "career",
|
||||
"description": "Career"
|
||||
},
|
||||
"custom": {
|
||||
"$": "custom",
|
||||
"description": "Custom range",
|
||||
"options": {
|
||||
"start": {
|
||||
"$": "start",
|
||||
"description": "Start in number of days ago from today"
|
||||
},
|
||||
"end": {
|
||||
"$": "end",
|
||||
"description": "End in number of days ago from today"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"clan": {
|
||||
"$": "clan",
|
||||
"description": "The clan name or tag"
|
||||
},
|
||||
"tank": {
|
||||
"$": "tank",
|
||||
"description": "The tank to get stats for"
|
||||
},
|
||||
"username": {
|
||||
"$": "username",
|
||||
"description": "The Blitz username of the player"
|
||||
},
|
||||
"filters": {
|
||||
"nation": {
|
||||
"$": "nation",
|
||||
"description": "Nation"
|
||||
},
|
||||
"class": {
|
||||
"$": "class",
|
||||
"description": "Class"
|
||||
},
|
||||
"type": {
|
||||
"$": "type",
|
||||
"description": "Type"
|
||||
}
|
||||
},
|
||||
"region": {
|
||||
"$": "region",
|
||||
"description": "Region"
|
||||
},
|
||||
"tier": {
|
||||
"$": "tier",
|
||||
"description": "Tier",
|
||||
"choices": {
|
||||
"tier": "Tier %s - %s"
|
||||
}
|
||||
}
|
||||
},
|
||||
"periods": {
|
||||
"career": "Career",
|
||||
"today": "Today",
|
||||
"days": "%s days",
|
||||
"custom": "%s - %s days"
|
||||
},
|
||||
"filters": {
|
||||
"none": "No filters",
|
||||
"tier": "Tier %s"
|
||||
},
|
||||
"no_data": {
|
||||
"battles_in_period": "No battles played in this period",
|
||||
"battles_in_season": "No battles played in this season",
|
||||
"players_in_period": "No players in this period",
|
||||
"tanks_found": "No tanks found"
|
||||
},
|
||||
"buttons": {
|
||||
"refresh": "Refresh"
|
||||
},
|
||||
"hero_stats": {
|
||||
"battles": "Battles",
|
||||
"winrate": "Winrate",
|
||||
"damage": "Damage",
|
||||
"damage_ratio": "Damage ratio",
|
||||
"tier": "Tier",
|
||||
"survival": "Survival",
|
||||
"accuracy": "Accuracy",
|
||||
"kills": "Kills"
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"auction": {
|
||||
"$": "auction",
|
||||
"description": "Tanks available right now in the auction",
|
||||
"body": {
|
||||
"title": "Blitz Auction",
|
||||
"subtitle": "%s available • next prices %s\nBid now: %s",
|
||||
"next": "Next price: %s",
|
||||
"available": "Available: %s / %s"
|
||||
},
|
||||
"errors": {
|
||||
"no_auction": "# No ongoing auction\n\nCurrently, there are no tanks available for purchase in the auction. If you think this is an error, please report this discrepancy in [the official Discord Server](https://discord.gg/nDt7AjGJQH)."
|
||||
},
|
||||
"actions": {
|
||||
"bid": "Bid now"
|
||||
}
|
||||
},
|
||||
"replay": {
|
||||
"$": "replay",
|
||||
"description": "Display replay results"
|
||||
},
|
||||
"research": {
|
||||
"$": "research",
|
||||
"description": "Lists the expenses in researching a line",
|
||||
"options": {
|
||||
"target_tank": {
|
||||
"$": "target-tank",
|
||||
"description": "The final tank to research in the line"
|
||||
},
|
||||
"starting_tank": {
|
||||
"$": "starting-tank",
|
||||
"description": "The starting tank in the line (inferred automatically if not provided)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"subtitle": "Research",
|
||||
"total": "Total",
|
||||
"research": "Research: %s",
|
||||
"upgrades": "Upgrades: %s",
|
||||
"purchase": "Purchase: %s",
|
||||
"equipment": "Equipment: %s",
|
||||
"estimation": "# This is an estimation!\n\nThis list may include already researched tanks because of the inaccurate information Wargaming provides. Try providing a `starting-tank` from the line when using the command if you can."
|
||||
},
|
||||
"errors": {
|
||||
"non_tech_tree": "# %s is not on the tech tree\n\nYou cannot research a premium/collector tank.",
|
||||
"tier_1": "# %s is a tier I\n\nTier I tanks are always unlocked for free for all players.",
|
||||
"already_researched": "# %s 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": "# %s was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
|
||||
"tanks_not_on_line": "# %s and %s are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
|
||||
"unordered": "# %s higher tiered to %s\n\nMake sure to provide %s as the `starting-tank` instead as it is the lower tiered tank."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"$": "about",
|
||||
"description": "All the info you need about the bot",
|
||||
"subcommands": {
|
||||
"commands": {
|
||||
"$": "commands",
|
||||
"description": "List of all commands and their descriptions",
|
||||
"body": "# Commands\n\nBlitzKit offers the following commands:\n\n"
|
||||
},
|
||||
"embeds": {
|
||||
"$": "embeds",
|
||||
"description": "How embeds work"
|
||||
},
|
||||
"introduction": {
|
||||
"$": "introduction",
|
||||
"description": "About the bot"
|
||||
},
|
||||
"invite": {
|
||||
"$": "invite",
|
||||
"description": "How to invite the bot"
|
||||
},
|
||||
"timezones": {
|
||||
"$": "timezones",
|
||||
"description": "How BlitzKit infers and uses your timezone"
|
||||
},
|
||||
"credits": {
|
||||
"$": "credits",
|
||||
"description": "People who have helped BlitzKit"
|
||||
}
|
||||
}
|
||||
},
|
||||
"breakdown": {
|
||||
"$": "breakdown",
|
||||
"description": "A period's breakdown by tanks played",
|
||||
"body": {
|
||||
"total": "Total",
|
||||
"battles": "Battles",
|
||||
"wn8": "WN8",
|
||||
"winrate": "Winrate",
|
||||
"damage": "Damage",
|
||||
"unknown_tank": "Unknown tank %s"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"$": "debug",
|
||||
"description": "Debug information about the bot",
|
||||
"body": {
|
||||
"title": "# Debug\n\n",
|
||||
"version": "Version",
|
||||
"shard": {
|
||||
"$": "Shard",
|
||||
"default": "default"
|
||||
},
|
||||
"uptime": "Uptime"
|
||||
}
|
||||
},
|
||||
"evolution": {
|
||||
"$": "evolution",
|
||||
"description": "Evolution of statistics",
|
||||
"groups": {
|
||||
"player": {
|
||||
"$": "player",
|
||||
"description": "Player statistics"
|
||||
},
|
||||
"tank": {
|
||||
"$": "tank",
|
||||
"description": "Tank statistics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inactive": {
|
||||
"$": "inactive",
|
||||
"description": "Lists all inactive players in a clan above a threshold",
|
||||
"options": {
|
||||
"threshold": {
|
||||
"$": "threshold",
|
||||
"description": "The number of days inactive (default: %s days)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"subtitle": "Inactive for %s+ days",
|
||||
"listing": "%s days"
|
||||
}
|
||||
},
|
||||
"full_stats": {
|
||||
"$": "full-stats",
|
||||
"description": "Full in-game statistics",
|
||||
"body": {
|
||||
"cards": {
|
||||
"games": {
|
||||
"$": "Games",
|
||||
"winrate": "Winrate",
|
||||
"battles": "Battles",
|
||||
"wins_losses": "Wins / losses"
|
||||
},
|
||||
"efficiency": {
|
||||
"$": "Efficiency",
|
||||
"wn8": "WN8",
|
||||
"survival": "Survival",
|
||||
"accuracy": "Accuracy"
|
||||
},
|
||||
"lethality": {
|
||||
"$": "Lethality",
|
||||
"average_damage": "Average damage",
|
||||
"damage_per_tier": "Damage per tier",
|
||||
"damage_ratio": "Damage ratio",
|
||||
"average_kills": "Average kills"
|
||||
},
|
||||
"miscellaneous": {
|
||||
"$": "Miscellaneous",
|
||||
"average_tier": "Average tier",
|
||||
"average_spots": "Average spots",
|
||||
"average_xp": "Average XP",
|
||||
"kills_to_death_ratio": "Kills to death ratio"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"link": {
|
||||
"$": "link",
|
||||
"description": "Links your Blitz and Discord account",
|
||||
"body": {
|
||||
"accounts_linked": "Accounts linked",
|
||||
"no_role_permissions": {
|
||||
"$": "%s failed to verify",
|
||||
"description": "I don't have the permission to manage roles."
|
||||
}
|
||||
},
|
||||
"embed": {
|
||||
"title": "Incorrect account?",
|
||||
"description": "Use the command again and select a username from the search results."
|
||||
}
|
||||
},
|
||||
"search_clans": {
|
||||
"$": "search-clans",
|
||||
"description": "Search clans",
|
||||
"options": {
|
||||
"limit": {
|
||||
"$": "limit",
|
||||
"description": "The max number of clan results (default: %s)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"title": "Clan search for %s in region %s",
|
||||
"no_results": "No clans found"
|
||||
}
|
||||
},
|
||||
"search_players": {
|
||||
"$": "search-players",
|
||||
"description": "Search players",
|
||||
"options": {
|
||||
"limit": {
|
||||
"$": "limit",
|
||||
"description": "The max number of player results (default: %s)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"title": "Player search for %s in region %s",
|
||||
"no_results": "No players found"
|
||||
}
|
||||
},
|
||||
"rating_leaderboard": {
|
||||
"$": "rating-leaderboard",
|
||||
"description": "Rating battles statistics",
|
||||
"subcommands": {
|
||||
"neighbors": {
|
||||
"$": "neighbors",
|
||||
"description": "Neighboring players on the leaderboard"
|
||||
},
|
||||
"league": {
|
||||
"$": "league",
|
||||
"description": "Top players of a league"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"limit": {
|
||||
"$": "limit",
|
||||
"description": "How many neighbors to display (default: %s)"
|
||||
},
|
||||
"season": {
|
||||
"$": "season",
|
||||
"description": "Season number (default: current)",
|
||||
"choices": {
|
||||
"current": "%s (current)"
|
||||
}
|
||||
},
|
||||
"league": {
|
||||
"$": "league",
|
||||
"description": "The league to display"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"top_players": "Top %s players",
|
||||
"no_participation": "# %s didn't play rating in season %s\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": "Deleted %s",
|
||||
"neighbors": "Rating neighbors",
|
||||
"more": "+ %s more"
|
||||
}
|
||||
},
|
||||
"today": {
|
||||
"$": "today",
|
||||
"description": "Today's played tanks and statistics"
|
||||
},
|
||||
"stats": {
|
||||
"$": "stats",
|
||||
"description": "Quick statistics for a period"
|
||||
},
|
||||
"owned_tanks": {
|
||||
"$": "owned-tanks",
|
||||
"description": "Shows all own tanks in a tier",
|
||||
"body": {
|
||||
"title": "# %s's owned tier %s tanks\n\n"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"$": "permissions",
|
||||
"description": "Checks wether the bot has the required permissions to function",
|
||||
"body": {
|
||||
"title": "# Permissions\n\n%s",
|
||||
"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": {
|
||||
"$": "ping",
|
||||
"description": "Check wether the bot is alive.",
|
||||
"body": {
|
||||
"pong": "Pong 🏓",
|
||||
"pong_with_time": "Pong 🏓 - %sms"
|
||||
},
|
||||
"subcommands": {
|
||||
"blitzkit": {
|
||||
"$": "blitzkit",
|
||||
"description": "Ping BlitzKit"
|
||||
},
|
||||
"wotb": {
|
||||
"$": "wotb",
|
||||
"description": "Ping World of Tanks Blitz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"player_info": {
|
||||
"$": "player-info",
|
||||
"description": "Basic information about a player",
|
||||
"body": {
|
||||
"title": "%s's information",
|
||||
"nickname": "Nickname",
|
||||
"battles": "Battles",
|
||||
"winrate": "Winrate",
|
||||
"account_id": "Account ID",
|
||||
"created": "Created",
|
||||
"last_battle": "Last battle"
|
||||
}
|
||||
},
|
||||
"rating": {
|
||||
"$": "rating",
|
||||
"description": "Rating player's statistics",
|
||||
"subcommands": {
|
||||
"today": {
|
||||
"$": "today",
|
||||
"description": "Today's rating statistics"
|
||||
},
|
||||
"season": {
|
||||
"$": "season",
|
||||
"description": "This season's rating statistics"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"subtitle": {
|
||||
"today": "Today's rating statistics",
|
||||
"season": "This season's rating statistics"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"no_participation": "# %s 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": {}
|
||||
}
|
||||
@@ -0,0 +1,603 @@
|
||||
{
|
||||
"common": {
|
||||
"wn8_percentile": {
|
||||
"0": "非常差",
|
||||
"1": "差",
|
||||
"2": "较差",
|
||||
"3": "平均",
|
||||
"4": "高于平均",
|
||||
"5": "好",
|
||||
"6": "非常好",
|
||||
"7": "很好",
|
||||
"8": "Unicum",
|
||||
"9": "Super unicum"
|
||||
},
|
||||
"bkni_percentile": {
|
||||
"0": "Super malum",
|
||||
"1": "Malum",
|
||||
"2": "Very bad",
|
||||
"3": "差",
|
||||
"4": "较差",
|
||||
"5": "平均",
|
||||
"6": "高于平均",
|
||||
"7": "好",
|
||||
"8": "非常好",
|
||||
"9": "Unicum",
|
||||
"10": "Super unicum"
|
||||
},
|
||||
"nations": {
|
||||
"ussr": "苏联 (S系)",
|
||||
"germany": "德国 (D系)",
|
||||
"usa": "美国 (M系)",
|
||||
"china": "中国 (C系)",
|
||||
"uk": "英国 (Y系)",
|
||||
"france": "法国 (F系)",
|
||||
"japan": "日本 (R系)",
|
||||
"european": "欧盟 (E系)",
|
||||
"other": "X系"
|
||||
},
|
||||
"nations_adjectives": {
|
||||
"ussr": "苏联 (S系)",
|
||||
"germany": "德国 (D系)",
|
||||
"usa": "美国 (M系)",
|
||||
"china": "中国 (C系)",
|
||||
"uk": "英国 (Y系)",
|
||||
"france": "法国 (F系)",
|
||||
"japan": "日本 (R系)",
|
||||
"european": "欧盟 (E系)",
|
||||
"other": "X系"
|
||||
},
|
||||
"tank_class_short": {
|
||||
"-1": "无法识别",
|
||||
"0": "轻型坦克",
|
||||
"1": "中型坦克",
|
||||
"2": "重型坦克",
|
||||
"3": "坦克歼击车"
|
||||
},
|
||||
"tank_class_short_plural_lowercase": {
|
||||
"0": "轻型坦克",
|
||||
"1": "中型坦克",
|
||||
"2": "重型坦克",
|
||||
"3": "坦克歼击车"
|
||||
},
|
||||
"tank_class_medium": {
|
||||
"-1": "无法识别",
|
||||
"0": "轻型坦克",
|
||||
"1": "Medium",
|
||||
"2": "Heavy",
|
||||
"3": "Tank destroyer"
|
||||
},
|
||||
"tree_type": {
|
||||
"-1": "unrecognized",
|
||||
"0": "Tech tree",
|
||||
"1": "Premium",
|
||||
"2": "Collector"
|
||||
},
|
||||
"regions": {
|
||||
"normal": {
|
||||
"com": "North America",
|
||||
"eu": "Europe",
|
||||
"asia": "Asia-Pacific"
|
||||
},
|
||||
"short": {
|
||||
"com": "NA",
|
||||
"eu": "EU",
|
||||
"asia": "APAC"
|
||||
}
|
||||
},
|
||||
"leagues": {
|
||||
"diamond": "Diamond",
|
||||
"platinum": "Platinum",
|
||||
"gold": "Gold",
|
||||
"silver": "Silver",
|
||||
"bronze": "Bronze"
|
||||
},
|
||||
"composite_stats": {
|
||||
"normalized_damage_dealt": "Damage",
|
||||
"normalized_damage_received": "Damage received",
|
||||
"normalized_deaths": "Deathrate",
|
||||
"normalized_frags": "Kills",
|
||||
"normalized_hits": "Hits",
|
||||
"normalized_losses": "Lossrate",
|
||||
"normalized_misses": "Misses",
|
||||
"normalized_shots": "Shots",
|
||||
"normalized_spotted": "Spots",
|
||||
"normalized_survived_battles": "Survival rate",
|
||||
"normalized_win_and_survived": "Winning and surviving rate",
|
||||
"normalized_wins": "Winrate",
|
||||
"normalized_xp": "XP",
|
||||
"normalized_battle_life_time": "Battle time",
|
||||
"cumulative_accuracy": "Accuracy",
|
||||
"cumulative_battles": "Battles",
|
||||
"cumulative_capture_points": "Total capture points",
|
||||
"cumulative_damage_dealt": "Total damage",
|
||||
"cumulative_damage_ratio": "Damage ratio",
|
||||
"cumulative_damage_received": "Total damage received",
|
||||
"cumulative_deaths": "Total deaths",
|
||||
"cumulative_dropped_capture_points": "Total dropped capture points",
|
||||
"cumulative_frags": "Total kills",
|
||||
"cumulative_hits": "Total hits",
|
||||
"cumulative_inaccuracy": "Inaccuracy",
|
||||
"cumulative_kills_to_death_ratio": "战损比",
|
||||
"cumulative_losses": "失败",
|
||||
"cumulative_misses": "总射击失误数",
|
||||
"cumulative_shots": "总射击数",
|
||||
"cumulative_spotted": "场均发现敌人",
|
||||
"cumulative_survived_battles": "幸存场次",
|
||||
"cumulative_win_and_survived": "胜利和幸存场次",
|
||||
"cumulative_wins": "胜利",
|
||||
"cumulative_xp": "总经验",
|
||||
"cumulative_wn8": "WN8",
|
||||
"cumulative_battle_life_time": "总战斗时间"
|
||||
}
|
||||
},
|
||||
"bot": {
|
||||
"common": {
|
||||
"errors": {
|
||||
"clan_not_found": "# 找不到军团\n找不到\"%s\"。请尝试从搜索结果中选择一个军团。",
|
||||
"player_not_found": "# 找不到用户\n无法找到用户 \"%s\"。 尝试从搜索结果中挑选用户,输入一个有效的用户名或使用 \"/link\" 命令。",
|
||||
"player_not_linked": "# 使用 `/link` 来开始\n我不知道你是谁,但你今天可以告诉我!",
|
||||
"tank_name_not_found": "# 坦克找不到\n无法找到名为 \"%s \"的坦克。",
|
||||
"tank_id_not_found": "# 坦克找不到\n无法找到ID \"%s\"的坦克。 试着输入坦克的名称而不是数字。",
|
||||
"no_tank_stats": "# 没有这个坦克的数据\n这个玩家没有数据。你可能找错人了。",
|
||||
"get_help": "Help Discord server",
|
||||
"uncaught_error": "# BlitzKit 崩溃了!\n非常抱歉。请加入[官方Discord](https://discord.gg/nDt7AjGJQH),以方便帮助你的问题。",
|
||||
"preview": "# 此命令处于预览\n\n您只能在[官方Discord服务器](https://discord.gg/nDt7AjGJQH)访问此命令。该命令将很快可用。",
|
||||
"query_too_small": "# 您的搜索太短了\n您的搜索必须至少 3 个字符长。",
|
||||
"query_too_long": "# 您的搜索太长了\n您的搜索必须最长为 100 个字符。"
|
||||
},
|
||||
"wn8": {
|
||||
"$": "WN8",
|
||||
"very_bad": "非常差",
|
||||
"bad": "差",
|
||||
"below_average": "较差",
|
||||
"average": "平均",
|
||||
"above_average": "高于平均",
|
||||
"good": "好",
|
||||
"very_good": "非常好",
|
||||
"great": "很好",
|
||||
"unicum": "Unicum",
|
||||
"super_unicum": "Super unicum"
|
||||
},
|
||||
"subcommands": {
|
||||
"today": {
|
||||
"$": "今日:",
|
||||
"description": "今日:"
|
||||
},
|
||||
"30": {
|
||||
"$": "30",
|
||||
"description": "30天"
|
||||
},
|
||||
"60": {
|
||||
"$": "60",
|
||||
"description": "60天"
|
||||
},
|
||||
"90": {
|
||||
"$": "90",
|
||||
"description": "90天"
|
||||
},
|
||||
"career": {
|
||||
"$": "玩家资料",
|
||||
"description": "玩家资料"
|
||||
},
|
||||
"custom": {
|
||||
"$": "自定义",
|
||||
"description": "自定义范围",
|
||||
"options": {
|
||||
"start": {
|
||||
"$": "开始时间",
|
||||
"description": "从今天开始于数天前"
|
||||
},
|
||||
"end": {
|
||||
"$": "结束时间",
|
||||
"description": "从今天开始于数天前"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"clan": {
|
||||
"$": "军团",
|
||||
"description": "The clan name or tag"
|
||||
},
|
||||
"tank": {
|
||||
"$": "tank",
|
||||
"description": "The tank to get stats for"
|
||||
},
|
||||
"username": {
|
||||
"$": "username",
|
||||
"description": "The Blitz username of the player"
|
||||
},
|
||||
"filters": {
|
||||
"nation": {
|
||||
"$": "nation",
|
||||
"description": "Nation"
|
||||
},
|
||||
"class": {
|
||||
"$": "class",
|
||||
"description": "Class"
|
||||
},
|
||||
"type": {
|
||||
"$": "type",
|
||||
"description": "Type"
|
||||
}
|
||||
},
|
||||
"region": {
|
||||
"$": "region",
|
||||
"description": "Region"
|
||||
},
|
||||
"tier": {
|
||||
"$": "tier",
|
||||
"description": "Tier",
|
||||
"choices": {
|
||||
"tier": "Tier %s - %s"
|
||||
}
|
||||
}
|
||||
},
|
||||
"periods": {
|
||||
"career": "Career",
|
||||
"today": "Today",
|
||||
"days": "%s days",
|
||||
"custom": "%s - %s days"
|
||||
},
|
||||
"filters": {
|
||||
"none": "No filters",
|
||||
"tier": "Tier %s"
|
||||
},
|
||||
"no_data": {
|
||||
"battles_in_period": "No battles played in this period",
|
||||
"battles_in_season": "No battles played in this season",
|
||||
"players_in_period": "No players in this period",
|
||||
"tanks_found": "No tanks found"
|
||||
},
|
||||
"buttons": {
|
||||
"refresh": "Refresh"
|
||||
},
|
||||
"hero_stats": {
|
||||
"battles": "Battles",
|
||||
"winrate": "Winrate",
|
||||
"damage": "Damage",
|
||||
"damage_ratio": "Damage ratio",
|
||||
"tier": "Tier",
|
||||
"survival": "Survival",
|
||||
"accuracy": "Accuracy",
|
||||
"kills": "Kills"
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"auction": {
|
||||
"$": "auction",
|
||||
"description": "Tanks available right now in the auction",
|
||||
"body": {
|
||||
"title": "Blitz Auction",
|
||||
"subtitle": "%s available • next prices %s\nBid now: %s",
|
||||
"next": "Next price: %s",
|
||||
"available": "Available: %s / %s"
|
||||
},
|
||||
"errors": {
|
||||
"no_auction": "# No ongoing auction\n\nCurrently, there are no tanks available for purchase in the auction. If you think this is an error, please report this discrepancy in [the official Discord Server](https://discord.gg/nDt7AjGJQH)."
|
||||
},
|
||||
"actions": {
|
||||
"bid": "Bid now"
|
||||
}
|
||||
},
|
||||
"replay": {
|
||||
"$": "replay",
|
||||
"description": "Display replay results"
|
||||
},
|
||||
"research": {
|
||||
"$": "research",
|
||||
"description": "Lists the expenses in researching a line",
|
||||
"options": {
|
||||
"target_tank": {
|
||||
"$": "target-tank",
|
||||
"description": "The final tank to research in the line"
|
||||
},
|
||||
"starting_tank": {
|
||||
"$": "starting-tank",
|
||||
"description": "The starting tank in the line (inferred automatically if not provided)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"subtitle": "Research",
|
||||
"total": "Total",
|
||||
"research": "Research: %s",
|
||||
"upgrades": "Upgrades: %s",
|
||||
"purchase": "Purchase: %s",
|
||||
"equipment": "Equipment: %s",
|
||||
"estimation": "# This is an estimation!\n\nThis list may include already researched tanks because of the inaccurate information Wargaming provides. Try providing a `starting-tank` from the line when using the command if you can."
|
||||
},
|
||||
"errors": {
|
||||
"non_tech_tree": "# %s is not on the tech tree\n\nYou cannot research a premium/collector tank.",
|
||||
"tier_1": "# %s is a tier I\n\nTier I tanks are always unlocked for free for all players.",
|
||||
"already_researched": "# %s 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": "# %s was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
|
||||
"tanks_not_on_line": "# %s and %s are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
|
||||
"unordered": "# %s higher tiered to %s\n\nMake sure to provide %s as the `starting-tank` instead as it is the lower tiered tank."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"$": "about",
|
||||
"description": "All the info you need about the bot",
|
||||
"subcommands": {
|
||||
"commands": {
|
||||
"$": "commands",
|
||||
"description": "List of all commands and their descriptions",
|
||||
"body": "# Commands\n\nBlitzKit offers the following commands:\n\n"
|
||||
},
|
||||
"embeds": {
|
||||
"$": "embeds",
|
||||
"description": "How embeds work"
|
||||
},
|
||||
"introduction": {
|
||||
"$": "introduction",
|
||||
"description": "About the bot"
|
||||
},
|
||||
"invite": {
|
||||
"$": "invite",
|
||||
"description": "How to invite the bot"
|
||||
},
|
||||
"timezones": {
|
||||
"$": "timezones",
|
||||
"description": "How BlitzKit infers and uses your timezone"
|
||||
},
|
||||
"credits": {
|
||||
"$": "credits",
|
||||
"description": "People who have helped BlitzKit"
|
||||
}
|
||||
}
|
||||
},
|
||||
"breakdown": {
|
||||
"$": "breakdown",
|
||||
"description": "A period's breakdown by tanks played",
|
||||
"body": {
|
||||
"total": "Total",
|
||||
"battles": "Battles",
|
||||
"wn8": "WN8",
|
||||
"winrate": "Winrate",
|
||||
"damage": "Damage",
|
||||
"unknown_tank": "Unknown tank %s"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"$": "debug",
|
||||
"description": "Debug information about the bot",
|
||||
"body": {
|
||||
"title": "# Debug\n\n",
|
||||
"version": "Version",
|
||||
"shard": {
|
||||
"$": "Shard",
|
||||
"default": "default"
|
||||
},
|
||||
"uptime": "Uptime"
|
||||
}
|
||||
},
|
||||
"evolution": {
|
||||
"$": "evolution",
|
||||
"description": "Evolution of statistics",
|
||||
"groups": {
|
||||
"player": {
|
||||
"$": "player",
|
||||
"description": "Player statistics"
|
||||
},
|
||||
"tank": {
|
||||
"$": "tank",
|
||||
"description": "Tank statistics"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inactive": {
|
||||
"$": "inactive",
|
||||
"description": "Lists all inactive players in a clan above a threshold",
|
||||
"options": {
|
||||
"threshold": {
|
||||
"$": "threshold",
|
||||
"description": "The number of days inactive (default: %s days)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"subtitle": "Inactive for %s+ days",
|
||||
"listing": "%s days"
|
||||
}
|
||||
},
|
||||
"full_stats": {
|
||||
"$": "full-stats",
|
||||
"description": "Full in-game statistics",
|
||||
"body": {
|
||||
"cards": {
|
||||
"games": {
|
||||
"$": "Games",
|
||||
"winrate": "Winrate",
|
||||
"battles": "Battles",
|
||||
"wins_losses": "Wins / losses"
|
||||
},
|
||||
"efficiency": {
|
||||
"$": "Efficiency",
|
||||
"wn8": "WN8",
|
||||
"survival": "Survival",
|
||||
"accuracy": "Accuracy"
|
||||
},
|
||||
"lethality": {
|
||||
"$": "Lethality",
|
||||
"average_damage": "Average damage",
|
||||
"damage_per_tier": "Damage per tier",
|
||||
"damage_ratio": "Damage ratio",
|
||||
"average_kills": "Average kills"
|
||||
},
|
||||
"miscellaneous": {
|
||||
"$": "Miscellaneous",
|
||||
"average_tier": "Average tier",
|
||||
"average_spots": "Average spots",
|
||||
"average_xp": "Average XP",
|
||||
"kills_to_death_ratio": "Kills to death ratio"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"link": {
|
||||
"$": "link",
|
||||
"description": "Links your Blitz and Discord account",
|
||||
"body": {
|
||||
"accounts_linked": "Accounts linked",
|
||||
"no_role_permissions": {
|
||||
"$": "%s failed to verify",
|
||||
"description": "I don't have the permission to manage roles."
|
||||
}
|
||||
},
|
||||
"embed": {
|
||||
"title": "Incorrect account?",
|
||||
"description": "Use the command again and select a username from the search results."
|
||||
}
|
||||
},
|
||||
"search_clans": {
|
||||
"$": "search-clans",
|
||||
"description": "Search clans",
|
||||
"options": {
|
||||
"limit": {
|
||||
"$": "limit",
|
||||
"description": "The max number of clan results (default: %s)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"title": "Clan search for %s in region %s",
|
||||
"no_results": "No clans found"
|
||||
}
|
||||
},
|
||||
"search_players": {
|
||||
"$": "search-players",
|
||||
"description": "Search players",
|
||||
"options": {
|
||||
"limit": {
|
||||
"$": "limit",
|
||||
"description": "The max number of player results (default: %s)"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"title": "Player search for %s in region %s",
|
||||
"no_results": "No players found"
|
||||
}
|
||||
},
|
||||
"rating_leaderboard": {
|
||||
"$": "rating-leaderboard",
|
||||
"description": "Rating battles statistics",
|
||||
"subcommands": {
|
||||
"neighbors": {
|
||||
"$": "neighbors",
|
||||
"description": "Neighboring players on the leaderboard"
|
||||
},
|
||||
"league": {
|
||||
"$": "league",
|
||||
"description": "Top players of a league"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"limit": {
|
||||
"$": "limit",
|
||||
"description": "How many neighbors to display (default: %s)"
|
||||
},
|
||||
"season": {
|
||||
"$": "season",
|
||||
"description": "Season number (default: current)",
|
||||
"choices": {
|
||||
"current": "%s (current)"
|
||||
}
|
||||
},
|
||||
"league": {
|
||||
"$": "league",
|
||||
"description": "The league to display"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"top_players": "Top %s players",
|
||||
"no_participation": "# %s didn't play rating in season %s\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": "Deleted %s",
|
||||
"neighbors": "Rating neighbors",
|
||||
"more": "+ %s more"
|
||||
}
|
||||
},
|
||||
"today": {
|
||||
"$": "today",
|
||||
"description": "Today's played tanks and statistics"
|
||||
},
|
||||
"stats": {
|
||||
"$": "stats",
|
||||
"description": "Quick statistics for a period"
|
||||
},
|
||||
"owned_tanks": {
|
||||
"$": "owned-tanks",
|
||||
"description": "Shows all own tanks in a tier",
|
||||
"body": {
|
||||
"title": "# %s's owned tier %s tanks\n\n"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"$": "permissions",
|
||||
"description": "Checks wether the bot has the required permissions to function",
|
||||
"body": {
|
||||
"title": "# Permissions\n\n%s",
|
||||
"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": {
|
||||
"$": "ping",
|
||||
"description": "Check wether the bot is alive.",
|
||||
"body": {
|
||||
"pong": "Pong 🏓",
|
||||
"pong_with_time": "Pong 🏓 - %sms"
|
||||
},
|
||||
"subcommands": {
|
||||
"blitzkit": {
|
||||
"$": "blitzkit",
|
||||
"description": "Ping BlitzKit"
|
||||
},
|
||||
"wotb": {
|
||||
"$": "wotb",
|
||||
"description": "Ping World of Tanks Blitz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"player_info": {
|
||||
"$": "player-info",
|
||||
"description": "Basic information about a player",
|
||||
"body": {
|
||||
"title": "%s's information",
|
||||
"nickname": "Nickname",
|
||||
"battles": "Battles",
|
||||
"winrate": "Winrate",
|
||||
"account_id": "Account ID",
|
||||
"created": "Created",
|
||||
"last_battle": "Last battle"
|
||||
}
|
||||
},
|
||||
"rating": {
|
||||
"$": "rating",
|
||||
"description": "Rating player's statistics",
|
||||
"subcommands": {
|
||||
"today": {
|
||||
"$": "today",
|
||||
"description": "Today's rating statistics"
|
||||
},
|
||||
"season": {
|
||||
"$": "season",
|
||||
"description": "This season's rating statistics"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"subtitle": {
|
||||
"today": "Today's rating statistics",
|
||||
"season": "This season's rating statistics"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"no_participation": "# %s 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": {}
|
||||
}
|
||||
@@ -13,7 +13,6 @@
|
||||
"lz4js": "^0.2.0",
|
||||
"pickleparser": "^0.2.1",
|
||||
"protobufjs": "^7.4.0",
|
||||
"sharp": "^0.33.5",
|
||||
"ts-proto": "^2.2.2",
|
||||
"yaml": "^2.5.0"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export type BlitzTankClass =
|
||||
| 'lightTank'
|
||||
| 'mediumTank'
|
||||
| 'heavyTank'
|
||||
| 'AT-SPG';
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './canSplash';
|
||||
export * from './clans';
|
||||
export * from './classes';
|
||||
export * from './constants';
|
||||
export * from './equipmentPrice';
|
||||
export * from './fetchBlitz';
|
||||
|
||||
@@ -11,7 +11,7 @@ export async function isValidBlitzId(id: number, token?: string) {
|
||||
|
||||
const json = await fetch(
|
||||
`https://api.wotblitz.${region}/wotb/account/info/?account_id=${id}&application_id=${assertSecret(
|
||||
process.env.PUBLIC_WARGAMING_APPLICATION_ID,
|
||||
import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID,
|
||||
)}${
|
||||
token === undefined ? '' : `&access_token=${token}`
|
||||
}&fields=account_id%2C-account_id`,
|
||||
|
||||
@@ -11,10 +11,11 @@ export type AccountListWithServer = (AccountListItem & {
|
||||
})[];
|
||||
|
||||
export async function searchPlayersAcrossRegions(search: string, limit = 25) {
|
||||
const trimmedSearch = search.trim();
|
||||
const trimmed = search.trim();
|
||||
if (trimmed.length < 3 && trimmed.length > 100) return [];
|
||||
const normalizedLimit = Math.floor(limit / 3);
|
||||
|
||||
if (usernamePattern.test(trimmedSearch)) {
|
||||
if (usernamePattern.test(trimmed)) {
|
||||
return (
|
||||
await Promise.all([
|
||||
fetchBlitz<AccountList>('com', 'account/list', {
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function getArchivedLatestSeasonNumber() {
|
||||
const regionsURL = (
|
||||
(await fetch(
|
||||
`https://api.github.com/repos/tresabhi/${assertSecret(
|
||||
process.env.PUBLIC_ASSET_REPO,
|
||||
import.meta.env.PUBLIC_ASSET_REPO,
|
||||
)}/git/trees/main`,
|
||||
).then((response) => response.json())) as GitHubTrees
|
||||
).tree.find(({ path }) => path === 'regions')!.url;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { asset } from '@blitzkit/core';
|
||||
|
||||
export function tankIcon(id: number, size: 'big' | 'small' = 'big') {
|
||||
export const BLITZKIT_TANK_ICON_SIZE = { width: 40 * 16, height: 30 * 16 };
|
||||
|
||||
export function tankIcon(
|
||||
id: number,
|
||||
size: 'big' | 'small' | 'blitzkit' = 'blitzkit',
|
||||
) {
|
||||
return asset(`icons/tanks/${size}/${id}.webp`);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { RatingStats } from '../blitz/getAccountInfo';
|
||||
import { BkrlSuperset1Entry } from '../streams/bkrl';
|
||||
import { RatingLeaderboardEntryV2 } from '../protos';
|
||||
|
||||
export function deltaBkrlBlitzStats(
|
||||
a: BkrlSuperset1Entry,
|
||||
a: RatingLeaderboardEntryV2,
|
||||
b1: RatingStats,
|
||||
b2: RatingPlayer,
|
||||
) {
|
||||
@@ -13,7 +13,7 @@ export function deltaBkrlBlitzStats(
|
||||
survived: b1.survived_battles - a.survived,
|
||||
wins: b1.wins - a.wins,
|
||||
score: b2.score - a.score,
|
||||
} satisfies Omit<BkrlSuperset1Entry, 'id'>;
|
||||
} satisfies Omit<RatingLeaderboardEntryV2, 'id'>;
|
||||
}
|
||||
export interface RatingPlayer {
|
||||
spa_id: number;
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
"build:ratingleaderboard": "bun src/buildRatingLeaderboard.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"esbuild": "^0.19.12",
|
||||
"@blitzkit/core": "workspace:*",
|
||||
"@gltf-transform/core": "^4.0.10",
|
||||
"@gltf-transform/extensions": "^4.0.10",
|
||||
"@gltf-transform/functions": "^4.0.10",
|
||||
"csv-parse": "^5.5.6",
|
||||
"decompress": "^4.2.1",
|
||||
"esbuild": "^0.19.12",
|
||||
"fast-xml-parser": "^4.4.1",
|
||||
"googleapis": "^134.0.0",
|
||||
"progress": "^2.0.3"
|
||||
"progress": "^2.0.3",
|
||||
"puppeteer": "^23.10.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/decompress": "^4.2.7",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { assertSecret } from '@blitzkit/core';
|
||||
import { mkdir, writeFile } from 'fs/promises';
|
||||
import { parse as parsePath } from 'path';
|
||||
import ProgressBar from 'progress';
|
||||
@@ -6,7 +7,6 @@ import { dvp } from '../../../submodules/blitzkit-closed/src/dvp';
|
||||
import { readStringDVPL } from '../src/core/blitz/readStringDVPL';
|
||||
import { DATA } from './buildAssets/constants';
|
||||
import { readYAMLDVPL } from './core/blitz/readYAMLDVPL';
|
||||
import { assertSecret } from '@blitzkit/core';
|
||||
import { writeDVPL } from './core/blitz/writeDVPL';
|
||||
|
||||
const versionTextFile = await readStringDVPL(`${DATA}/version.txt.dvpl`);
|
||||
@@ -22,7 +22,7 @@ let patchIndex = 1;
|
||||
while (true) {
|
||||
const response = await fetch(
|
||||
`${assertSecret(
|
||||
process.env.WOTB_DLC_CDN,
|
||||
import.meta.env.WOTB_DLC_CDN,
|
||||
)}/dlc/s${currentVersion}_${patchIndex}.yaml`,
|
||||
);
|
||||
|
||||
@@ -31,10 +31,10 @@ while (true) {
|
||||
|
||||
const data = parseYaml(await response.text());
|
||||
const dvpm = await fetch(
|
||||
`${assertSecret(process.env.WOTB_DLC_CDN)}/dlc/${data.dx11}`,
|
||||
`${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${data.dx11}`,
|
||||
).then((response) => response.arrayBuffer());
|
||||
const dvpd = await fetch(
|
||||
`${assertSecret(process.env.WOTB_DLC_CDN)}/dlc/${data.dx11.replace(
|
||||
`${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${data.dx11.replace(
|
||||
'.dvpm',
|
||||
'.dvpd',
|
||||
)}`,
|
||||
@@ -63,7 +63,7 @@ while (true) {
|
||||
console.log('Found dynamic content localizations; patching...');
|
||||
|
||||
const localizationsResponse = await fetch(
|
||||
`${assertSecret(process.env.WOTB_DLC_CDN)}/dlc/${
|
||||
`${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${
|
||||
data.dynamicContentLocalizationsDir
|
||||
}/en.yaml`,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { BLITZKIT_TANK_ICON_SIZE, fetchTankDefinitions } from '@blitzkit/core';
|
||||
import { exec } from 'child_process';
|
||||
import { launch } from 'puppeteer';
|
||||
import sharp from 'sharp';
|
||||
import { commitAssets } from '../core/github/commitAssets';
|
||||
import { FileChange } from '../core/github/commitMultipleFiles';
|
||||
|
||||
export async function blitzkitTankIcons() {
|
||||
console.log('Building blitzkit tank icons...');
|
||||
|
||||
console.log('Starting website dev server...');
|
||||
|
||||
const child = exec('cd ../.. && bun dev:website');
|
||||
const browser = await launch();
|
||||
const page = await browser.newPage();
|
||||
const tanks = Object.values((await fetchTankDefinitions()).tanks);
|
||||
const files: FileChange[] = [];
|
||||
|
||||
let index = 0;
|
||||
for (const { id, name } of tanks) {
|
||||
await page.setViewport({
|
||||
width: BLITZKIT_TANK_ICON_SIZE.width - 1,
|
||||
height: BLITZKIT_TANK_ICON_SIZE.height,
|
||||
});
|
||||
await page.goto(`http://localhost:4321/api/tankopedia/tank-icon/${id}/`, {
|
||||
waitUntil: 'networkidle0',
|
||||
});
|
||||
|
||||
if (index === 0) {
|
||||
console.log('Dev server started!');
|
||||
}
|
||||
|
||||
const screenshot = await page.screenshot({
|
||||
type: 'webp',
|
||||
omitBackground: true,
|
||||
});
|
||||
const content = await sharp(screenshot).trim({ threshold: 100 }).toBuffer();
|
||||
|
||||
index++;
|
||||
console.log(
|
||||
`Rendered ${id} (${name}) ${((index / tanks.length) * 100).toFixed(2)}%`,
|
||||
);
|
||||
|
||||
files.push({ path: `icons/tanks/blitzkit/${id}.webp`, content });
|
||||
}
|
||||
|
||||
child.kill('SIGHUP');
|
||||
await browser.close();
|
||||
|
||||
await commitAssets('tank icons', files);
|
||||
}
|
||||
@@ -35,9 +35,9 @@ export async function currencies() {
|
||||
}),
|
||||
);
|
||||
|
||||
const glossary = await fetch(assertSecret(process.env.WOTB_GLOSSARY)).then(
|
||||
(response) => response.json() as Promise<BlitzGlossary>,
|
||||
);
|
||||
const glossary = await fetch(
|
||||
assertSecret(import.meta.env.WOTB_GLOSSARY),
|
||||
).then((response) => response.json() as Promise<BlitzGlossary>);
|
||||
|
||||
await Promise.all(
|
||||
Object.entries(glossary)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Armor,
|
||||
assertSecret,
|
||||
BlitzTankClass,
|
||||
CamouflageDefinitions,
|
||||
Consumable,
|
||||
ConsumableDefinitions,
|
||||
@@ -38,9 +39,9 @@ import { readStringDVPL } from '../core/blitz/readStringDVPL';
|
||||
import { readXMLDVPL } from '../core/blitz/readXMLDVPL';
|
||||
import { readYAMLDVPL } from '../core/blitz/readYAMLDVPL';
|
||||
import { commitAssets } from '../core/github/commitAssets';
|
||||
import { TankParameters } from './blitzkitTankIcons';
|
||||
import { DATA } from './constants';
|
||||
import { Avatar } from './skillIcons';
|
||||
import { TankParameters } from './tankIcons';
|
||||
|
||||
function parseResearchCost(raw: number | string) {
|
||||
if (typeof raw === 'number') {
|
||||
@@ -70,7 +71,6 @@ const blitzTankFilterDefinitionCategoryToBlitzkit: Record<
|
||||
function vector3TupleToBlitzkit(tuple: Vector3Tuple) {
|
||||
return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3;
|
||||
}
|
||||
type BlitzTankClass = 'lightTank' | 'mediumTank' | 'heavyTank' | 'AT-SPG';
|
||||
const blitzTankClassToBlitzkit: Record<BlitzTankClass, TankClass> = {
|
||||
lightTank: TankClass.LIGHT,
|
||||
'AT-SPG': TankClass.TANK_DESTROYER,
|
||||
@@ -514,7 +514,7 @@ export async function definitions() {
|
||||
|
||||
const wargamingTankopedia = (await fetch(
|
||||
`https://api.wotblitz.com/wotb/encyclopedia/vehicles/?application_id=${assertSecret(
|
||||
process.env.PUBLIC_WARGAMING_APPLICATION_ID,
|
||||
import.meta.env.PUBLIC_WARGAMING_APPLICATION_ID,
|
||||
)}&fields=description`,
|
||||
).then((response) => response.json())) as {
|
||||
data: { [key: number]: { description: null | string } };
|
||||
|
||||
@@ -9,6 +9,7 @@ const targets = argv
|
||||
?.split('=')[1]
|
||||
.split(',');
|
||||
|
||||
import { blitzkitTankIcons } from './blitzkitTankIcons';
|
||||
import { boosterIcons } from './boosterIcons';
|
||||
import { camouflageIcons } from './camouflageIcons';
|
||||
import { consumableProvisionIcons } from './consumableProvisionIcons';
|
||||
@@ -28,7 +29,6 @@ if (!targets && !allTargets) throw new Error('No target(s) specified');
|
||||
|
||||
const methods = [
|
||||
definitions,
|
||||
tankIcons,
|
||||
tankModels,
|
||||
tankArmors,
|
||||
gameModeBanners,
|
||||
@@ -41,6 +41,8 @@ const methods = [
|
||||
consumableProvisionIcons,
|
||||
camouflageIcons,
|
||||
boosterIcons,
|
||||
tankIcons,
|
||||
blitzkitTankIcons,
|
||||
];
|
||||
|
||||
for (const method of methods) {
|
||||
|
||||
@@ -7,9 +7,9 @@ import { readXMLDVPL } from '../core/blitz/readXMLDVPL';
|
||||
import { readYAMLDVPL } from '../core/blitz/readYAMLDVPL';
|
||||
import { commitAssets } from '../core/github/commitAssets';
|
||||
import { FileChange } from '../core/github/commitMultipleFiles';
|
||||
import { TankParameters } from './blitzkitTankIcons';
|
||||
import { DATA } from './constants';
|
||||
import { VehicleDefinitionList } from './definitions';
|
||||
import { TankParameters } from './tankIcons';
|
||||
|
||||
export async function tankModels() {
|
||||
console.log('Building tank models...');
|
||||
|
||||
@@ -25,10 +25,13 @@ build({
|
||||
'.node': 'empty',
|
||||
'.json': 'json',
|
||||
},
|
||||
external: ['sharp'],
|
||||
minifyIdentifiers: false, // causes goofy ahh issues
|
||||
minifySyntax: !dev,
|
||||
minifyWhitespace: !dev,
|
||||
sourcemap: !dev,
|
||||
jsxImportSource: 'react',
|
||||
jsx: 'automatic',
|
||||
});
|
||||
|
||||
for (const module of await readdir(`${modulesRoot}/@resvg`)) {
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { DdsReadStream, PvrReadStream } from '@blitzkit/core';
|
||||
import { existsSync } from 'fs';
|
||||
import sharp, { JpegOptions } from 'sharp';
|
||||
import sharp from 'sharp';
|
||||
import { readDVPLFile } from '../readDVPLFile';
|
||||
import { TextureMutation } from './constants';
|
||||
|
||||
const jpegOptions: JpegOptions = {
|
||||
quality: 20,
|
||||
mozjpeg: true,
|
||||
};
|
||||
|
||||
export async function readTexture(path: string, mutation?: TextureMutation) {
|
||||
const ddsTexturePath = path.replace('.tex', '.dx11.dds.dvpl');
|
||||
const isDds = existsSync(ddsTexturePath);
|
||||
@@ -40,7 +35,7 @@ export async function readTexture(path: string, mutation?: TextureMutation) {
|
||||
raw.data[index + 3] = 255;
|
||||
}
|
||||
|
||||
return await sharp(raw.data, { raw }).jpeg(jpegOptions).toBuffer();
|
||||
return await sharp(raw.data, { raw }).jpeg().toBuffer();
|
||||
}
|
||||
|
||||
case TextureMutation.RoughnessMetallicness: {
|
||||
@@ -49,16 +44,16 @@ export async function readTexture(path: string, mutation?: TextureMutation) {
|
||||
});
|
||||
const metallicness = sharp(raw.data, { raw })
|
||||
.extractChannel('green')
|
||||
.jpeg(jpegOptions)
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
const roughness = sharp(raw.data, { raw })
|
||||
.extractChannel('alpha')
|
||||
.jpeg(jpegOptions)
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
|
||||
return await newImage
|
||||
.joinChannel(await Promise.all([roughness, metallicness]))
|
||||
.jpeg(jpegOptions)
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
@@ -69,18 +64,15 @@ export async function readTexture(path: string, mutation?: TextureMutation) {
|
||||
*/
|
||||
return await sharp(raw.data, { raw })
|
||||
.extractChannel('alpha')
|
||||
.jpeg(jpegOptions)
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
case TextureMutation.Albedo: {
|
||||
return await sharp(raw.data, { raw })
|
||||
.removeAlpha()
|
||||
.jpeg(jpegOptions)
|
||||
.toBuffer();
|
||||
return await sharp(raw.data, { raw }).removeAlpha().jpeg().toBuffer();
|
||||
}
|
||||
|
||||
default:
|
||||
return await sharp(raw.data, { raw }).jpeg(jpegOptions).toBuffer();
|
||||
return await sharp(raw.data, { raw }).jpeg().toBuffer();
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ export async function commitAssets(message: string, changes: FileChange[]) {
|
||||
if (changes.length === 0) return;
|
||||
|
||||
await commitMultipleFiles(
|
||||
assertSecret(process.env.PUBLIC_ASSET_REPO),
|
||||
assertSecret(process.env.PUBLIC_ASSET_BRANCH),
|
||||
assertSecret(import.meta.env.PUBLIC_ASSET_REPO),
|
||||
assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
|
||||
`${message} - ${new Date().toDateString()}`,
|
||||
changes,
|
||||
);
|
||||
|
||||
@@ -30,7 +30,10 @@ export async function commitMultipleFiles(
|
||||
} else if (response.status === 200) {
|
||||
const buffer = Buffer.from(await response.arrayBuffer());
|
||||
|
||||
if (!buffer.equals(change.content)) {
|
||||
// we discard blob if they're the same size; it's unlikely their
|
||||
// contents will be different; I love playing russian roulette!
|
||||
const equal = buffer.length === change.content.length || buffer.equals(change.content);
|
||||
if (!equal) {
|
||||
const diff = change.content.length - buffer.length;
|
||||
|
||||
console.log(
|
||||
|
||||
@@ -5,5 +5,5 @@ import { Octokit } from '@octokit/rest';
|
||||
Octokit.plugin(throttling);
|
||||
|
||||
export const octokit = new Octokit({
|
||||
auth: assertSecret(process.env.GH_TOKEN),
|
||||
auth: assertSecret(import.meta.env.GH_TOKEN),
|
||||
});
|
||||
@@ -10,7 +10,7 @@ import { google } from 'googleapis';
|
||||
import { cloneDeep, uniqBy } from 'lodash-es';
|
||||
import { commitAssets } from './core/github/commitAssets';
|
||||
|
||||
const MAX_QUERIES = 128;
|
||||
const MAX_QUERIES = 64;
|
||||
|
||||
console.log('Finding reviews...');
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@react-three/fiber": "^8.17.10",
|
||||
"compare-versions": "^6.1.1",
|
||||
"echarts": "^5.5.1",
|
||||
"googleapis": "^144.0.0",
|
||||
"immer": "^10.1.1",
|
||||
"js-cookie": "^3.0.5",
|
||||
"markdown-to-jsx": "^7.5.0",
|
||||
|
||||
+19
-6
@@ -49,6 +49,7 @@ export function PrimaryArmorSceneComponent({
|
||||
damage: { value: null },
|
||||
explosionRadius: { value: null },
|
||||
greenPenetration: { value: null },
|
||||
advancedHighlighting: { value: null },
|
||||
opaque: { value: null },
|
||||
|
||||
inverseProjectionMatrix: { value: null },
|
||||
@@ -90,6 +91,10 @@ export function PrimaryArmorSceneComponent({
|
||||
function handleGreenPenetrationChange(greenPenetration: boolean) {
|
||||
material.uniforms.greenPenetration.value = greenPenetration;
|
||||
}
|
||||
function handleAdvancedHighlightingChange(advancedHighlighting: boolean) {
|
||||
material.uniforms.advancedHighlighting.value = advancedHighlighting;
|
||||
invalidate();
|
||||
}
|
||||
function handleOpaqueChange(opaque: boolean) {
|
||||
material.uniforms.opaque.value = opaque;
|
||||
}
|
||||
@@ -133,12 +138,13 @@ export function PrimaryArmorSceneComponent({
|
||||
if (!noInvalidate) invalidate();
|
||||
}
|
||||
|
||||
const initialState = tankopediaPersistentStore.getState();
|
||||
|
||||
handleShellChange();
|
||||
handleGreenPenetrationChange(
|
||||
tankopediaPersistentStore.getState().greenPenetration,
|
||||
);
|
||||
handleOpaqueChange(tankopediaPersistentStore.getState().opaque);
|
||||
handleWireframeChange(tankopediaPersistentStore.getState().wireframe);
|
||||
handleGreenPenetrationChange(initialState.greenPenetration);
|
||||
handleAdvancedHighlightingChange(initialState.advancedHighlighting);
|
||||
handleOpaqueChange(initialState.opaque);
|
||||
handleWireframeChange(initialState.wireframe);
|
||||
handleProtagonistEquipmentChange(
|
||||
duelStore.getState().protagonist.equipmentMatrix,
|
||||
);
|
||||
@@ -156,7 +162,14 @@ export function PrimaryArmorSceneComponent({
|
||||
(state) => state.greenPenetration,
|
||||
handleGreenPenetrationChange,
|
||||
),
|
||||
|
||||
tankopediaPersistentStore.subscribe(
|
||||
(state) => state.advancedHighlighting,
|
||||
handleAdvancedHighlightingChange,
|
||||
),
|
||||
tankopediaPersistentStore.subscribe(
|
||||
(state) => state.opaque,
|
||||
handleOpaqueChange,
|
||||
),
|
||||
duelStore.subscribe(
|
||||
(state) => state.protagonist.equipmentMatrix,
|
||||
(equipment) => handleProtagonistEquipmentChange(equipment),
|
||||
|
||||
+66
-46
@@ -12,82 +12,102 @@ uniform bool canSplash;
|
||||
uniform float damage;
|
||||
uniform float explosionRadius;
|
||||
uniform bool greenPenetration;
|
||||
uniform bool advancedHighlighting;
|
||||
uniform bool opaque;
|
||||
uniform mat4 inverseProjectionMatrix;
|
||||
|
||||
uniform highp sampler2D spacedArmorBuffer;
|
||||
uniform highp sampler2D spacedArmorDepth;
|
||||
|
||||
float depthToDistance(float depth) {
|
||||
vec4 clipPosition = vec4(gl_FragCoord.xy / resolution * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
|
||||
vec4 eyePosition = inverseProjectionMatrix * clipPosition;
|
||||
return length(eyePosition.xyz / eyePosition.w);
|
||||
const float HALF = 0.5;
|
||||
const float EXPLOSION_DAMAGE_FACTOR = 1.1;
|
||||
const float RANDOMIZATION_FACTOR = 0.05;
|
||||
const float GREEN_VALUE = 0.392;
|
||||
|
||||
float getDistance(vec2 coord, float depth) {
|
||||
vec4 clipPos = vec4(coord * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
|
||||
vec4 eyePos = inverseProjectionMatrix * clipPos;
|
||||
return length(eyePos.xyz / eyePos.w);
|
||||
}
|
||||
|
||||
vec3 getPenetrationColor(bool isThreeCalibersRule) {
|
||||
if (advancedHighlighting) {
|
||||
return vec3(0.0, 1.0, isThreeCalibersRule ? 1.0 : 0.0);
|
||||
}
|
||||
|
||||
return vec3(0.0, 1.0, 0.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
float penetrationChance = -1.0;
|
||||
float splashChance = -1.0;
|
||||
vec2 screenCoord = gl_FragCoord.xy / resolution;
|
||||
vec4 spacedArmorData = texture2D(spacedArmorBuffer, screenCoord);
|
||||
bool isUnderSpacedArmor = spacedArmorData.a != 0.0;
|
||||
|
||||
vec2 screenCoordinates = gl_FragCoord.xy / resolution;
|
||||
float angle = acos(dot(vNormal, -vViewPosition) / length(vViewPosition));
|
||||
|
||||
vec4 spacedArmorBufferFragment = texture2D(spacedArmorBuffer, screenCoordinates);
|
||||
bool isUnderSpacedArmor = spacedArmorBufferFragment.a != 0.0;
|
||||
float viewDistance = length(vViewPosition);
|
||||
float angle = acos(dot(vNormal, -vViewPosition) / viewDistance);
|
||||
|
||||
bool threeCalibersRule = caliber > thickness * 3.0 || isUnderSpacedArmor;
|
||||
float penetrationChance = -1.0;
|
||||
float splashChance = 0.0;
|
||||
bool didRicochet = false;
|
||||
|
||||
if (!threeCalibersRule && angle >= ricochet) {
|
||||
penetrationChance = 0.0;
|
||||
didRicochet = true;
|
||||
} else {
|
||||
bool twoCalibersRule = caliber > thickness * 2.0 && thickness > 0.0;
|
||||
float finalNormalization = twoCalibersRule ? ((1.4 * normalization * caliber) / (2.0 * thickness)) : normalization;
|
||||
float finalThickness = thickness / cos(max(0.0, angle - finalNormalization));
|
||||
float thicknessRatio = thickness > 0.0 ? caliber / thickness : 0.0;
|
||||
bool twoCalibersRule = thicknessRatio > 2.0;
|
||||
float finalNorm = twoCalibersRule ? ((1.4 * normalization * caliber) / (2.0 * thickness)) : normalization;
|
||||
float finalThickness = thickness / cos(max(0.0, angle - finalNorm));
|
||||
|
||||
// world space in meters
|
||||
vec4 spacedArmorDepthFragment = texture2D(spacedArmorDepth, screenCoordinates);
|
||||
float spacedArmorDistance = depthToDistance(spacedArmorDepthFragment.r);
|
||||
float primaryArmorDistance = depthToDistance(gl_FragCoord.z);
|
||||
float distanceFromSpacedArmor = primaryArmorDistance - spacedArmorDistance;
|
||||
float remainingPen = penetration;
|
||||
|
||||
if (canSplash && isUnderSpacedArmor) {
|
||||
float spacedArmorThickness = spacedArmorBufferFragment.r * penetration;
|
||||
float finalDamage = 0.5 * damage * (1.0 - distanceFromSpacedArmor / explosionRadius) - 1.1 * (finalThickness + spacedArmorThickness);
|
||||
if (isUnderSpacedArmor) {
|
||||
float spacedArmorThickness = spacedArmorData.r * penetration;
|
||||
remainingPen -= spacedArmorThickness;
|
||||
|
||||
penetrationChance = 0.0;
|
||||
splashChance = finalDamage > 0.0 ? 1.0 : 0.0;
|
||||
} else {
|
||||
float remainingPenetration = penetration;
|
||||
if (isExplosive && remainingPen > 0.0) {
|
||||
float spacedDist = getDistance(screenCoord, texture2D(spacedArmorDepth, screenCoord).r);
|
||||
float primaryDist = getDistance(screenCoord, gl_FragCoord.z);
|
||||
float distFromArmor = primaryDist - spacedDist;
|
||||
|
||||
if (isUnderSpacedArmor) {
|
||||
float spacedArmorThickness = spacedArmorBufferFragment.r * penetration;
|
||||
remainingPenetration -= spacedArmorThickness;
|
||||
if (canSplash) {
|
||||
float finalDamage = HALF * damage * (1.0 - distFromArmor / explosionRadius) - EXPLOSION_DAMAGE_FACTOR * (finalThickness + spacedArmorThickness);
|
||||
splashChance = step(0.0, finalDamage);
|
||||
penetrationChance = 0.0;
|
||||
|
||||
if (isExplosive && remainingPenetration > 0.0) {
|
||||
remainingPenetration -= 0.5 * remainingPenetration * distanceFromSpacedArmor;
|
||||
} else {
|
||||
remainingPen -= HALF * remainingPen * distFromArmor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remainingPenetration = max(0.0, remainingPenetration);
|
||||
float delta = finalThickness - remainingPenetration;
|
||||
float randomization = remainingPenetration * 0.05;
|
||||
if (penetrationChance < 0.0) {
|
||||
remainingPen = max(0.0, remainingPen);
|
||||
float delta = finalThickness - remainingPen;
|
||||
float randomization = remainingPen * RANDOMIZATION_FACTOR;
|
||||
penetrationChance = clamp(1.0 - (delta + randomization) / (2.0 * randomization), 0.0, 1.0);
|
||||
|
||||
if (canSplash) {
|
||||
float splashDamage = 0.5 * damage - 1.1 * finalThickness;
|
||||
splashChance = splashDamage > 0.0 ? 1.0 : 0.0;
|
||||
} else {
|
||||
splashChance = 0.0;
|
||||
float splashDamage = HALF * damage - EXPLOSION_DAMAGE_FACTOR * finalThickness;
|
||||
splashChance = step(0.0, splashDamage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float alpha = opaque ? 1.0 : 0.5;
|
||||
vec3 color = vec3(1.0, splashChance * 0.392, 0.0);
|
||||
if (greenPenetration) {
|
||||
// non-trigonometric red-green mixing: https://www.desmos.com/calculator/ceo1oq7l67
|
||||
float falling = -pow(penetrationChance, 2.0) + 1.0;
|
||||
float gaining = -pow(penetrationChance - 1.0, 2.0) + 1.0;
|
||||
gl_FragColor = vec4(falling * color + gaining * vec3(0.0, 1.0, 0.0), alpha);
|
||||
float alpha = opaque ? 1.0 : HALF;
|
||||
vec3 baseColor = vec3(1.0, splashChance * GREEN_VALUE, 0.0);
|
||||
|
||||
if (advancedHighlighting && didRicochet) {
|
||||
baseColor = vec3(1.0, baseColor.g, 1.0);
|
||||
}
|
||||
|
||||
if (greenPenetration || advancedHighlighting) {
|
||||
float falling = -penetrationChance * penetrationChance + 1.0;
|
||||
float gaining = -(penetrationChance - 1.0) * (penetrationChance - 1.0) + 1.0;
|
||||
vec3 penColor = getPenetrationColor(threeCalibersRule);
|
||||
gl_FragColor = vec4(falling * baseColor + gaining * penColor, alpha);
|
||||
} else {
|
||||
gl_FragColor = vec4(color, (1.0 - penetrationChance) * alpha);
|
||||
gl_FragColor = vec4(baseColor, (1.0 - penetrationChance) * alpha);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,12 @@
|
||||
import {
|
||||
asset,
|
||||
availableProvisions,
|
||||
fetchConsumableDefinitions,
|
||||
fetchEquipmentDefinitions,
|
||||
fetchProvisionDefinitions,
|
||||
fetchSkillDefinitions,
|
||||
permanentSkills,
|
||||
} from '@blitzkit/core';
|
||||
import { asset, availableProvisions, permanentSkills } from '@blitzkit/core';
|
||||
import { checkConsumableProvisionInclusivity } from '@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity';
|
||||
import { ComponentPlaceholderIcon } from '@radix-ui/react-icons';
|
||||
import { Button, Flex, IconButton, Popover, Table } from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions';
|
||||
import { awaitableEquipmentDefinitions } from '../../core/awaitables/equipmentDefinitions';
|
||||
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
|
||||
import { awaitableSkillDefinitions } from '../../core/awaitables/skillDefinitions';
|
||||
import type { TankCharacteristics } from '../../core/blitzkit/tankCharacteristics';
|
||||
import { Var } from '../../core/radix/var';
|
||||
import { CompareEphemeral } from '../../stores/compareEphemeral';
|
||||
@@ -31,10 +27,17 @@ interface CompareTableProps {
|
||||
stats: TankCharacteristics[];
|
||||
}
|
||||
|
||||
const skillDefinitions = await fetchSkillDefinitions();
|
||||
const equipmentDefinitions = await fetchEquipmentDefinitions();
|
||||
const consumableDefinitions = await fetchConsumableDefinitions();
|
||||
const provisionDefinitions = await fetchProvisionDefinitions();
|
||||
const [
|
||||
skillDefinitions,
|
||||
equipmentDefinitions,
|
||||
consumableDefinitions,
|
||||
provisionDefinitions,
|
||||
] = await Promise.all([
|
||||
awaitableSkillDefinitions,
|
||||
awaitableEquipmentDefinitions,
|
||||
awaitableConsumableDefinitions,
|
||||
awaitableProvisionDefinitions,
|
||||
]);
|
||||
|
||||
export function CompareTable({ stats }: CompareTableProps) {
|
||||
const crewSkills = CompareEphemeral.use((state) => state.crewSkills);
|
||||
@@ -541,23 +544,25 @@ export function CompareTable({ stats }: CompareTableProps) {
|
||||
</Popover.Root>
|
||||
</Flex>
|
||||
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
mutateCompareEphemeral((draft) => {
|
||||
draft.members[index].camouflage = !camouflage;
|
||||
});
|
||||
}}
|
||||
variant={camouflage ? 'solid' : 'outline'}
|
||||
>
|
||||
<img
|
||||
src={asset('icons/camo.webp')}
|
||||
style={{
|
||||
width: '1em',
|
||||
height: '1em',
|
||||
transform: 'scale(2) translate(15%, 15%)',
|
||||
{!tank.fixed_camouflage && (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
mutateCompareEphemeral((draft) => {
|
||||
draft.members[index].camouflage = !camouflage;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
variant={camouflage ? 'solid' : 'outline'}
|
||||
>
|
||||
<img
|
||||
src={asset('icons/camo.webp')}
|
||||
style={{
|
||||
width: '1em',
|
||||
height: '1em',
|
||||
transform: 'scale(2) translate(15%, 15%)',
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { fetchProvisionDefinitions } from '@blitzkit/core';
|
||||
import { PlusIcon, TrashIcon } from '@radix-ui/react-icons';
|
||||
import { Button, Dialog, Flex, SegmentedControl } from '@radix-ui/themes';
|
||||
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
|
||||
import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember';
|
||||
import { CompareEphemeral } from '../../stores/compareEphemeral';
|
||||
import {
|
||||
@@ -14,7 +14,7 @@ interface ControlsProps {
|
||||
onAddTankDialogOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const provisionDefinitions = await fetchProvisionDefinitions();
|
||||
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||
|
||||
export function Controls({
|
||||
addTankDialogOpen,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { fetchProvisionDefinitions } from '@blitzkit/core';
|
||||
import { ExternalLinkIcon, LoopIcon, TrashIcon } from '@radix-ui/react-icons';
|
||||
import { Dialog, Flex, IconButton, Link } from '@radix-ui/themes';
|
||||
import { useState } from 'react';
|
||||
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
|
||||
import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember';
|
||||
import { CompareEphemeral } from '../../stores/compareEphemeral';
|
||||
import { TankSearch } from '../TankSearch';
|
||||
@@ -11,7 +11,7 @@ interface TankControlProps {
|
||||
id: number;
|
||||
}
|
||||
|
||||
const provisionDefinitions = await fetchProvisionDefinitions();
|
||||
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||
|
||||
export function TankControl({ index, id }: TankControlProps) {
|
||||
const [switchTankDialogOpen, setSwitchTankDialogOpen] = useState(false);
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import {
|
||||
asset,
|
||||
fetchSkillDefinitions,
|
||||
permanentSkills,
|
||||
romanize,
|
||||
} from '@blitzkit/core';
|
||||
import { asset, permanentSkills, romanize } from '@blitzkit/core';
|
||||
import { Flex } from '@radix-ui/themes';
|
||||
import { awaitableSkillDefinitions } from '../core/awaitables/skillDefinitions';
|
||||
import { GenericTankComponentButton } from './ModuleButtons/GenericTankComponentButton';
|
||||
|
||||
interface CrewSkillManagerProps {
|
||||
@@ -12,7 +8,7 @@ interface CrewSkillManagerProps {
|
||||
onChange?: (skillLevels: Record<string, number>) => void;
|
||||
}
|
||||
|
||||
const skillDefinitions = await fetchSkillDefinitions();
|
||||
const skillDefinitions = await awaitableSkillDefinitions;
|
||||
|
||||
export function CrewSkillManager({
|
||||
skillLevels,
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
compositeStats,
|
||||
compositeStatsKeys,
|
||||
deltaTankStats,
|
||||
fetchAverageDefinitions,
|
||||
fetchTankDefinitions,
|
||||
getTankStats,
|
||||
idToRegion,
|
||||
sumCompositeStats,
|
||||
@@ -15,6 +13,8 @@ import { ReloadIcon } from '@radix-ui/react-icons';
|
||||
import { ContextMenu } from '@radix-ui/themes';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { breakdownConfig } from '../../constants/embeds';
|
||||
import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions';
|
||||
import { awaitableTankDefinitions } from '../../core/awaitables/tankDefinitions';
|
||||
import { EmbedBreakdownPersistent } from '../../stores/embedBreakdownPersistent';
|
||||
import { useEmbedStateCurry } from '../../stores/embedState/utilities';
|
||||
import { BreakdownEmbedCard, BreakdownEmbedWrapper } from '../TanksEmbed';
|
||||
@@ -24,8 +24,10 @@ export const compositeStatsKeysOptions = compositeStatsKeys.map((value) => ({
|
||||
value,
|
||||
}));
|
||||
|
||||
const tankDefinitions = await fetchTankDefinitions();
|
||||
const averageDefinitions = await fetchAverageDefinitions();
|
||||
const [tankDefinitions, averageDefinitions] = await Promise.all([
|
||||
awaitableTankDefinitions,
|
||||
awaitableAverageDefinitions,
|
||||
]);
|
||||
|
||||
export const fakeCompositeStats = compositeStats(
|
||||
{
|
||||
|
||||
@@ -1,27 +1,67 @@
|
||||
import { fetchAverageDefinitions, fetchTankDefinitions } from '@blitzkit/core';
|
||||
import { Box, Flex, Heading } from '@radix-ui/themes';
|
||||
import { assertSecret } from '@blitzkit/core';
|
||||
import { EyeOpenIcon } from '@radix-ui/react-icons';
|
||||
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
||||
import { google } from 'googleapis';
|
||||
import { awaitableTankDefinitions } from '../core/awaitables/tankDefinitions';
|
||||
import { TankopediaPersistent } from '../stores/tankopediaPersistent';
|
||||
import { TankCard } from './TankCard';
|
||||
|
||||
const tankDefinitions = await fetchTankDefinitions();
|
||||
const averageDefinitions = await fetchAverageDefinitions();
|
||||
const tanks = Object.values(tankDefinitions.tanks);
|
||||
const tankAverages = Object.entries(averageDefinitions.averages).map(
|
||||
([id, average]) => ({
|
||||
id: Number(id),
|
||||
...average,
|
||||
}),
|
||||
);
|
||||
const [tankDefinitions] = await Promise.all([awaitableTankDefinitions, ,]);
|
||||
|
||||
// lol don't ask questions
|
||||
const testTanks = tanks
|
||||
.filter((tank) => tank.testing)
|
||||
.sort((a, b) => b.tier - a.tier);
|
||||
const mostPlayedTanks = tankAverages
|
||||
.sort((a, b) => b.samples.d_1 - a.samples.d_1)
|
||||
.map(({ id }) => tankDefinitions.tanks[id])
|
||||
.filter((tank) => tank !== undefined && !tank.testing);
|
||||
const popularTanks = [...testTanks, ...mostPlayedTanks].slice(0, 8);
|
||||
const auth = await google.auth.getClient({
|
||||
keyFile: import.meta.env.GOOGLE_APPLICATION_CREDENTIALS,
|
||||
scopes: ['https://www.googleapis.com/auth/analytics.readonly'],
|
||||
});
|
||||
const analytics = google.analyticsdata({ version: 'v1beta', auth });
|
||||
const report = await analytics.properties.runReport({
|
||||
property: `properties/${assertSecret(import.meta.env.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID)}`,
|
||||
requestBody: {
|
||||
dateRanges: [{ startDate: '7daysAgo', endDate: 'today' }],
|
||||
dimensions: [{ name: 'pagePath' }],
|
||||
metrics: [{ name: 'screenPageViews' }],
|
||||
orderBys: [{ metric: { metricName: 'screenPageViews' }, desc: true }],
|
||||
dimensionFilter: {
|
||||
filter: {
|
||||
fieldName: 'pagePath',
|
||||
stringFilter: { matchType: 'BEGINS_WITH', value: '/tools/tankopedia/' },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!report.data.rows) {
|
||||
throw new Error('No rows in report');
|
||||
}
|
||||
|
||||
const hotTanks = report.data.rows
|
||||
.filter(
|
||||
(row) =>
|
||||
row.dimensionValues &&
|
||||
row.dimensionValues[0].value &&
|
||||
row.dimensionValues[0].value !== '/tools/tankopedia/' &&
|
||||
row.metricValues &&
|
||||
row.metricValues[0].value,
|
||||
)
|
||||
.map((row) => ({
|
||||
id: Number(
|
||||
row.dimensionValues![0].value!.match(
|
||||
/\/tools\/tankopedia\/(\d+)\/?/,
|
||||
)?.[1],
|
||||
),
|
||||
views: Number(row.metricValues![0].value!),
|
||||
}))
|
||||
.filter((row) => {
|
||||
if (!(row.id in tankDefinitions.tanks)) {
|
||||
// console.log('Unknown tank', row.id);
|
||||
}
|
||||
|
||||
return row.id in tankDefinitions.tanks;
|
||||
})
|
||||
.slice(0, 8)
|
||||
.map(({ id, views }) => ({
|
||||
tank: tankDefinitions.tanks[id],
|
||||
views,
|
||||
}));
|
||||
|
||||
export function HomePageHotTanks() {
|
||||
return (
|
||||
@@ -33,15 +73,26 @@ export function HomePageHotTanks() {
|
||||
|
||||
function Content() {
|
||||
return (
|
||||
<Flex direction="column" gap="5" pt="4" pb="8">
|
||||
<Flex direction="column" pt="4" pb="8">
|
||||
<Heading align="center" size="5">
|
||||
Popular tanks
|
||||
</Heading>
|
||||
<Text color="gray" align="center" mb="5" size="2">
|
||||
Last 7 days
|
||||
</Text>
|
||||
|
||||
<Flex justify="center" gap="4" wrap="wrap">
|
||||
{popularTanks.map((tank) => (
|
||||
<Box width="7rem" key={tank.id}>
|
||||
<TankCard tank={tank} />
|
||||
{hotTanks.map((row) => (
|
||||
<Box width="7rem" key={row.tank.id}>
|
||||
<TankCard
|
||||
tank={row.tank}
|
||||
discriminator={
|
||||
<Flex align="center" gap="1" justify="center">
|
||||
<EyeOpenIcon />
|
||||
{Math.round(row.views * (30 / 7)).toLocaleString('en-US')}
|
||||
</Flex>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { asset, fetchConsumableDefinitions } from '@blitzkit/core';
|
||||
import { asset } from '@blitzkit/core';
|
||||
import { ClockIcon, ReloadIcon } from '@radix-ui/react-icons';
|
||||
import { Flex, Text } from '@radix-ui/themes';
|
||||
import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions';
|
||||
import { useDelta } from '../../hooks/useDelta';
|
||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
||||
@@ -11,7 +12,7 @@ interface ConsumableButtonProps extends TankComponentButtonProps {
|
||||
cooldown?: number;
|
||||
}
|
||||
|
||||
const consumableDefinitions = await fetchConsumableDefinitions();
|
||||
const consumableDefinitions = await awaitableConsumableDefinitions;
|
||||
|
||||
export function ConsumableButton({
|
||||
consumable,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { asset, fetchEquipmentDefinitions } from '@blitzkit/core';
|
||||
import { asset } from '@blitzkit/core';
|
||||
import { awaitableEquipmentDefinitions } from '../../core/awaitables/equipmentDefinitions';
|
||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
||||
|
||||
@@ -6,7 +7,7 @@ interface EquipmentButtonProps extends TankComponentButtonProps {
|
||||
equipment: number;
|
||||
}
|
||||
|
||||
const equipmentDefinitions = await fetchEquipmentDefinitions();
|
||||
const equipmentDefinitions = await awaitableEquipmentDefinitions;
|
||||
|
||||
export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) {
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { asset, fetchProvisionDefinitions } from '@blitzkit/core';
|
||||
import { asset } from '@blitzkit/core';
|
||||
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
|
||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
||||
|
||||
@@ -6,7 +7,7 @@ interface ProvisionButtonProps extends TankComponentButtonProps {
|
||||
provision: number;
|
||||
}
|
||||
|
||||
const provisionDefinitions = await fetchProvisionDefinitions();
|
||||
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||
|
||||
export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) {
|
||||
return (
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
---
|
||||
import { assertSecret } from '@blitzkit/core';
|
||||
|
||||
const PUBLIC_GOOGLE_TAG_MANAGER_ID = assertSecret(
|
||||
import.meta.env.PUBLIC_GOOGLE_TAG_MANAGER_ID,
|
||||
);
|
||||
---
|
||||
|
||||
{/* google analytics */}
|
||||
<script
|
||||
is:inline
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-GL2JVHCGPQ"></script>
|
||||
<script is:inline>
|
||||
src={`https://www.googletagmanager.com/gtag/js?id=${PUBLIC_GOOGLE_TAG_MANAGER_ID}`}
|
||||
></script>
|
||||
<script is:inline define:vars={{ PUBLIC_GOOGLE_TAG_MANAGER_ID }}>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-GL2JVHCGPQ');
|
||||
gtag('config', PUBLIC_GOOGLE_TAG_MANAGER_ID);
|
||||
</script>
|
||||
|
||||
{/* nitro ads */}
|
||||
{
|
||||
/*
|
||||
<script is:inline data-cfasync="false">
|
||||
window.nitroAds = window.nitroAds || {
|
||||
createAd: function () {
|
||||
@@ -27,3 +38,5 @@
|
||||
queue: [],
|
||||
};
|
||||
</script>
|
||||
*/
|
||||
}
|
||||
+7
-4
@@ -1,6 +1,4 @@
|
||||
import {
|
||||
fetchAverageDefinitions,
|
||||
fetchTankDefinitions,
|
||||
formatCompact,
|
||||
TANK_CLASSES,
|
||||
TankClass,
|
||||
@@ -10,14 +8,19 @@ import {
|
||||
import strings from '@blitzkit/core/lang/en-US.json';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { Box, Flex, Heading } from '@radix-ui/themes';
|
||||
import { awaitableAverageDefinitions } from '../../../../core/awaitables/averageDefinitions';
|
||||
import { awaitableTankDefinitions } from '../../../../core/awaitables/tankDefinitions';
|
||||
import { filterTank } from '../../../../core/blitzkit/filterTank';
|
||||
import { useAveragesExclusionRatio } from '../../../../hooks/useAveragesExclusionRatio';
|
||||
import { $tankFilters } from '../../../../stores/tankFilters';
|
||||
import { TankPerformanceEphemeral } from '../../../../stores/tankPerformanceEphemeral';
|
||||
import { ThemedBar } from '../../../Nivo/ThemedBar';
|
||||
|
||||
const averageDefinitions = await fetchAverageDefinitions();
|
||||
const tankDefinitions = await fetchTankDefinitions();
|
||||
const [tankDefinitions, averageDefinitions] = await Promise.all([
|
||||
awaitableTankDefinitions,
|
||||
awaitableAverageDefinitions,
|
||||
]);
|
||||
|
||||
const averageDefinitionsArray = Object.entries(averageDefinitions.averages);
|
||||
|
||||
export function PlayersByTierAndClass() {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import {
|
||||
fetchAverageDefinitions,
|
||||
fetchDiscoveredIdsDefinitions,
|
||||
formatCompact,
|
||||
} from '@blitzkit/core';
|
||||
import { formatCompact } from '@blitzkit/core';
|
||||
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
||||
import { Callout, Flex } from '@radix-ui/themes';
|
||||
import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions';
|
||||
import { awaitableDiscoveredIdsDefinitions } from '../../core/awaitables/discoveredIdsDefinitions';
|
||||
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
|
||||
import { InlineSkeleton } from '../InlineSkeleton';
|
||||
|
||||
const discoveredIdsDefinitions = await fetchDiscoveredIdsDefinitions();
|
||||
const averageDefinitions = await fetchAverageDefinitions();
|
||||
const [discoveredIdsDefinitions, averageDefinitions] = await Promise.all([
|
||||
awaitableDiscoveredIdsDefinitions,
|
||||
awaitableAverageDefinitions,
|
||||
]);
|
||||
|
||||
export function PerformanceInfo({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const samples = formatCompact(Math.round(discoveredIdsDefinitions.count));
|
||||
|
||||
@@ -6,11 +6,16 @@ import { Tanks } from './Tanks';
|
||||
|
||||
export function TankTable({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
return (
|
||||
<Flex justify="center">
|
||||
<Flex justify="center" maxWidth="100%" flexGrow="1" position="relative">
|
||||
<StickyTableRoot
|
||||
size={{ initial: '1', sm: '2' }}
|
||||
variant="surface"
|
||||
style={{ maxWidth: '100%' }}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Header />
|
||||
<Tanks skeleton={skeleton} />
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import {
|
||||
TankDefinition,
|
||||
fetchAverageDefinitions,
|
||||
formatCompact,
|
||||
} from '@blitzkit/core';
|
||||
import { TankDefinition, formatCompact } from '@blitzkit/core';
|
||||
import { Table } from '@radix-ui/themes';
|
||||
import { memo } from 'react';
|
||||
import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions';
|
||||
import { useAveragesExclusionRatio } from '../../hooks/useAveragesExclusionRatio';
|
||||
import { TankPerformanceEphemeral } from '../../stores/tankPerformanceEphemeral';
|
||||
import { TankRowHeaderCell } from '../TankRowHeaderCell';
|
||||
@@ -13,7 +10,7 @@ interface TankRowProps {
|
||||
tank: TankDefinition;
|
||||
}
|
||||
|
||||
const averageDefinitions = await fetchAverageDefinitions();
|
||||
const averageDefinitions = await awaitableAverageDefinitions;
|
||||
|
||||
export const TankRow = memo<TankRowProps>(
|
||||
({ tank }) => {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { fetchAverageDefinitions, fetchTankDefinitions } from '@blitzkit/core';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { Table } from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import { Fragment, Suspense, useEffect, useMemo, useState } from 'react';
|
||||
import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions';
|
||||
import { awaitableTankDefinitions } from '../../core/awaitables/tankDefinitions';
|
||||
import { filterTank } from '../../core/blitzkit/filterTank';
|
||||
import { $tankFilters } from '../../stores/tankFilters';
|
||||
import { TankPerformanceEphemeral } from '../../stores/tankPerformanceEphemeral';
|
||||
@@ -15,13 +16,16 @@ import { Total } from './Total';
|
||||
const PREVIEW_COUNT = 10;
|
||||
const DEFAULT_LOADED_ROWS = 25;
|
||||
|
||||
const tankDefinitions = await fetchTankDefinitions();
|
||||
const averageDefinitionsArray = await fetchAverageDefinitions().then(
|
||||
({ averages }) =>
|
||||
Object.entries(averages).map(([id, average]) => ({
|
||||
id: Number(id),
|
||||
...average,
|
||||
})),
|
||||
const [tankDefinitions, averageDefinitions] = await Promise.all([
|
||||
awaitableTankDefinitions,
|
||||
awaitableAverageDefinitions,
|
||||
]);
|
||||
|
||||
const averageDefinitionsArray = Object.entries(averageDefinitions.averages).map(
|
||||
([id, average]) => ({
|
||||
id: Number(id),
|
||||
...average,
|
||||
}),
|
||||
);
|
||||
|
||||
export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {
|
||||
AverageDefinitionsEntry,
|
||||
fetchAverageDefinitions,
|
||||
formatCompact,
|
||||
type BlitzkitStats,
|
||||
} from '@blitzkit/core';
|
||||
import { Table } from '@radix-ui/themes';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions';
|
||||
import { useAveragesExclusionRatio } from '../../hooks/useAveragesExclusionRatio';
|
||||
import { TankPerformanceEphemeral } from '../../stores/tankPerformanceEphemeral';
|
||||
import { StickyRowHeaderCell } from '../StickyRowHeaderCell';
|
||||
@@ -18,7 +18,7 @@ interface TotalProps {
|
||||
tanks: AverageDefinitionsEntryWithId[];
|
||||
}
|
||||
|
||||
const averageDefinitions = await fetchAverageDefinitions();
|
||||
const averageDefinitions = await awaitableAverageDefinitions;
|
||||
|
||||
export const Total = memo<TotalProps>(
|
||||
({ tanks }) => {
|
||||
|
||||
@@ -23,9 +23,10 @@
|
||||
|
||||
.tank-search-card .image {
|
||||
width: 100%;
|
||||
height: 4rem;
|
||||
height: 6rem;
|
||||
object-position: center right;
|
||||
object-fit: contain;
|
||||
filter: brightness(1.25) drop-shadow(-1px -1px 4px black);
|
||||
}
|
||||
|
||||
.tank-search-card .class-icon {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { asset, tankIcon, TankType, type TankDefinition } from '@blitzkit/core';
|
||||
import { asset, TankType, type TankDefinition } from '@blitzkit/core';
|
||||
import { Flex, Link, Text, type TextProps } from '@radix-ui/themes';
|
||||
import { uniq } from 'lodash-es';
|
||||
import { forwardRef } from 'react';
|
||||
import { forwardRef, type ReactNode } from 'react';
|
||||
import { TankopediaPersistent } from '../../stores/tankopediaPersistent';
|
||||
import { classIcons } from '../ClassIcon';
|
||||
import { MAX_RECENTLY_VIEWED } from '../TankSearch/constants';
|
||||
@@ -10,7 +10,7 @@ import './index.css';
|
||||
type TankCardProps = TextProps & {
|
||||
tank: TankDefinition;
|
||||
onTankSelect?: (tank: TankDefinition) => void;
|
||||
discriminator?: string;
|
||||
discriminator?: ReactNode;
|
||||
noLink?: boolean;
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
||||
<Text
|
||||
ref={ref}
|
||||
tabIndex={onSelect ? 0 : undefined}
|
||||
size="1"
|
||||
size="2"
|
||||
color={
|
||||
tank.type === TankType.COLLECTOR
|
||||
? 'blue'
|
||||
@@ -63,11 +63,14 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
||||
<Link
|
||||
className="link"
|
||||
underline="hover"
|
||||
href={provideLink ? `/tools/tankopedia/${tank.id}` : undefined}
|
||||
href={provideLink ? `/tools/tankopedia/${tank.id}` : '#'}
|
||||
onClick={(event) => {
|
||||
if (!provideLink) event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={tank.name}
|
||||
src={tankIcon(tank.id)}
|
||||
src={asset(`icons/tanks/blitzkit/${tank.id}.webp`)}
|
||||
className="image"
|
||||
draggable={false}
|
||||
/>
|
||||
@@ -78,6 +81,7 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
||||
align="center"
|
||||
overflow="hidden"
|
||||
width="100%"
|
||||
mt="1"
|
||||
>
|
||||
<Icon className="class-icon" />
|
||||
<Text align="center" className="name">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
fetchModelDefinitions,
|
||||
normalizeBoundingBox,
|
||||
resolveDpm,
|
||||
unionBoundingBox,
|
||||
@@ -7,6 +6,7 @@ import {
|
||||
} from '@blitzkit/core';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useMemo } from 'react';
|
||||
import { awaitableModelDefinitions } from '../../../core/awaitables/modelDefinitions';
|
||||
import { resolveReload } from '../../../core/blitzkit/resolveReload';
|
||||
import { $tankopediaSort } from '../../../stores/tankopediaSort';
|
||||
import { TankCard } from '../../TankCard';
|
||||
@@ -16,7 +16,7 @@ interface TankSearchCardProps {
|
||||
onSelect?: (tank: TankDefinition) => void;
|
||||
}
|
||||
|
||||
const modelDefinitions = await fetchModelDefinitions();
|
||||
const modelDefinitions = await awaitableModelDefinitions;
|
||||
|
||||
export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
|
||||
const tankopediaSort = useStore($tankopediaSort);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
asset,
|
||||
fetchGameDefinitions,
|
||||
TANK_CLASSES,
|
||||
TankType,
|
||||
TIER_ROMAN_NUMERALS,
|
||||
@@ -9,6 +8,7 @@ import { useStore } from '@nanostores/react';
|
||||
import { ReloadIcon } from '@radix-ui/react-icons';
|
||||
import { Box, Flex, IconButton, Text } from '@radix-ui/themes';
|
||||
import { times } from 'lodash-es';
|
||||
import { awaitableGameDefinitions } from '../../../core/awaitables/gameDefinitions';
|
||||
import { $tankFilters, initialTankFilters } from '../../../stores/tankFilters';
|
||||
import {
|
||||
$tankopediaSort,
|
||||
@@ -27,7 +27,7 @@ interface FilterControlProps {
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
const gameDefinitions = await fetchGameDefinitions();
|
||||
const gameDefinitions = await awaitableGameDefinitions;
|
||||
|
||||
export function FilterControl({ compact }: FilterControlProps) {
|
||||
const tankFilters = useStore($tankFilters);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { fetchTankDefinitions } from '@blitzkit/core';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { Flex, Separator, Text } from '@radix-ui/themes';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { useMemo } from 'react';
|
||||
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
|
||||
import { $tankFilters, initialTankFilters } from '../../../stores/tankFilters';
|
||||
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
||||
import { $tankopediaSort } from '../../../stores/tankopediaSort';
|
||||
import { TankCard } from '../../TankCard';
|
||||
import { TankCardWrapper } from './TankCardWrapper';
|
||||
|
||||
const tankDefinitions = await fetchTankDefinitions();
|
||||
const tankDefinitions = await awaitableTankDefinitions;
|
||||
|
||||
export function RecentlyViewed() {
|
||||
const tankopediaPersistentStore = TankopediaPersistent.useStore();
|
||||
|
||||
@@ -16,7 +16,7 @@ export const SkeletonTankCard = memo(
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2" ref={card}>
|
||||
<Skeleton height="64px" />
|
||||
<Skeleton height="5rem" />
|
||||
<Skeleton height="1em" />
|
||||
</Flex>
|
||||
);
|
||||
|
||||
Loaded 100 of 160 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user