mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
@@ -24,6 +24,9 @@ env:
|
|||||||
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
|
||||||
ALLOW_ROBOTS: true
|
ALLOW_ROBOTS: true
|
||||||
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
|
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:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -31,6 +34,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
|
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: oven-sh/setup-bun@v1
|
||||||
- uses: actions/configure-pages@v5
|
- uses: actions/configure-pages@v5
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,22 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
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:
|
jobs:
|
||||||
host:
|
host:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: oven-sh/setup-bun@v1
|
- uses: oven-sh/setup-bun@v1
|
||||||
with:
|
# with:
|
||||||
bun-version: 1.1.33
|
# bun-version: 1.1.33
|
||||||
|
|
||||||
- name: Download release assets
|
- name: Download release assets
|
||||||
run: |
|
run: |
|
||||||
@@ -32,12 +40,11 @@ jobs:
|
|||||||
mkdir -p dist/bot
|
mkdir -p dist/bot
|
||||||
unzip bot.zip -d 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
|
- name: Run bot
|
||||||
env:
|
run: bun run dist/bot/main.js
|
||||||
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
|
|
||||||
@@ -19,8 +19,10 @@ jobs:
|
|||||||
- name: Generate leaderboard
|
- name: Generate leaderboard
|
||||||
env:
|
env:
|
||||||
PUBLIC_ASSET_BRANCH: main
|
PUBLIC_ASSET_BRANCH: main
|
||||||
|
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||||
|
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||||
run: bun build:ratingleaderboard --region=asia
|
run: bun build:ratingleaderboard --region=asia
|
||||||
@@ -19,8 +19,10 @@ jobs:
|
|||||||
- name: Generate leaderboard
|
- name: Generate leaderboard
|
||||||
env:
|
env:
|
||||||
PUBLIC_ASSET_BRANCH: main
|
PUBLIC_ASSET_BRANCH: main
|
||||||
|
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||||
|
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||||
run: bun build:ratingleaderboard --region=eu
|
run: bun build:ratingleaderboard --region=eu
|
||||||
@@ -19,8 +19,10 @@ jobs:
|
|||||||
- name: Generate leaderboard
|
- name: Generate leaderboard
|
||||||
env:
|
env:
|
||||||
PUBLIC_ASSET_BRANCH: main
|
PUBLIC_ASSET_BRANCH: main
|
||||||
|
PUBLIC_ASSET_REPO: ${{ secrets.PUBLIC_ASSET_REPO }}
|
||||||
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||||
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
||||||
|
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
|
||||||
run: bun build:ratingleaderboard --region=com
|
run: bun build:ratingleaderboard --region=com
|
||||||
Vendored
+3
@@ -7,6 +7,8 @@
|
|||||||
"adsense",
|
"adsense",
|
||||||
"aeson",
|
"aeson",
|
||||||
"alphatest",
|
"alphatest",
|
||||||
|
"analyticsdata",
|
||||||
|
"analyticsreporting",
|
||||||
"APAC",
|
"APAC",
|
||||||
"apcr",
|
"apcr",
|
||||||
"applypatches",
|
"applypatches",
|
||||||
@@ -94,6 +96,7 @@
|
|||||||
"leaderboards",
|
"leaderboards",
|
||||||
"lerp",
|
"lerp",
|
||||||
"LINEARSIZE",
|
"LINEARSIZE",
|
||||||
|
"lmao",
|
||||||
"loadouts",
|
"loadouts",
|
||||||
"loddist",
|
"loddist",
|
||||||
"lods",
|
"lods",
|
||||||
|
|||||||
@@ -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
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "blitzkit",
|
"name": "blitzkit",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.26.0",
|
"version": "1.26.3",
|
||||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "./types.d.ts",
|
"types": "./types.d.ts",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"@blitzkit/core": "workspace:*",
|
"@blitzkit/core": "workspace:*",
|
||||||
"satori": "^0.10.14",
|
"satori": "^0.10.14",
|
||||||
"@resvg/resvg-js": "^2.6.2",
|
"@resvg/resvg-js": "^2.6.2",
|
||||||
"discord.js": "^14.15.3",
|
"discord.js": "^14.16.3",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"@radix-ui/colors": "^3.0.0",
|
"@radix-ui/colors": "^3.0.0",
|
||||||
"fuzzysort": "^2.0.4",
|
"fuzzysort": "^2.0.4",
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
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';
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 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),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return `${
|
||||||
|
index + 1
|
||||||
|
}. ${name} <:gold:1317173197082333244> ${data.price!.value.toLocaleString(
|
||||||
|
interaction.locale,
|
||||||
|
)}\n-# ${available}\n-# ${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 { client } from '../core/discord/client';
|
||||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||||
import { translator } from '../core/localization/translator';
|
import { translator } from '../core/localization/translator';
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
getAccountInfo,
|
getAccountInfo,
|
||||||
getTankStats,
|
getTankStats,
|
||||||
} from '@blitzkit/core';
|
} from '@blitzkit/core';
|
||||||
import { chunk } from 'lodash-es';
|
|
||||||
import markdownEscape from 'markdown-escape';
|
import markdownEscape from 'markdown-escape';
|
||||||
import {
|
import {
|
||||||
gameDefinitions,
|
gameDefinitions,
|
||||||
@@ -15,13 +14,12 @@ import {
|
|||||||
import { addTierChoices } from '../core/discord/addTierChoices';
|
import { addTierChoices } from '../core/discord/addTierChoices';
|
||||||
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
import { addUsernameChoices } from '../core/discord/addUsernameChoices';
|
||||||
import { autocompleteUsername } from '../core/discord/autocompleteUsername';
|
import { autocompleteUsername } from '../core/discord/autocompleteUsername';
|
||||||
|
import { chunkLines } from '../core/discord/chunkLines';
|
||||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||||
import { resolvePlayerFromCommand } from '../core/discord/resolvePlayerFromCommand';
|
import { resolvePlayerFromCommand } from '../core/discord/resolvePlayerFromCommand';
|
||||||
import { translator } from '../core/localization/translator';
|
import { translator } from '../core/localization/translator';
|
||||||
import { CommandRegistry } from '../events/interactionCreate';
|
import { CommandRegistry } from '../events/interactionCreate';
|
||||||
|
|
||||||
const TANKS_PER_MESSAGE = 64;
|
|
||||||
|
|
||||||
export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
||||||
resolve({
|
resolve({
|
||||||
command: createLocalizedCommand('owned-tanks')
|
command: createLocalizedCommand('owned-tanks')
|
||||||
@@ -61,7 +59,11 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
const awaitedTankDefinitions = await tankDefinitions;
|
const awaitedTankDefinitions = await tankDefinitions;
|
||||||
const awaitedGameDefinitions = await gameDefinitions;
|
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])
|
.map(({ tank_id }) => awaitedTankDefinitions.tanks[tank_id])
|
||||||
.filter((tank) => tank.tier === tier)
|
.filter((tank) => tank.tier === tier)
|
||||||
.sort(
|
.sort(
|
||||||
@@ -73,16 +75,13 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
awaitedGameDefinitions.nations.indexOf(a.nation) -
|
awaitedGameDefinitions.nations.indexOf(a.nation) -
|
||||||
awaitedGameDefinitions.nations.indexOf(b.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) =>
|
return chunkLines(lines);
|
||||||
index === 0
|
|
||||||
? `${translate('bot.commands.owned_tanks.body.title', [
|
|
||||||
markdownEscape(accountInfo.nickname),
|
|
||||||
TIER_ROMAN_NUMERALS[tier],
|
|
||||||
])}${lines.join('\n')}`
|
|
||||||
: lines.join('\n'),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
autocomplete: autocompleteUsername,
|
autocomplete: autocompleteUsername,
|
||||||
|
|||||||
@@ -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 {
|
import {
|
||||||
|
fetchDiscoveredIdsDefinitions,
|
||||||
fetchGameDefinitions,
|
fetchGameDefinitions,
|
||||||
fetchMapDefinitions,
|
fetchMapDefinitions,
|
||||||
fetchTankDefinitions,
|
fetchTankDefinitions,
|
||||||
@@ -9,3 +10,4 @@ export const tankDefinitions = fetchTankDefinitions();
|
|||||||
export const gameDefinitions = fetchGameDefinitions();
|
export const gameDefinitions = fetchGameDefinitions();
|
||||||
export const tankNames = fetchTankNames();
|
export const tankNames = fetchTankNames();
|
||||||
export const mapDefinitions = fetchMapDefinitions();
|
export const mapDefinitions = fetchMapDefinitions();
|
||||||
|
export const discoveredIdsDefinitions = fetchDiscoveredIdsDefinitions();
|
||||||
@@ -50,17 +50,15 @@ export function addFilterOptions<
|
|||||||
.addStringOption((option) => addTierChoices(option).setRequired(false))
|
.addStringOption((option) => addTierChoices(option).setRequired(false))
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
.setName(translate('bot.common.options.filters.tank_class'))
|
.setName(translate('bot.common.options.filters.class'))
|
||||||
.setNameLocalizations(
|
.setNameLocalizations(
|
||||||
localizationObject('bot.common.options.filters.tank_class'),
|
localizationObject('bot.common.options.filters.class'),
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
translate('bot.common.options.filters.tank_class.description'),
|
translate('bot.common.options.filters.class.description'),
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject('bot.common.options.filters.class.description'),
|
||||||
'bot.common.options.filters.tank_class.description',
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.addChoices(
|
.addChoices(
|
||||||
...TANK_CLASSES.map(
|
...TANK_CLASSES.map(
|
||||||
@@ -78,17 +76,15 @@ export function addFilterOptions<
|
|||||||
)
|
)
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
.setName(translate('bot.common.options.filters.tree_type'))
|
.setName(translate('bot.common.options.filters.type'))
|
||||||
.setNameLocalizations(
|
.setNameLocalizations(
|
||||||
localizationObject('bot.common.options.filters.tree_type'),
|
localizationObject('bot.common.options.filters.type'),
|
||||||
)
|
)
|
||||||
.setDescription(
|
.setDescription(
|
||||||
translate('bot.common.options.filters.tree_type.description'),
|
translate('bot.common.options.filters.type.description'),
|
||||||
)
|
)
|
||||||
.setDescriptionLocalizations(
|
.setDescriptionLocalizations(
|
||||||
localizationObject(
|
localizationObject('bot.common.options.filters.type.description'),
|
||||||
'bot.common.options.filters.tree_type.description',
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.addChoices(
|
.addChoices(
|
||||||
...TANK_TYPES.map(
|
...TANK_TYPES.map(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -2,5 +2,5 @@ import { assertSecret } from '@blitzkit/core';
|
|||||||
import { ShardingManager } from 'discord.js';
|
import { ShardingManager } from 'discord.js';
|
||||||
|
|
||||||
export const manager = new ShardingManager('dist/bot/workers/bot.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 { ChatInputCommandInteraction } from 'discord.js';
|
||||||
import markdownEscape from 'markdown-escape';
|
import markdownEscape from 'markdown-escape';
|
||||||
|
import { searchClansAcrossRegionsBotWrapper } from '../blitz/searchClansAcrossRegionsBotWrapper';
|
||||||
import { UserError } from '../blitzkit/userError';
|
import { UserError } from '../blitzkit/userError';
|
||||||
import { translator } from '../localization/translator';
|
import { translator } from '../localization/translator';
|
||||||
|
|
||||||
@@ -16,7 +17,10 @@ export async function resolveClanFromCommand(
|
|||||||
const [server, accountId] = clan.split('/');
|
const [server, accountId] = clan.split('/');
|
||||||
return { region: server as Region, id: Number(accountId) };
|
return { region: server as Region, id: Number(accountId) };
|
||||||
} else {
|
} else {
|
||||||
const accounts = await searchClansAcrossRegions(clan);
|
const accounts = await searchClansAcrossRegionsBotWrapper(
|
||||||
|
clan,
|
||||||
|
interaction.locale,
|
||||||
|
);
|
||||||
|
|
||||||
if (accounts[0]) {
|
if (accounts[0]) {
|
||||||
return { region: accounts[0].region, id: accounts[0].clan_id };
|
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 { CacheType, ChatInputCommandInteraction } from 'discord.js';
|
||||||
import markdownEscape from 'markdown-escape';
|
import markdownEscape from 'markdown-escape';
|
||||||
|
import { searchPlayersAcrossRegionsBotWrapper } from '../../blitz/searchPlayersAcrossRegionsBotWrapper';
|
||||||
import { getBlitzFromDiscord } from '../../blitzkit/getBlitzFromDiscord';
|
import { getBlitzFromDiscord } from '../../blitzkit/getBlitzFromDiscord';
|
||||||
import { UserError } from '../../blitzkit/userError';
|
import { UserError } from '../../blitzkit/userError';
|
||||||
import { translator } from '../../localization/translator';
|
import { translator } from '../../localization/translator';
|
||||||
@@ -23,7 +24,10 @@ export async function resolvePlayerFromCommand(
|
|||||||
const region = idToRegion(id);
|
const region = idToRegion(id);
|
||||||
return { region, id } satisfies ResolvedPlayer;
|
return { region, id } satisfies ResolvedPlayer;
|
||||||
} else {
|
} else {
|
||||||
const accounts = await searchPlayersAcrossRegions(commandUsername);
|
const accounts = await searchPlayersAcrossRegionsBotWrapper(
|
||||||
|
commandUsername,
|
||||||
|
interaction.locale,
|
||||||
|
);
|
||||||
|
|
||||||
if (accounts[0]) {
|
if (accounts[0]) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const translations = SUPPORTED_LOCALES.reduce<
|
|||||||
>(
|
>(
|
||||||
(table, locale) => ({
|
(table, locale) => ({
|
||||||
...table,
|
...table,
|
||||||
[locale]: require(`@blitzkit/core/lang/${locale}.json`),
|
[locale]: require(`../../../../../core/lang/${locale}.json`),
|
||||||
}),
|
}),
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
SlashCommandSubcommandsOnlyBuilder,
|
SlashCommandSubcommandsOnlyBuilder,
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import { aboutCommand } from '../../commands/about';
|
import { aboutCommand } from '../../commands/about';
|
||||||
|
import { auctionCommand } from '../../commands/auction';
|
||||||
import { breakdownCommand } from '../../commands/breakdown';
|
import { breakdownCommand } from '../../commands/breakdown';
|
||||||
import { debugCommand } from '../../commands/debug';
|
import { debugCommand } from '../../commands/debug';
|
||||||
import { evolutionCommand } from '../../commands/evolution';
|
import { evolutionCommand } from '../../commands/evolution';
|
||||||
@@ -76,6 +77,7 @@ interface CommandRegistryExplicit extends CommandRegistryBase {
|
|||||||
export type CommandRegistry = CommandRegistryImplicit | CommandRegistryExplicit;
|
export type CommandRegistry = CommandRegistryImplicit | CommandRegistryExplicit;
|
||||||
|
|
||||||
export const COMMANDS_RAW: Promise<CommandRegistry>[] = [
|
export const COMMANDS_RAW: Promise<CommandRegistry>[] = [
|
||||||
|
auctionCommand,
|
||||||
permissionsCommand,
|
permissionsCommand,
|
||||||
debugCommand,
|
debugCommand,
|
||||||
aboutCommand,
|
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) => {
|
commands.then((awaitedCommands) => {
|
||||||
const body = Object.values(awaitedCommands).map((registry) =>
|
const body = Object.values(awaitedCommands).map((registry) =>
|
||||||
@@ -123,7 +125,7 @@ commands.then((awaitedCommands) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
rest.put(
|
rest.put(
|
||||||
Routes.applicationCommands(assertSecret(process.env.DISCORD_CLIENT_ID)),
|
Routes.applicationCommands(assertSecret(import.meta.env.DISCORD_CLIENT_ID)),
|
||||||
{
|
{
|
||||||
body,
|
body,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,29 +1,5 @@
|
|||||||
import { ActivityType, Client } from 'discord.js';
|
import { Client } from 'discord.js';
|
||||||
|
|
||||||
export function ready(client: Client<true>) {
|
export function ready(client: Client<true>) {
|
||||||
console.log(`🟢 Launched bot ${client.shard?.ids[0] ?? 'default'}`);
|
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)](),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,67 @@
|
|||||||
|
import { ActivityType } from 'discord.js';
|
||||||
|
import {
|
||||||
|
discoveredIdsDefinitions,
|
||||||
|
tankDefinitions,
|
||||||
|
} from './core/blitzkit/nonBlockingPromises';
|
||||||
import { manager } from './core/discord/manager';
|
import { manager } from './core/discord/manager';
|
||||||
|
|
||||||
manager
|
console.log('pre manager event listeners and spawn');
|
||||||
|
|
||||||
|
const shards = await manager
|
||||||
.on('shardCreate', (shard) => {
|
.on('shardCreate', (shard) => {
|
||||||
console.log(`🟡 Launching shard ${shard.id}`);
|
console.log(`🟡 Launching shard ${shard.id}`);
|
||||||
shard.on('ready', () => console.log(`🟢 Launched shard ${shard.id}`));
|
shard.on('ready', () => console.log(`🟢 Launched shard ${shard.id}`));
|
||||||
})
|
})
|
||||||
.spawn();
|
.spawn();
|
||||||
|
|
||||||
|
console.log('post manager event listeners and spawn');
|
||||||
|
|
||||||
|
try {
|
||||||
|
let servers = 0;
|
||||||
|
let channels = 0;
|
||||||
|
let users = 0;
|
||||||
|
|
||||||
|
for (const [, shard] of shards) {
|
||||||
|
// wtf is this typing bro???
|
||||||
|
const guilds = (await shard.fetchClientValue('guilds.cache')) as any;
|
||||||
|
|
||||||
|
servers += guilds.length;
|
||||||
|
|
||||||
|
for (const guild of guilds) {
|
||||||
|
channels += guild.channels.length;
|
||||||
|
users += guild.memberCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const awaitedTankDefinitions = await tankDefinitions;
|
||||||
|
const tanks = Object.values(awaitedTankDefinitions.tanks).length;
|
||||||
|
const awaitedDiscoveredIdsDefinitions = await discoveredIdsDefinitions;
|
||||||
|
const usernames = awaitedDiscoveredIdsDefinitions.count;
|
||||||
|
|
||||||
|
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 } },
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
'An error occurred when setting up presence, ignoring to keep the process alive',
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,4 +6,8 @@ console.log(`🟡 Launching bot ${client.shard?.ids[0]}`);
|
|||||||
|
|
||||||
registerProcesses();
|
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);
|
const png = svgToPng(svg);
|
||||||
parentPort?.postMessage(png);
|
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.",
|
"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",
|
"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.",
|
"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": {
|
"wn8": {
|
||||||
@@ -230,13 +232,13 @@
|
|||||||
"$": "nation",
|
"$": "nation",
|
||||||
"description": "Nation"
|
"description": "Nation"
|
||||||
},
|
},
|
||||||
"tank_class": {
|
"class": {
|
||||||
"$": "tank-class",
|
"$": "class",
|
||||||
"description": "Tank class"
|
"description": "Class"
|
||||||
},
|
},
|
||||||
"tree_type": {
|
"type": {
|
||||||
"$": "tree-type",
|
"$": "type",
|
||||||
"description": "Tree type"
|
"description": "Type"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -291,6 +293,24 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"commands": {
|
"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": {
|
||||||
"$": "replay",
|
"$": "replay",
|
||||||
"description": "Display replay results"
|
"description": "Display replay results"
|
||||||
|
|||||||
@@ -263,6 +263,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"commands": {
|
"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": {
|
||||||
"$": "replay",
|
"$": "replay",
|
||||||
"description": "Display replay results"
|
"description": "Display replay results"
|
||||||
@@ -323,9 +339,9 @@
|
|||||||
"$": "zonas-horarias",
|
"$": "zonas-horarias",
|
||||||
"description": "Cómo Blitzkrieg infiere y utiliza tu zona horaria"
|
"description": "Cómo Blitzkrieg infiere y utiliza tu zona horaria"
|
||||||
},
|
},
|
||||||
"creators": {
|
"credits": {
|
||||||
"$": "creadores",
|
"$": "credits",
|
||||||
"description": "Personas que han trabajado en el bot"
|
"description": "People who have helped BlitzKit"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -263,6 +263,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"commands": {
|
"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": {
|
||||||
"$": "replay",
|
"$": "replay",
|
||||||
"description": "Display replay results"
|
"description": "Display replay results"
|
||||||
@@ -323,9 +339,9 @@
|
|||||||
"$": "vremenske zone",
|
"$": "vremenske zone",
|
||||||
"description": "Kako \"Blitzkrieg\" koristi podatke o tvojoj vremenskoj zoni"
|
"description": "Kako \"Blitzkrieg\" koristi podatke o tvojoj vremenskoj zoni"
|
||||||
},
|
},
|
||||||
"creators": {
|
"credits": {
|
||||||
"$": "stvarači",
|
"$": "credits",
|
||||||
"description": "Ljudi koji su radili na robotu"
|
"description": "People who have helped BlitzKit"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,601 @@
|
|||||||
|
{
|
||||||
|
"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."
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"tank_class": {
|
||||||
|
"$": "tank-class",
|
||||||
|
"description": "Tank class"
|
||||||
|
},
|
||||||
|
"tree_type": {
|
||||||
|
"$": "tree-type",
|
||||||
|
"description": "Tree 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,601 @@
|
|||||||
|
{
|
||||||
|
"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."
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"tank_class": {
|
||||||
|
"$": "tank-class",
|
||||||
|
"description": "Tank class"
|
||||||
|
},
|
||||||
|
"tree_type": {
|
||||||
|
"$": "tree-type",
|
||||||
|
"description": "Tree 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,601 @@
|
|||||||
|
{
|
||||||
|
"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."
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"tank_class": {
|
||||||
|
"$": "tank-class",
|
||||||
|
"description": "Tank class"
|
||||||
|
},
|
||||||
|
"tree_type": {
|
||||||
|
"$": "tree-type",
|
||||||
|
"description": "Tree 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,5 @@
|
|||||||
|
export type BlitzTankClass =
|
||||||
|
| 'lightTank'
|
||||||
|
| 'mediumTank'
|
||||||
|
| 'heavyTank'
|
||||||
|
| 'AT-SPG';
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export * from './canSplash';
|
export * from './canSplash';
|
||||||
export * from './clans';
|
export * from './clans';
|
||||||
|
export * from './classes';
|
||||||
export * from './constants';
|
export * from './constants';
|
||||||
export * from './equipmentPrice';
|
export * from './equipmentPrice';
|
||||||
export * from './fetchBlitz';
|
export * from './fetchBlitz';
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export async function isValidBlitzId(id: number, token?: string) {
|
|||||||
|
|
||||||
const json = await fetch(
|
const json = await fetch(
|
||||||
`https://api.wotblitz.${region}/wotb/account/info/?account_id=${id}&application_id=${assertSecret(
|
`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}`
|
token === undefined ? '' : `&access_token=${token}`
|
||||||
}&fields=account_id%2C-account_id`,
|
}&fields=account_id%2C-account_id`,
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ export type AccountListWithServer = (AccountListItem & {
|
|||||||
})[];
|
})[];
|
||||||
|
|
||||||
export async function searchPlayersAcrossRegions(search: string, limit = 25) {
|
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);
|
const normalizedLimit = Math.floor(limit / 3);
|
||||||
|
|
||||||
if (usernamePattern.test(trimmedSearch)) {
|
if (usernamePattern.test(trimmed)) {
|
||||||
return (
|
return (
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetchBlitz<AccountList>('com', 'account/list', {
|
fetchBlitz<AccountList>('com', 'account/list', {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export async function getArchivedLatestSeasonNumber() {
|
|||||||
const regionsURL = (
|
const regionsURL = (
|
||||||
(await fetch(
|
(await fetch(
|
||||||
`https://api.github.com/repos/tresabhi/${assertSecret(
|
`https://api.github.com/repos/tresabhi/${assertSecret(
|
||||||
process.env.PUBLIC_ASSET_REPO,
|
import.meta.env.PUBLIC_ASSET_REPO,
|
||||||
)}/git/trees/main`,
|
)}/git/trees/main`,
|
||||||
).then((response) => response.json())) as GitHubTrees
|
).then((response) => response.json())) as GitHubTrees
|
||||||
).tree.find(({ path }) => path === 'regions')!.url;
|
).tree.find(({ path }) => path === 'regions')!.url;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { RatingStats } from '../blitz/getAccountInfo';
|
import { RatingStats } from '../blitz/getAccountInfo';
|
||||||
import { BkrlSuperset1Entry } from '../streams/bkrl';
|
import { RatingLeaderboardEntryV2 } from '../protos';
|
||||||
|
|
||||||
export function deltaBkrlBlitzStats(
|
export function deltaBkrlBlitzStats(
|
||||||
a: BkrlSuperset1Entry,
|
a: RatingLeaderboardEntryV2,
|
||||||
b1: RatingStats,
|
b1: RatingStats,
|
||||||
b2: RatingPlayer,
|
b2: RatingPlayer,
|
||||||
) {
|
) {
|
||||||
@@ -13,7 +13,7 @@ export function deltaBkrlBlitzStats(
|
|||||||
survived: b1.survived_battles - a.survived,
|
survived: b1.survived_battles - a.survived,
|
||||||
wins: b1.wins - a.wins,
|
wins: b1.wins - a.wins,
|
||||||
score: b2.score - a.score,
|
score: b2.score - a.score,
|
||||||
} satisfies Omit<BkrlSuperset1Entry, 'id'>;
|
} satisfies Omit<RatingLeaderboardEntryV2, 'id'>;
|
||||||
}
|
}
|
||||||
export interface RatingPlayer {
|
export interface RatingPlayer {
|
||||||
spa_id: number;
|
spa_id: number;
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
"csv-parse": "^5.5.6",
|
"csv-parse": "^5.5.6",
|
||||||
"decompress": "^4.2.1",
|
"decompress": "^4.2.1",
|
||||||
"fast-xml-parser": "^4.4.1",
|
"fast-xml-parser": "^4.4.1",
|
||||||
"googleapis": "^134.0.0",
|
|
||||||
"progress": "^2.0.3"
|
"progress": "^2.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { assertSecret } from '@blitzkit/core';
|
||||||
import { mkdir, writeFile } from 'fs/promises';
|
import { mkdir, writeFile } from 'fs/promises';
|
||||||
import { parse as parsePath } from 'path';
|
import { parse as parsePath } from 'path';
|
||||||
import ProgressBar from 'progress';
|
import ProgressBar from 'progress';
|
||||||
@@ -6,7 +7,6 @@ import { dvp } from '../../../submodules/blitzkit-closed/src/dvp';
|
|||||||
import { readStringDVPL } from '../src/core/blitz/readStringDVPL';
|
import { readStringDVPL } from '../src/core/blitz/readStringDVPL';
|
||||||
import { DATA } from './buildAssets/constants';
|
import { DATA } from './buildAssets/constants';
|
||||||
import { readYAMLDVPL } from './core/blitz/readYAMLDVPL';
|
import { readYAMLDVPL } from './core/blitz/readYAMLDVPL';
|
||||||
import { assertSecret } from '@blitzkit/core';
|
|
||||||
import { writeDVPL } from './core/blitz/writeDVPL';
|
import { writeDVPL } from './core/blitz/writeDVPL';
|
||||||
|
|
||||||
const versionTextFile = await readStringDVPL(`${DATA}/version.txt.dvpl`);
|
const versionTextFile = await readStringDVPL(`${DATA}/version.txt.dvpl`);
|
||||||
@@ -22,7 +22,7 @@ let patchIndex = 1;
|
|||||||
while (true) {
|
while (true) {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${assertSecret(
|
`${assertSecret(
|
||||||
process.env.WOTB_DLC_CDN,
|
import.meta.env.WOTB_DLC_CDN,
|
||||||
)}/dlc/s${currentVersion}_${patchIndex}.yaml`,
|
)}/dlc/s${currentVersion}_${patchIndex}.yaml`,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -31,10 +31,10 @@ while (true) {
|
|||||||
|
|
||||||
const data = parseYaml(await response.text());
|
const data = parseYaml(await response.text());
|
||||||
const dvpm = await fetch(
|
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());
|
).then((response) => response.arrayBuffer());
|
||||||
const dvpd = await fetch(
|
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',
|
'.dvpm',
|
||||||
'.dvpd',
|
'.dvpd',
|
||||||
)}`,
|
)}`,
|
||||||
@@ -63,7 +63,7 @@ while (true) {
|
|||||||
console.log('Found dynamic content localizations; patching...');
|
console.log('Found dynamic content localizations; patching...');
|
||||||
|
|
||||||
const localizationsResponse = await fetch(
|
const localizationsResponse = await fetch(
|
||||||
`${assertSecret(process.env.WOTB_DLC_CDN)}/dlc/${
|
`${assertSecret(import.meta.env.WOTB_DLC_CDN)}/dlc/${
|
||||||
data.dynamicContentLocalizationsDir
|
data.dynamicContentLocalizationsDir
|
||||||
}/en.yaml`,
|
}/en.yaml`,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ export async function currencies() {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const glossary = await fetch(assertSecret(process.env.WOTB_GLOSSARY)).then(
|
const glossary = await fetch(
|
||||||
(response) => response.json() as Promise<BlitzGlossary>,
|
assertSecret(import.meta.env.WOTB_GLOSSARY),
|
||||||
);
|
).then((response) => response.json() as Promise<BlitzGlossary>);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.entries(glossary)
|
Object.entries(glossary)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Armor,
|
Armor,
|
||||||
assertSecret,
|
assertSecret,
|
||||||
|
BlitzTankClass,
|
||||||
CamouflageDefinitions,
|
CamouflageDefinitions,
|
||||||
Consumable,
|
Consumable,
|
||||||
ConsumableDefinitions,
|
ConsumableDefinitions,
|
||||||
@@ -70,7 +71,6 @@ const blitzTankFilterDefinitionCategoryToBlitzkit: Record<
|
|||||||
function vector3TupleToBlitzkit(tuple: Vector3Tuple) {
|
function vector3TupleToBlitzkit(tuple: Vector3Tuple) {
|
||||||
return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3;
|
return { x: tuple[0], y: tuple[1], z: tuple[2] } satisfies Vector3;
|
||||||
}
|
}
|
||||||
type BlitzTankClass = 'lightTank' | 'mediumTank' | 'heavyTank' | 'AT-SPG';
|
|
||||||
const blitzTankClassToBlitzkit: Record<BlitzTankClass, TankClass> = {
|
const blitzTankClassToBlitzkit: Record<BlitzTankClass, TankClass> = {
|
||||||
lightTank: TankClass.LIGHT,
|
lightTank: TankClass.LIGHT,
|
||||||
'AT-SPG': TankClass.TANK_DESTROYER,
|
'AT-SPG': TankClass.TANK_DESTROYER,
|
||||||
@@ -514,7 +514,7 @@ export async function definitions() {
|
|||||||
|
|
||||||
const wargamingTankopedia = (await fetch(
|
const wargamingTankopedia = (await fetch(
|
||||||
`https://api.wotblitz.com/wotb/encyclopedia/vehicles/?application_id=${assertSecret(
|
`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`,
|
)}&fields=description`,
|
||||||
).then((response) => response.json())) as {
|
).then((response) => response.json())) as {
|
||||||
data: { [key: number]: { description: null | string } };
|
data: { [key: number]: { description: null | string } };
|
||||||
|
|||||||
@@ -25,10 +25,13 @@ build({
|
|||||||
'.node': 'empty',
|
'.node': 'empty',
|
||||||
'.json': 'json',
|
'.json': 'json',
|
||||||
},
|
},
|
||||||
|
external: ['sharp'],
|
||||||
minifyIdentifiers: false, // causes goofy ahh issues
|
minifyIdentifiers: false, // causes goofy ahh issues
|
||||||
minifySyntax: !dev,
|
minifySyntax: !dev,
|
||||||
minifyWhitespace: !dev,
|
minifyWhitespace: !dev,
|
||||||
sourcemap: !dev,
|
sourcemap: !dev,
|
||||||
|
jsxImportSource: 'react',
|
||||||
|
jsx: 'automatic',
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const module of await readdir(`${modulesRoot}/@resvg`)) {
|
for (const module of await readdir(`${modulesRoot}/@resvg`)) {
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import { DdsReadStream, PvrReadStream } from '@blitzkit/core';
|
import { DdsReadStream, PvrReadStream } from '@blitzkit/core';
|
||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
import sharp, { JpegOptions } from 'sharp';
|
import sharp from 'sharp';
|
||||||
import { readDVPLFile } from '../readDVPLFile';
|
import { readDVPLFile } from '../readDVPLFile';
|
||||||
import { TextureMutation } from './constants';
|
import { TextureMutation } from './constants';
|
||||||
|
|
||||||
const jpegOptions: JpegOptions = {
|
|
||||||
quality: 20,
|
|
||||||
mozjpeg: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function readTexture(path: string, mutation?: TextureMutation) {
|
export async function readTexture(path: string, mutation?: TextureMutation) {
|
||||||
const ddsTexturePath = path.replace('.tex', '.dx11.dds.dvpl');
|
const ddsTexturePath = path.replace('.tex', '.dx11.dds.dvpl');
|
||||||
const isDds = existsSync(ddsTexturePath);
|
const isDds = existsSync(ddsTexturePath);
|
||||||
@@ -40,7 +35,7 @@ export async function readTexture(path: string, mutation?: TextureMutation) {
|
|||||||
raw.data[index + 3] = 255;
|
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: {
|
case TextureMutation.RoughnessMetallicness: {
|
||||||
@@ -49,16 +44,16 @@ export async function readTexture(path: string, mutation?: TextureMutation) {
|
|||||||
});
|
});
|
||||||
const metallicness = sharp(raw.data, { raw })
|
const metallicness = sharp(raw.data, { raw })
|
||||||
.extractChannel('green')
|
.extractChannel('green')
|
||||||
.jpeg(jpegOptions)
|
.jpeg()
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
const roughness = sharp(raw.data, { raw })
|
const roughness = sharp(raw.data, { raw })
|
||||||
.extractChannel('alpha')
|
.extractChannel('alpha')
|
||||||
.jpeg(jpegOptions)
|
.jpeg()
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
|
|
||||||
return await newImage
|
return await newImage
|
||||||
.joinChannel(await Promise.all([roughness, metallicness]))
|
.joinChannel(await Promise.all([roughness, metallicness]))
|
||||||
.jpeg(jpegOptions)
|
.jpeg()
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,18 +64,15 @@ export async function readTexture(path: string, mutation?: TextureMutation) {
|
|||||||
*/
|
*/
|
||||||
return await sharp(raw.data, { raw })
|
return await sharp(raw.data, { raw })
|
||||||
.extractChannel('alpha')
|
.extractChannel('alpha')
|
||||||
.jpeg(jpegOptions)
|
.jpeg()
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
case TextureMutation.Albedo: {
|
case TextureMutation.Albedo: {
|
||||||
return await sharp(raw.data, { raw })
|
return await sharp(raw.data, { raw }).removeAlpha().jpeg().toBuffer();
|
||||||
.removeAlpha()
|
|
||||||
.jpeg(jpegOptions)
|
|
||||||
.toBuffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
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;
|
if (changes.length === 0) return;
|
||||||
|
|
||||||
await commitMultipleFiles(
|
await commitMultipleFiles(
|
||||||
assertSecret(process.env.PUBLIC_ASSET_REPO),
|
assertSecret(import.meta.env.PUBLIC_ASSET_REPO),
|
||||||
assertSecret(process.env.PUBLIC_ASSET_BRANCH),
|
assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
|
||||||
`${message} - ${new Date().toDateString()}`,
|
`${message} - ${new Date().toDateString()}`,
|
||||||
changes,
|
changes,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ export async function commitMultipleFiles(
|
|||||||
} else if (response.status === 200) {
|
} else if (response.status === 200) {
|
||||||
const buffer = Buffer.from(await response.arrayBuffer());
|
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;
|
const diff = change.content.length - buffer.length;
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ import { Octokit } from '@octokit/rest';
|
|||||||
Octokit.plugin(throttling);
|
Octokit.plugin(throttling);
|
||||||
|
|
||||||
export const octokit = new Octokit({
|
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 { cloneDeep, uniqBy } from 'lodash-es';
|
||||||
import { commitAssets } from './core/github/commitAssets';
|
import { commitAssets } from './core/github/commitAssets';
|
||||||
|
|
||||||
const MAX_QUERIES = 128;
|
const MAX_QUERIES = 64;
|
||||||
|
|
||||||
console.log('Finding reviews...');
|
console.log('Finding reviews...');
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"@react-three/fiber": "^8.17.10",
|
"@react-three/fiber": "^8.17.10",
|
||||||
"compare-versions": "^6.1.1",
|
"compare-versions": "^6.1.1",
|
||||||
"echarts": "^5.5.1",
|
"echarts": "^5.5.1",
|
||||||
|
"googleapis": "^144.0.0",
|
||||||
"immer": "^10.1.1",
|
"immer": "^10.1.1",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"markdown-to-jsx": "^7.5.0",
|
"markdown-to-jsx": "^7.5.0",
|
||||||
|
|||||||
+4
-1
@@ -156,7 +156,10 @@ export function PrimaryArmorSceneComponent({
|
|||||||
(state) => state.greenPenetration,
|
(state) => state.greenPenetration,
|
||||||
handleGreenPenetrationChange,
|
handleGreenPenetrationChange,
|
||||||
),
|
),
|
||||||
|
tankopediaPersistentStore.subscribe(
|
||||||
|
(state) => state.opaque,
|
||||||
|
handleOpaqueChange,
|
||||||
|
),
|
||||||
duelStore.subscribe(
|
duelStore.subscribe(
|
||||||
(state) => state.protagonist.equipmentMatrix,
|
(state) => state.protagonist.equipmentMatrix,
|
||||||
(equipment) => handleProtagonistEquipmentChange(equipment),
|
(equipment) => handleProtagonistEquipmentChange(equipment),
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import {
|
import { asset, availableProvisions, permanentSkills } from '@blitzkit/core';
|
||||||
asset,
|
|
||||||
availableProvisions,
|
|
||||||
fetchConsumableDefinitions,
|
|
||||||
fetchEquipmentDefinitions,
|
|
||||||
fetchProvisionDefinitions,
|
|
||||||
fetchSkillDefinitions,
|
|
||||||
permanentSkills,
|
|
||||||
} from '@blitzkit/core';
|
|
||||||
import { checkConsumableProvisionInclusivity } from '@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity';
|
import { checkConsumableProvisionInclusivity } from '@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity';
|
||||||
import { ComponentPlaceholderIcon } from '@radix-ui/react-icons';
|
import { ComponentPlaceholderIcon } from '@radix-ui/react-icons';
|
||||||
import { Button, Flex, IconButton, Popover, Table } from '@radix-ui/themes';
|
import { Button, Flex, IconButton, Popover, Table } from '@radix-ui/themes';
|
||||||
import { times } from 'lodash-es';
|
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 type { TankCharacteristics } from '../../core/blitzkit/tankCharacteristics';
|
||||||
import { Var } from '../../core/radix/var';
|
import { Var } from '../../core/radix/var';
|
||||||
import { CompareEphemeral } from '../../stores/compareEphemeral';
|
import { CompareEphemeral } from '../../stores/compareEphemeral';
|
||||||
@@ -31,10 +27,17 @@ interface CompareTableProps {
|
|||||||
stats: TankCharacteristics[];
|
stats: TankCharacteristics[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const skillDefinitions = await fetchSkillDefinitions();
|
const [
|
||||||
const equipmentDefinitions = await fetchEquipmentDefinitions();
|
skillDefinitions,
|
||||||
const consumableDefinitions = await fetchConsumableDefinitions();
|
equipmentDefinitions,
|
||||||
const provisionDefinitions = await fetchProvisionDefinitions();
|
consumableDefinitions,
|
||||||
|
provisionDefinitions,
|
||||||
|
] = await Promise.all([
|
||||||
|
awaitableSkillDefinitions,
|
||||||
|
awaitableEquipmentDefinitions,
|
||||||
|
awaitableConsumableDefinitions,
|
||||||
|
awaitableProvisionDefinitions,
|
||||||
|
]);
|
||||||
|
|
||||||
export function CompareTable({ stats }: CompareTableProps) {
|
export function CompareTable({ stats }: CompareTableProps) {
|
||||||
const crewSkills = CompareEphemeral.use((state) => state.crewSkills);
|
const crewSkills = CompareEphemeral.use((state) => state.crewSkills);
|
||||||
@@ -541,23 +544,25 @@ export function CompareTable({ stats }: CompareTableProps) {
|
|||||||
</Popover.Root>
|
</Popover.Root>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<IconButton
|
{!tank.fixed_camouflage && (
|
||||||
onClick={() => {
|
<IconButton
|
||||||
mutateCompareEphemeral((draft) => {
|
onClick={() => {
|
||||||
draft.members[index].camouflage = !camouflage;
|
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%)',
|
|
||||||
}}
|
}}
|
||||||
/>
|
variant={camouflage ? 'solid' : 'outline'}
|
||||||
</IconButton>
|
>
|
||||||
|
<img
|
||||||
|
src={asset('icons/camo.webp')}
|
||||||
|
style={{
|
||||||
|
width: '1em',
|
||||||
|
height: '1em',
|
||||||
|
transform: 'scale(2) translate(15%, 15%)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { fetchProvisionDefinitions } from '@blitzkit/core';
|
|
||||||
import { PlusIcon, TrashIcon } from '@radix-ui/react-icons';
|
import { PlusIcon, TrashIcon } from '@radix-ui/react-icons';
|
||||||
import { Button, Dialog, Flex, SegmentedControl } from '@radix-ui/themes';
|
import { Button, Dialog, Flex, SegmentedControl } from '@radix-ui/themes';
|
||||||
|
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
|
||||||
import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember';
|
import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember';
|
||||||
import { CompareEphemeral } from '../../stores/compareEphemeral';
|
import { CompareEphemeral } from '../../stores/compareEphemeral';
|
||||||
import {
|
import {
|
||||||
@@ -14,7 +14,7 @@ interface ControlsProps {
|
|||||||
onAddTankDialogOpenChange: (open: boolean) => void;
|
onAddTankDialogOpenChange: (open: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const provisionDefinitions = await fetchProvisionDefinitions();
|
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||||
|
|
||||||
export function Controls({
|
export function Controls({
|
||||||
addTankDialogOpen,
|
addTankDialogOpen,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { fetchProvisionDefinitions } from '@blitzkit/core';
|
|
||||||
import { ExternalLinkIcon, LoopIcon, TrashIcon } from '@radix-ui/react-icons';
|
import { ExternalLinkIcon, LoopIcon, TrashIcon } from '@radix-ui/react-icons';
|
||||||
import { Dialog, Flex, IconButton, Link } from '@radix-ui/themes';
|
import { Dialog, Flex, IconButton, Link } from '@radix-ui/themes';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
|
||||||
import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember';
|
import { tankToCompareMember } from '../../core/blitzkit/tankToCompareMember';
|
||||||
import { CompareEphemeral } from '../../stores/compareEphemeral';
|
import { CompareEphemeral } from '../../stores/compareEphemeral';
|
||||||
import { TankSearch } from '../TankSearch';
|
import { TankSearch } from '../TankSearch';
|
||||||
@@ -11,7 +11,7 @@ interface TankControlProps {
|
|||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const provisionDefinitions = await fetchProvisionDefinitions();
|
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||||
|
|
||||||
export function TankControl({ index, id }: TankControlProps) {
|
export function TankControl({ index, id }: TankControlProps) {
|
||||||
const [switchTankDialogOpen, setSwitchTankDialogOpen] = useState(false);
|
const [switchTankDialogOpen, setSwitchTankDialogOpen] = useState(false);
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import {
|
import { asset, permanentSkills, romanize } from '@blitzkit/core';
|
||||||
asset,
|
|
||||||
fetchSkillDefinitions,
|
|
||||||
permanentSkills,
|
|
||||||
romanize,
|
|
||||||
} from '@blitzkit/core';
|
|
||||||
import { Flex } from '@radix-ui/themes';
|
import { Flex } from '@radix-ui/themes';
|
||||||
|
import { awaitableSkillDefinitions } from '../core/awaitables/skillDefinitions';
|
||||||
import { GenericTankComponentButton } from './ModuleButtons/GenericTankComponentButton';
|
import { GenericTankComponentButton } from './ModuleButtons/GenericTankComponentButton';
|
||||||
|
|
||||||
interface CrewSkillManagerProps {
|
interface CrewSkillManagerProps {
|
||||||
@@ -12,7 +8,7 @@ interface CrewSkillManagerProps {
|
|||||||
onChange?: (skillLevels: Record<string, number>) => void;
|
onChange?: (skillLevels: Record<string, number>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const skillDefinitions = await fetchSkillDefinitions();
|
const skillDefinitions = await awaitableSkillDefinitions;
|
||||||
|
|
||||||
export function CrewSkillManager({
|
export function CrewSkillManager({
|
||||||
skillLevels,
|
skillLevels,
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import {
|
|||||||
compositeStats,
|
compositeStats,
|
||||||
compositeStatsKeys,
|
compositeStatsKeys,
|
||||||
deltaTankStats,
|
deltaTankStats,
|
||||||
fetchAverageDefinitions,
|
|
||||||
fetchTankDefinitions,
|
|
||||||
getTankStats,
|
getTankStats,
|
||||||
idToRegion,
|
idToRegion,
|
||||||
sumCompositeStats,
|
sumCompositeStats,
|
||||||
@@ -15,6 +13,8 @@ import { ReloadIcon } from '@radix-ui/react-icons';
|
|||||||
import { ContextMenu } from '@radix-ui/themes';
|
import { ContextMenu } from '@radix-ui/themes';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { breakdownConfig } from '../../constants/embeds';
|
import { breakdownConfig } from '../../constants/embeds';
|
||||||
|
import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions';
|
||||||
|
import { awaitableTankDefinitions } from '../../core/awaitables/tankDefinitions';
|
||||||
import { EmbedBreakdownPersistent } from '../../stores/embedBreakdownPersistent';
|
import { EmbedBreakdownPersistent } from '../../stores/embedBreakdownPersistent';
|
||||||
import { useEmbedStateCurry } from '../../stores/embedState/utilities';
|
import { useEmbedStateCurry } from '../../stores/embedState/utilities';
|
||||||
import { BreakdownEmbedCard, BreakdownEmbedWrapper } from '../TanksEmbed';
|
import { BreakdownEmbedCard, BreakdownEmbedWrapper } from '../TanksEmbed';
|
||||||
@@ -24,8 +24,10 @@ export const compositeStatsKeysOptions = compositeStatsKeys.map((value) => ({
|
|||||||
value,
|
value,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
const [tankDefinitions, averageDefinitions] = await Promise.all([
|
||||||
const averageDefinitions = await fetchAverageDefinitions();
|
awaitableTankDefinitions,
|
||||||
|
awaitableAverageDefinitions,
|
||||||
|
]);
|
||||||
|
|
||||||
export const fakeCompositeStats = compositeStats(
|
export const fakeCompositeStats = compositeStats(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,27 +1,67 @@
|
|||||||
import { fetchAverageDefinitions, fetchTankDefinitions } from '@blitzkit/core';
|
import { assertSecret } from '@blitzkit/core';
|
||||||
import { Box, Flex, Heading } from '@radix-ui/themes';
|
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 { TankopediaPersistent } from '../stores/tankopediaPersistent';
|
||||||
import { TankCard } from './TankCard';
|
import { TankCard } from './TankCard';
|
||||||
|
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
const [tankDefinitions] = await Promise.all([awaitableTankDefinitions, ,]);
|
||||||
const averageDefinitions = await fetchAverageDefinitions();
|
|
||||||
const tanks = Object.values(tankDefinitions.tanks);
|
|
||||||
const tankAverages = Object.entries(averageDefinitions.averages).map(
|
|
||||||
([id, average]) => ({
|
|
||||||
id: Number(id),
|
|
||||||
...average,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// lol don't ask questions
|
const auth = await google.auth.getClient({
|
||||||
const testTanks = tanks
|
keyFile: import.meta.env.GOOGLE_APPLICATION_CREDENTIALS,
|
||||||
.filter((tank) => tank.testing)
|
scopes: ['https://www.googleapis.com/auth/analytics.readonly'],
|
||||||
.sort((a, b) => b.tier - a.tier);
|
});
|
||||||
const mostPlayedTanks = tankAverages
|
const analytics = google.analyticsdata({ version: 'v1beta', auth });
|
||||||
.sort((a, b) => b.samples.d_1 - a.samples.d_1)
|
const report = await analytics.properties.runReport({
|
||||||
.map(({ id }) => tankDefinitions.tanks[id])
|
property: `properties/${assertSecret(import.meta.env.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID)}`,
|
||||||
.filter((tank) => tank !== undefined && !tank.testing);
|
requestBody: {
|
||||||
const popularTanks = [...testTanks, ...mostPlayedTanks].slice(0, 8);
|
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() {
|
export function HomePageHotTanks() {
|
||||||
return (
|
return (
|
||||||
@@ -33,15 +73,26 @@ export function HomePageHotTanks() {
|
|||||||
|
|
||||||
function Content() {
|
function Content() {
|
||||||
return (
|
return (
|
||||||
<Flex direction="column" gap="5" pt="4" pb="8">
|
<Flex direction="column" pt="4" pb="8">
|
||||||
<Heading align="center" size="5">
|
<Heading align="center" size="5">
|
||||||
Popular tanks
|
Popular tanks
|
||||||
</Heading>
|
</Heading>
|
||||||
|
<Text color="gray" align="center" mb="5" size="2">
|
||||||
|
Last 7 days
|
||||||
|
</Text>
|
||||||
|
|
||||||
<Flex justify="center" gap="4" wrap="wrap">
|
<Flex justify="center" gap="4" wrap="wrap">
|
||||||
{popularTanks.map((tank) => (
|
{hotTanks.map((row) => (
|
||||||
<Box width="7rem" key={tank.id}>
|
<Box width="7rem" key={row.tank.id}>
|
||||||
<TankCard tank={tank} />
|
<TankCard
|
||||||
|
tank={row.tank}
|
||||||
|
discriminator={
|
||||||
|
<Flex align="center" gap="1" justify="center">
|
||||||
|
<EyeOpenIcon />
|
||||||
|
{Math.round(row.views * (30 / 7)).toLocaleString('en-US')}
|
||||||
|
</Flex>
|
||||||
|
}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Flex>
|
</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 { ClockIcon, ReloadIcon } from '@radix-ui/react-icons';
|
||||||
import { Flex, Text } from '@radix-ui/themes';
|
import { Flex, Text } from '@radix-ui/themes';
|
||||||
|
import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions';
|
||||||
import { useDelta } from '../../hooks/useDelta';
|
import { useDelta } from '../../hooks/useDelta';
|
||||||
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
import { GenericTankComponentButton } from './GenericTankComponentButton';
|
||||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
import type { TankComponentButtonProps } from './TankComponentButton';
|
||||||
@@ -11,7 +12,7 @@ interface ConsumableButtonProps extends TankComponentButtonProps {
|
|||||||
cooldown?: number;
|
cooldown?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const consumableDefinitions = await fetchConsumableDefinitions();
|
const consumableDefinitions = await awaitableConsumableDefinitions;
|
||||||
|
|
||||||
export function ConsumableButton({
|
export function ConsumableButton({
|
||||||
consumable,
|
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 { GenericTankComponentButton } from './GenericTankComponentButton';
|
||||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
import type { TankComponentButtonProps } from './TankComponentButton';
|
||||||
|
|
||||||
@@ -6,7 +7,7 @@ interface EquipmentButtonProps extends TankComponentButtonProps {
|
|||||||
equipment: number;
|
equipment: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const equipmentDefinitions = await fetchEquipmentDefinitions();
|
const equipmentDefinitions = await awaitableEquipmentDefinitions;
|
||||||
|
|
||||||
export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) {
|
export function EquipmentButton({ equipment, ...props }: EquipmentButtonProps) {
|
||||||
return (
|
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 { GenericTankComponentButton } from './GenericTankComponentButton';
|
||||||
import type { TankComponentButtonProps } from './TankComponentButton';
|
import type { TankComponentButtonProps } from './TankComponentButton';
|
||||||
|
|
||||||
@@ -6,7 +7,7 @@ interface ProvisionButtonProps extends TankComponentButtonProps {
|
|||||||
provision: number;
|
provision: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const provisionDefinitions = await fetchProvisionDefinitions();
|
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||||
|
|
||||||
export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) {
|
export function ProvisionButton({ provision, ...props }: ProvisionButtonProps) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
---
|
||||||
|
import { assertSecret } from '@blitzkit/core';
|
||||||
|
---
|
||||||
|
|
||||||
{/* google analytics */}
|
{/* google analytics */}
|
||||||
<script
|
<script
|
||||||
is:inline
|
is:inline
|
||||||
async
|
async
|
||||||
src="https://www.googletagmanager.com/gtag/js?id=G-GL2JVHCGPQ"></script>
|
src={`https://www.googletagmanager.com/gtag/js?id=${assertSecret(
|
||||||
|
import.meta.env.PUBLIC_GOOGLE_TAG_MANAGER_ID,
|
||||||
|
)}`}></script>
|
||||||
<script is:inline>
|
<script is:inline>
|
||||||
window.dataLayer = window.dataLayer || [];
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
|
||||||
@@ -10,7 +16,7 @@
|
|||||||
dataLayer.push(arguments);
|
dataLayer.push(arguments);
|
||||||
}
|
}
|
||||||
gtag('js', new Date());
|
gtag('js', new Date());
|
||||||
gtag('config', 'G-GL2JVHCGPQ');
|
gtag('config', assertSecret(import.meta.env.PUBLIC_GOOGLE_TAG_MANAGER_ID));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{/* nitro ads */}
|
{/* nitro ads */}
|
||||||
|
|||||||
+7
-4
@@ -1,6 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
fetchAverageDefinitions,
|
|
||||||
fetchTankDefinitions,
|
|
||||||
formatCompact,
|
formatCompact,
|
||||||
TANK_CLASSES,
|
TANK_CLASSES,
|
||||||
TankClass,
|
TankClass,
|
||||||
@@ -10,14 +8,19 @@ import {
|
|||||||
import strings from '@blitzkit/core/lang/en-US.json';
|
import strings from '@blitzkit/core/lang/en-US.json';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { Box, Flex, Heading } from '@radix-ui/themes';
|
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 { filterTank } from '../../../../core/blitzkit/filterTank';
|
||||||
import { useAveragesExclusionRatio } from '../../../../hooks/useAveragesExclusionRatio';
|
import { useAveragesExclusionRatio } from '../../../../hooks/useAveragesExclusionRatio';
|
||||||
import { $tankFilters } from '../../../../stores/tankFilters';
|
import { $tankFilters } from '../../../../stores/tankFilters';
|
||||||
import { TankPerformanceEphemeral } from '../../../../stores/tankPerformanceEphemeral';
|
import { TankPerformanceEphemeral } from '../../../../stores/tankPerformanceEphemeral';
|
||||||
import { ThemedBar } from '../../../Nivo/ThemedBar';
|
import { ThemedBar } from '../../../Nivo/ThemedBar';
|
||||||
|
|
||||||
const averageDefinitions = await fetchAverageDefinitions();
|
const [tankDefinitions, averageDefinitions] = await Promise.all([
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
awaitableTankDefinitions,
|
||||||
|
awaitableAverageDefinitions,
|
||||||
|
]);
|
||||||
|
|
||||||
const averageDefinitionsArray = Object.entries(averageDefinitions.averages);
|
const averageDefinitionsArray = Object.entries(averageDefinitions.averages);
|
||||||
|
|
||||||
export function PlayersByTierAndClass() {
|
export function PlayersByTierAndClass() {
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import {
|
import { formatCompact } from '@blitzkit/core';
|
||||||
fetchAverageDefinitions,
|
|
||||||
fetchDiscoveredIdsDefinitions,
|
|
||||||
formatCompact,
|
|
||||||
} from '@blitzkit/core';
|
|
||||||
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
||||||
import { Callout, Flex } from '@radix-ui/themes';
|
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 type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
|
||||||
import { InlineSkeleton } from '../InlineSkeleton';
|
import { InlineSkeleton } from '../InlineSkeleton';
|
||||||
|
|
||||||
const discoveredIdsDefinitions = await fetchDiscoveredIdsDefinitions();
|
const [discoveredIdsDefinitions, averageDefinitions] = await Promise.all([
|
||||||
const averageDefinitions = await fetchAverageDefinitions();
|
awaitableDiscoveredIdsDefinitions,
|
||||||
|
awaitableAverageDefinitions,
|
||||||
|
]);
|
||||||
|
|
||||||
export function PerformanceInfo({ skeleton }: MaybeSkeletonComponentProps) {
|
export function PerformanceInfo({ skeleton }: MaybeSkeletonComponentProps) {
|
||||||
const samples = formatCompact(Math.round(discoveredIdsDefinitions.count));
|
const samples = formatCompact(Math.round(discoveredIdsDefinitions.count));
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import {
|
import { TankDefinition, formatCompact } from '@blitzkit/core';
|
||||||
TankDefinition,
|
|
||||||
fetchAverageDefinitions,
|
|
||||||
formatCompact,
|
|
||||||
} from '@blitzkit/core';
|
|
||||||
import { Table } from '@radix-ui/themes';
|
import { Table } from '@radix-ui/themes';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions';
|
||||||
import { useAveragesExclusionRatio } from '../../hooks/useAveragesExclusionRatio';
|
import { useAveragesExclusionRatio } from '../../hooks/useAveragesExclusionRatio';
|
||||||
import { TankPerformanceEphemeral } from '../../stores/tankPerformanceEphemeral';
|
import { TankPerformanceEphemeral } from '../../stores/tankPerformanceEphemeral';
|
||||||
import { TankRowHeaderCell } from '../TankRowHeaderCell';
|
import { TankRowHeaderCell } from '../TankRowHeaderCell';
|
||||||
@@ -13,7 +10,7 @@ interface TankRowProps {
|
|||||||
tank: TankDefinition;
|
tank: TankDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
const averageDefinitions = await fetchAverageDefinitions();
|
const averageDefinitions = await awaitableAverageDefinitions;
|
||||||
|
|
||||||
export const TankRow = memo<TankRowProps>(
|
export const TankRow = memo<TankRowProps>(
|
||||||
({ tank }) => {
|
({ tank }) => {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { fetchAverageDefinitions, fetchTankDefinitions } from '@blitzkit/core';
|
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { Table } from '@radix-ui/themes';
|
import { Table } from '@radix-ui/themes';
|
||||||
import { times } from 'lodash-es';
|
import { times } from 'lodash-es';
|
||||||
import { Fragment, Suspense, useEffect, useMemo, useState } from 'react';
|
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 { filterTank } from '../../core/blitzkit/filterTank';
|
||||||
import { $tankFilters } from '../../stores/tankFilters';
|
import { $tankFilters } from '../../stores/tankFilters';
|
||||||
import { TankPerformanceEphemeral } from '../../stores/tankPerformanceEphemeral';
|
import { TankPerformanceEphemeral } from '../../stores/tankPerformanceEphemeral';
|
||||||
@@ -15,13 +16,16 @@ import { Total } from './Total';
|
|||||||
const PREVIEW_COUNT = 10;
|
const PREVIEW_COUNT = 10;
|
||||||
const DEFAULT_LOADED_ROWS = 25;
|
const DEFAULT_LOADED_ROWS = 25;
|
||||||
|
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
const [tankDefinitions, averageDefinitions] = await Promise.all([
|
||||||
const averageDefinitionsArray = await fetchAverageDefinitions().then(
|
awaitableTankDefinitions,
|
||||||
({ averages }) =>
|
awaitableAverageDefinitions,
|
||||||
Object.entries(averages).map(([id, average]) => ({
|
]);
|
||||||
id: Number(id),
|
|
||||||
...average,
|
const averageDefinitionsArray = Object.entries(averageDefinitions.averages).map(
|
||||||
})),
|
([id, average]) => ({
|
||||||
|
id: Number(id),
|
||||||
|
...average,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
export function Tanks({ skeleton }: MaybeSkeletonComponentProps) {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
AverageDefinitionsEntry,
|
AverageDefinitionsEntry,
|
||||||
fetchAverageDefinitions,
|
|
||||||
formatCompact,
|
formatCompact,
|
||||||
type BlitzkitStats,
|
type BlitzkitStats,
|
||||||
} from '@blitzkit/core';
|
} from '@blitzkit/core';
|
||||||
import { Table } from '@radix-ui/themes';
|
import { Table } from '@radix-ui/themes';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions';
|
||||||
import { useAveragesExclusionRatio } from '../../hooks/useAveragesExclusionRatio';
|
import { useAveragesExclusionRatio } from '../../hooks/useAveragesExclusionRatio';
|
||||||
import { TankPerformanceEphemeral } from '../../stores/tankPerformanceEphemeral';
|
import { TankPerformanceEphemeral } from '../../stores/tankPerformanceEphemeral';
|
||||||
import { StickyRowHeaderCell } from '../StickyRowHeaderCell';
|
import { StickyRowHeaderCell } from '../StickyRowHeaderCell';
|
||||||
@@ -18,7 +18,7 @@ interface TotalProps {
|
|||||||
tanks: AverageDefinitionsEntryWithId[];
|
tanks: AverageDefinitionsEntryWithId[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const averageDefinitions = await fetchAverageDefinitions();
|
const averageDefinitions = await awaitableAverageDefinitions;
|
||||||
|
|
||||||
export const Total = memo<TotalProps>(
|
export const Total = memo<TotalProps>(
|
||||||
({ tanks }) => {
|
({ tanks }) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { asset, tankIcon, TankType, type TankDefinition } from '@blitzkit/core';
|
import { asset, tankIcon, TankType, type TankDefinition } from '@blitzkit/core';
|
||||||
import { Flex, Link, Text, type TextProps } from '@radix-ui/themes';
|
import { Flex, Link, Text, type TextProps } from '@radix-ui/themes';
|
||||||
import { uniq } from 'lodash-es';
|
import { uniq } from 'lodash-es';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef, type ReactNode } from 'react';
|
||||||
import { TankopediaPersistent } from '../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../../stores/tankopediaPersistent';
|
||||||
import { classIcons } from '../ClassIcon';
|
import { classIcons } from '../ClassIcon';
|
||||||
import { MAX_RECENTLY_VIEWED } from '../TankSearch/constants';
|
import { MAX_RECENTLY_VIEWED } from '../TankSearch/constants';
|
||||||
@@ -10,7 +10,7 @@ import './index.css';
|
|||||||
type TankCardProps = TextProps & {
|
type TankCardProps = TextProps & {
|
||||||
tank: TankDefinition;
|
tank: TankDefinition;
|
||||||
onTankSelect?: (tank: TankDefinition) => void;
|
onTankSelect?: (tank: TankDefinition) => void;
|
||||||
discriminator?: string;
|
discriminator?: ReactNode;
|
||||||
noLink?: boolean;
|
noLink?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,7 +63,10 @@ export const TankCard = forwardRef<HTMLSpanElement, TankCardProps>(
|
|||||||
<Link
|
<Link
|
||||||
className="link"
|
className="link"
|
||||||
underline="hover"
|
underline="hover"
|
||||||
href={provideLink ? `/tools/tankopedia/${tank.id}` : undefined}
|
href={provideLink ? `/tools/tankopedia/${tank.id}` : '#'}
|
||||||
|
onClick={(event) => {
|
||||||
|
if (!provideLink) event.preventDefault();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt={tank.name}
|
alt={tank.name}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
fetchModelDefinitions,
|
|
||||||
normalizeBoundingBox,
|
normalizeBoundingBox,
|
||||||
resolveDpm,
|
resolveDpm,
|
||||||
unionBoundingBox,
|
unionBoundingBox,
|
||||||
@@ -7,6 +6,7 @@ import {
|
|||||||
} from '@blitzkit/core';
|
} from '@blitzkit/core';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { awaitableModelDefinitions } from '../../../core/awaitables/modelDefinitions';
|
||||||
import { resolveReload } from '../../../core/blitzkit/resolveReload';
|
import { resolveReload } from '../../../core/blitzkit/resolveReload';
|
||||||
import { $tankopediaSort } from '../../../stores/tankopediaSort';
|
import { $tankopediaSort } from '../../../stores/tankopediaSort';
|
||||||
import { TankCard } from '../../TankCard';
|
import { TankCard } from '../../TankCard';
|
||||||
@@ -16,7 +16,7 @@ interface TankSearchCardProps {
|
|||||||
onSelect?: (tank: TankDefinition) => void;
|
onSelect?: (tank: TankDefinition) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modelDefinitions = await fetchModelDefinitions();
|
const modelDefinitions = await awaitableModelDefinitions;
|
||||||
|
|
||||||
export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
|
export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) {
|
||||||
const tankopediaSort = useStore($tankopediaSort);
|
const tankopediaSort = useStore($tankopediaSort);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
asset,
|
asset,
|
||||||
fetchGameDefinitions,
|
|
||||||
TANK_CLASSES,
|
TANK_CLASSES,
|
||||||
TankType,
|
TankType,
|
||||||
TIER_ROMAN_NUMERALS,
|
TIER_ROMAN_NUMERALS,
|
||||||
@@ -9,6 +8,7 @@ import { useStore } from '@nanostores/react';
|
|||||||
import { ReloadIcon } from '@radix-ui/react-icons';
|
import { ReloadIcon } from '@radix-ui/react-icons';
|
||||||
import { Box, Flex, IconButton, Text } from '@radix-ui/themes';
|
import { Box, Flex, IconButton, Text } from '@radix-ui/themes';
|
||||||
import { times } from 'lodash-es';
|
import { times } from 'lodash-es';
|
||||||
|
import { awaitableGameDefinitions } from '../../../core/awaitables/gameDefinitions';
|
||||||
import { $tankFilters, initialTankFilters } from '../../../stores/tankFilters';
|
import { $tankFilters, initialTankFilters } from '../../../stores/tankFilters';
|
||||||
import {
|
import {
|
||||||
$tankopediaSort,
|
$tankopediaSort,
|
||||||
@@ -27,7 +27,7 @@ interface FilterControlProps {
|
|||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gameDefinitions = await fetchGameDefinitions();
|
const gameDefinitions = await awaitableGameDefinitions;
|
||||||
|
|
||||||
export function FilterControl({ compact }: FilterControlProps) {
|
export function FilterControl({ compact }: FilterControlProps) {
|
||||||
const tankFilters = useStore($tankFilters);
|
const tankFilters = useStore($tankFilters);
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { fetchTankDefinitions } from '@blitzkit/core';
|
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { Flex, Separator, Text } from '@radix-ui/themes';
|
import { Flex, Separator, Text } from '@radix-ui/themes';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
|
||||||
import { $tankFilters, initialTankFilters } from '../../../stores/tankFilters';
|
import { $tankFilters, initialTankFilters } from '../../../stores/tankFilters';
|
||||||
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../../../stores/tankopediaPersistent';
|
||||||
import { $tankopediaSort } from '../../../stores/tankopediaSort';
|
import { $tankopediaSort } from '../../../stores/tankopediaSort';
|
||||||
import { TankCard } from '../../TankCard';
|
import { TankCard } from '../../TankCard';
|
||||||
import { TankCardWrapper } from './TankCardWrapper';
|
import { TankCardWrapper } from './TankCardWrapper';
|
||||||
|
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
const tankDefinitions = await awaitableTankDefinitions;
|
||||||
|
|
||||||
export function RecentlyViewed() {
|
export function RecentlyViewed() {
|
||||||
const tankopediaPersistentStore = TankopediaPersistent.useStore();
|
const tankopediaPersistentStore = TankopediaPersistent.useStore();
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
fetchGameDefinitions,
|
|
||||||
fetchModelDefinitions,
|
|
||||||
fetchTankDefinitions,
|
|
||||||
fetchTankNames,
|
|
||||||
metaSortTank,
|
metaSortTank,
|
||||||
normalizeBoundingBox,
|
normalizeBoundingBox,
|
||||||
resolveDpm,
|
resolveDpm,
|
||||||
@@ -14,6 +10,10 @@ import { Callout, Flex, Link, Text, type FlexProps } from '@radix-ui/themes';
|
|||||||
import fuzzysort from 'fuzzysort';
|
import fuzzysort from 'fuzzysort';
|
||||||
import { times, uniq } from 'lodash-es';
|
import { times, uniq } from 'lodash-es';
|
||||||
import { memo, useEffect, useMemo, useState } from 'react';
|
import { memo, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { awaitableGameDefinitions } from '../../core/awaitables/gameDefinitions';
|
||||||
|
import { awaitableModelDefinitions } from '../../core/awaitables/modelDefinitions';
|
||||||
|
import { awaitableTankDefinitions } from '../../core/awaitables/tankDefinitions';
|
||||||
|
import { awaitableTankNames } from '../../core/awaitables/tankNames';
|
||||||
import { filterTank } from '../../core/blitzkit/filterTank';
|
import { filterTank } from '../../core/blitzkit/filterTank';
|
||||||
import { resolveReload } from '../../core/blitzkit/resolveReload';
|
import { resolveReload } from '../../core/blitzkit/resolveReload';
|
||||||
import { $tankFilters } from '../../stores/tankFilters';
|
import { $tankFilters } from '../../stores/tankFilters';
|
||||||
@@ -42,10 +42,13 @@ type TankSearchProps = Omit<FlexProps, 'onSelect'> & {
|
|||||||
const PREVIEW_COUNT = 25;
|
const PREVIEW_COUNT = 25;
|
||||||
const DEFAULT_LOADED_CARDS = 75;
|
const DEFAULT_LOADED_CARDS = 75;
|
||||||
|
|
||||||
const gameDefinitions = await fetchGameDefinitions();
|
const [gameDefinitions, modelDefinitions, tankDefinitions, tankNames] =
|
||||||
const modelDefinitions = await fetchModelDefinitions();
|
await Promise.all([
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
awaitableGameDefinitions,
|
||||||
const tankNames = await fetchTankNames();
|
awaitableModelDefinitions,
|
||||||
|
awaitableTankDefinitions,
|
||||||
|
awaitableTankNames,
|
||||||
|
]);
|
||||||
|
|
||||||
export const TankSearch = memo<TankSearchProps>(
|
export const TankSearch = memo<TankSearchProps>(
|
||||||
({ compact, onSelect, onSelectAll, ...props }) => {
|
({ compact, onSelect, onSelectAll, ...props }) => {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function CalloutsSection() {
|
|||||||
if (!tank.testing && !tank.deprecated && !promoteAeson) return null;
|
if (!tank.testing && !tank.deprecated && !promoteAeson) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex direction="column" align="center" gap="2" mb="4">
|
<Flex direction="column" align="center" gap="2" mb="4" px="4">
|
||||||
{promoteAeson && <AesonPlug />}
|
{promoteAeson && <AesonPlug />}
|
||||||
|
|
||||||
{tank.deprecated && (
|
{tank.deprecated && (
|
||||||
@@ -36,7 +36,7 @@ export function CalloutsSection() {
|
|||||||
<MinusCircledIcon />
|
<MinusCircledIcon />
|
||||||
</Callout.Icon>
|
</Callout.Icon>
|
||||||
<Callout.Text>
|
<Callout.Text>
|
||||||
This tank is deprecated and may display undefined behavior.
|
Deprecated tanks may display undefined behavior.
|
||||||
</Callout.Text>
|
</Callout.Text>
|
||||||
</Callout.Root>
|
</Callout.Root>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+20
-14
@@ -1,13 +1,11 @@
|
|||||||
import {
|
import { createDefaultSkills } from '@blitzkit/core';
|
||||||
createDefaultSkills,
|
|
||||||
fetchEquipmentDefinitions,
|
|
||||||
fetchModelDefinitions,
|
|
||||||
fetchProvisionDefinitions,
|
|
||||||
fetchSkillDefinitions,
|
|
||||||
fetchTankDefinitions,
|
|
||||||
} from '@blitzkit/core';
|
|
||||||
import { Flex, Progress, Text } from '@radix-ui/themes';
|
import { Flex, Progress, Text } from '@radix-ui/themes';
|
||||||
import { useMemo, type ComponentProps } from 'react';
|
import { useMemo, type ComponentProps } from 'react';
|
||||||
|
import { awaitableEquipmentDefinitions } from '../../../../../../core/awaitables/equipmentDefinitions';
|
||||||
|
import { awaitableModelDefinitions } from '../../../../../../core/awaitables/modelDefinitions';
|
||||||
|
import { awaitableProvisionDefinitions } from '../../../../../../core/awaitables/provisionDefinitions';
|
||||||
|
import { awaitableSkillDefinitions } from '../../../../../../core/awaitables/skillDefinitions';
|
||||||
|
import { awaitableTankDefinitions } from '../../../../../../core/awaitables/tankDefinitions';
|
||||||
import {
|
import {
|
||||||
tankCharacteristics,
|
tankCharacteristics,
|
||||||
type TankCharacteristics,
|
type TankCharacteristics,
|
||||||
@@ -29,11 +27,19 @@ interface InfoWithDeltaProps extends InfoProps {
|
|||||||
noRanking?: boolean;
|
noRanking?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
const [
|
||||||
const provisionDefinitions = await fetchProvisionDefinitions();
|
tankDefinitions,
|
||||||
const equipmentDefinitions = await fetchEquipmentDefinitions();
|
provisionDefinitions,
|
||||||
const tankModelDefinitions = await fetchModelDefinitions();
|
equipmentDefinitions,
|
||||||
const skillDefinitions = await fetchSkillDefinitions();
|
modelDefinitions,
|
||||||
|
skillDefinitions,
|
||||||
|
] = await Promise.all([
|
||||||
|
awaitableTankDefinitions,
|
||||||
|
awaitableProvisionDefinitions,
|
||||||
|
awaitableEquipmentDefinitions,
|
||||||
|
awaitableModelDefinitions,
|
||||||
|
awaitableSkillDefinitions,
|
||||||
|
]);
|
||||||
|
|
||||||
export function InfoWithDelta({
|
export function InfoWithDelta({
|
||||||
value,
|
value,
|
||||||
@@ -90,7 +96,7 @@ export function InfoWithDelta({
|
|||||||
{
|
{
|
||||||
equipmentDefinitions,
|
equipmentDefinitions,
|
||||||
provisionDefinitions,
|
provisionDefinitions,
|
||||||
tankModelDefinition: tankModelDefinitions.models[tank.id],
|
tankModelDefinition: modelDefinitions.models[tank.id],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|||||||
+6
-4
@@ -3,8 +3,6 @@ import {
|
|||||||
coefficient,
|
coefficient,
|
||||||
CREW_MEMBER_NAMES,
|
CREW_MEMBER_NAMES,
|
||||||
CrewType,
|
CrewType,
|
||||||
fetchEquipmentDefinitions,
|
|
||||||
fetchProvisionDefinitions,
|
|
||||||
GUN_TYPE_NAMES,
|
GUN_TYPE_NAMES,
|
||||||
isExplosive,
|
isExplosive,
|
||||||
resolvePenetrationCoefficient,
|
resolvePenetrationCoefficient,
|
||||||
@@ -24,6 +22,8 @@ import { debounce } from 'lodash-es';
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { Fragment } from 'react/jsx-runtime';
|
import { Fragment } from 'react/jsx-runtime';
|
||||||
import { lerp } from 'three/src/math/MathUtils.js';
|
import { lerp } from 'three/src/math/MathUtils.js';
|
||||||
|
import { awaitableEquipmentDefinitions } from '../../../../../core/awaitables/equipmentDefinitions';
|
||||||
|
import { awaitableProvisionDefinitions } from '../../../../../core/awaitables/provisionDefinitions';
|
||||||
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { tankCharacteristics } from '../../../../../core/blitzkit/tankCharacteristics';
|
import { tankCharacteristics } from '../../../../../core/blitzkit/tankCharacteristics';
|
||||||
import { useEquipment } from '../../../../../hooks/useEquipment';
|
import { useEquipment } from '../../../../../hooks/useEquipment';
|
||||||
@@ -33,8 +33,10 @@ import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
|
|||||||
import { Info } from './components/Info';
|
import { Info } from './components/Info';
|
||||||
import { InfoWithDelta } from './components/InfoWithDelta';
|
import { InfoWithDelta } from './components/InfoWithDelta';
|
||||||
|
|
||||||
const equipmentDefinitions = await fetchEquipmentDefinitions();
|
const [equipmentDefinitions, provisionDefinitions] = await Promise.all([
|
||||||
const provisionDefinitions = await fetchProvisionDefinitions();
|
awaitableEquipmentDefinitions,
|
||||||
|
awaitableProvisionDefinitions,
|
||||||
|
]);
|
||||||
|
|
||||||
export function Characteristics() {
|
export function Characteristics() {
|
||||||
const mutateDuel = Duel.useMutation();
|
const mutateDuel = Duel.useMutation();
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,3 @@
|
|||||||
import { fetchConsumableDefinitions } from '@blitzkit/core';
|
|
||||||
import { checkConsumableProvisionInclusivity } from '@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity';
|
import { checkConsumableProvisionInclusivity } from '@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity';
|
||||||
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
import { InfoCircledIcon } from '@radix-ui/react-icons';
|
||||||
import {
|
import {
|
||||||
@@ -9,12 +8,13 @@ import {
|
|||||||
Popover,
|
Popover,
|
||||||
Text,
|
Text,
|
||||||
} from '@radix-ui/themes';
|
} from '@radix-ui/themes';
|
||||||
|
import { awaitableConsumableDefinitions } from '../../../../core/awaitables/consumableDefinitions';
|
||||||
import { useEquipment } from '../../../../hooks/useEquipment';
|
import { useEquipment } from '../../../../hooks/useEquipment';
|
||||||
import { Duel } from '../../../../stores/duel';
|
import { Duel } from '../../../../stores/duel';
|
||||||
import { ConsumablesManager } from '../../../ConsumablesManager';
|
import { ConsumablesManager } from '../../../ConsumablesManager';
|
||||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||||
|
|
||||||
const consumableDefinitions = await fetchConsumableDefinitions();
|
const consumableDefinitions = await awaitableConsumableDefinitions;
|
||||||
|
|
||||||
export function Consumables() {
|
export function Consumables() {
|
||||||
const mutateDuel = Duel.useMutation();
|
const mutateDuel = Duel.useMutation();
|
||||||
|
|||||||
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
import { fetchEquipmentDefinitions } from '@blitzkit/core';
|
|
||||||
import { Button, Flex, Heading } from '@radix-ui/themes';
|
import { Button, Flex, Heading } from '@radix-ui/themes';
|
||||||
|
import { awaitableEquipmentDefinitions } from '../../../../core/awaitables/equipmentDefinitions';
|
||||||
import { Duel } from '../../../../stores/duel';
|
import { Duel } from '../../../../stores/duel';
|
||||||
import { EquipmentManager } from '../../../EquipmentManager';
|
import { EquipmentManager } from '../../../EquipmentManager';
|
||||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||||
|
|
||||||
const equipmentDefinitions = await fetchEquipmentDefinitions();
|
const equipmentDefinitions = await awaitableEquipmentDefinitions;
|
||||||
|
|
||||||
export function Equipment() {
|
export function Equipment() {
|
||||||
const mutateDuel = Duel.useMutation();
|
const mutateDuel = Duel.useMutation();
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
asset,
|
asset,
|
||||||
EngineDefinition,
|
EngineDefinition,
|
||||||
fetchTankDefinitions,
|
|
||||||
formatCompact,
|
formatCompact,
|
||||||
GunDefinition,
|
GunDefinition,
|
||||||
ModuleType,
|
ModuleType,
|
||||||
@@ -21,6 +20,7 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
Text,
|
Text,
|
||||||
} from '@radix-ui/themes';
|
} from '@radix-ui/themes';
|
||||||
|
import { awaitableTankDefinitions } from '../../../../core/awaitables/tankDefinitions';
|
||||||
import { Duel } from '../../../../stores/duel';
|
import { Duel } from '../../../../stores/duel';
|
||||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ function ModuleButton({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
const tankDefinitions = await awaitableTankDefinitions;
|
||||||
|
|
||||||
export function Modules() {
|
export function Modules() {
|
||||||
const mutateDuel = Duel.useMutation();
|
const mutateDuel = Duel.useMutation();
|
||||||
|
|||||||
+3
-2
@@ -1,10 +1,11 @@
|
|||||||
import { availableProvisions, fetchProvisionDefinitions } from '@blitzkit/core';
|
import { availableProvisions } from '@blitzkit/core';
|
||||||
import { Button, Flex, Heading } from '@radix-ui/themes';
|
import { Button, Flex, Heading } from '@radix-ui/themes';
|
||||||
|
import { awaitableProvisionDefinitions } from '../../../../core/awaitables/provisionDefinitions';
|
||||||
import { Duel } from '../../../../stores/duel';
|
import { Duel } from '../../../../stores/duel';
|
||||||
import { ProvisionsManager } from '../../../ProvisionsManager';
|
import { ProvisionsManager } from '../../../ProvisionsManager';
|
||||||
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
import { ConfigurationChildWrapper } from './ConfigurationChildWrapper';
|
||||||
|
|
||||||
const provisionDefinitions = await fetchProvisionDefinitions();
|
const provisionDefinitions = await awaitableProvisionDefinitions;
|
||||||
|
|
||||||
export function Provisions() {
|
export function Provisions() {
|
||||||
const mutateDuel = Duel.useMutation();
|
const mutateDuel = Duel.useMutation();
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import {
|
import { asset } from '@blitzkit/core';
|
||||||
asset,
|
|
||||||
fetchConsumableDefinitions,
|
|
||||||
fetchGameDefinitions,
|
|
||||||
fetchProvisionDefinitions,
|
|
||||||
} from '@blitzkit/core';
|
|
||||||
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
||||||
|
import { awaitableConsumableDefinitions } from '../../core/awaitables/consumableDefinitions';
|
||||||
|
import { awaitableGameDefinitions } from '../../core/awaitables/gameDefinitions';
|
||||||
|
import { awaitableProvisionDefinitions } from '../../core/awaitables/provisionDefinitions';
|
||||||
import { Duel } from '../../stores/duel';
|
import { Duel } from '../../stores/duel';
|
||||||
|
|
||||||
const gameDefinitions = await fetchGameDefinitions();
|
const [gameDefinitions, consumableDefinitions, provisionDefinitions] =
|
||||||
const consumableDefinitions = await fetchConsumableDefinitions();
|
await Promise.all([
|
||||||
const provisionDefinitions = await fetchProvisionDefinitions();
|
awaitableGameDefinitions,
|
||||||
|
awaitableConsumableDefinitions,
|
||||||
|
awaitableProvisionDefinitions,
|
||||||
|
]);
|
||||||
|
|
||||||
export function GameModeSection() {
|
export function GameModeSection() {
|
||||||
const tank = Duel.use((state) => state.protagonist.tank);
|
const tank = Duel.use((state) => state.protagonist.tank);
|
||||||
|
|||||||
+4
-1
@@ -1,5 +1,6 @@
|
|||||||
import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from '@radix-ui/react-icons';
|
import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from '@radix-ui/react-icons';
|
||||||
import { Box, Button, Checkbox, Flex, Text } from '@radix-ui/themes';
|
import { Box, Button, Checkbox, Flex, Text } from '@radix-ui/themes';
|
||||||
|
import { Suspense } from 'react';
|
||||||
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
||||||
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
|
||||||
import type { ThicknessRange } from '../../../../../Armor/components/StaticArmor';
|
import type { ThicknessRange } from '../../../../../Armor/components/StaticArmor';
|
||||||
@@ -168,7 +169,9 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<DynamicArmorSwitcher />
|
<Suspense>
|
||||||
|
<DynamicArmorSwitcher />
|
||||||
|
</Suspense>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+40
-9
@@ -15,6 +15,7 @@ import {
|
|||||||
GearIcon,
|
GearIcon,
|
||||||
} from '@radix-ui/react-icons';
|
} from '@radix-ui/react-icons';
|
||||||
import {
|
import {
|
||||||
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -26,7 +27,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@radix-ui/themes';
|
} from '@radix-ui/themes';
|
||||||
import { useState, type RefObject } from 'react';
|
import { Suspense, useState, type RefObject } from 'react';
|
||||||
import { ENVIRONMENTS } from '../../../../../constants/lightingEnvironments';
|
import { ENVIRONMENTS } from '../../../../../constants/lightingEnvironments';
|
||||||
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
|
import { Pose, poseEvent } from '../../../../../core/blitzkit/pose';
|
||||||
import { useEquipment } from '../../../../../hooks/useEquipment';
|
import { useEquipment } from '../../../../../hooks/useEquipment';
|
||||||
@@ -34,7 +35,10 @@ import { useFullScreen } from '../../../../../hooks/useFullScreen';
|
|||||||
import { useFullscreenAvailability } from '../../../../../hooks/useFullscreenAvailability';
|
import { useFullscreenAvailability } from '../../../../../hooks/useFullscreenAvailability';
|
||||||
import { App } from '../../../../../stores/app';
|
import { App } from '../../../../../stores/app';
|
||||||
import { Duel } from '../../../../../stores/duel';
|
import { Duel } from '../../../../../stores/duel';
|
||||||
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
|
import {
|
||||||
|
ShootingRangeZoom,
|
||||||
|
TankopediaEphemeral,
|
||||||
|
} from '../../../../../stores/tankopediaEphemeral';
|
||||||
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
|
||||||
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
|
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
|
||||||
import type { ThicknessRange } from '../../../../Armor/components/StaticArmor';
|
import type { ThicknessRange } from '../../../../Armor/components/StaticArmor';
|
||||||
@@ -55,10 +59,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
const hasCustomShell = TankopediaEphemeral.use(
|
const hasCustomShell = TankopediaEphemeral.use(
|
||||||
(state) => state.customShell !== undefined,
|
(state) => state.customShell !== undefined,
|
||||||
);
|
);
|
||||||
const display = TankopediaPersistent.useDeferred(
|
const display = TankopediaEphemeral.use((state) => state.display);
|
||||||
(state) => state.display,
|
|
||||||
TankopediaDisplay.Model,
|
|
||||||
);
|
|
||||||
const isFullScreen = useFullScreen();
|
const isFullScreen = useFullScreen();
|
||||||
const showGrid = TankopediaPersistent.use((state) => state.showGrid);
|
const showGrid = TankopediaPersistent.use((state) => state.showGrid);
|
||||||
const showEnvironment = TankopediaPersistent.use(
|
const showEnvironment = TankopediaPersistent.use(
|
||||||
@@ -89,6 +90,23 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
<>
|
<>
|
||||||
<QuickInputs />
|
<QuickInputs />
|
||||||
|
|
||||||
|
{display === TankopediaDisplay.ShootingRange && (
|
||||||
|
<Box
|
||||||
|
width="1rem"
|
||||||
|
height="1rem"
|
||||||
|
position="absolute"
|
||||||
|
top="50%"
|
||||||
|
left="50%"
|
||||||
|
style={{
|
||||||
|
zIndex: 2,
|
||||||
|
backgroundImage: `url(${imgur('27Gwth4')})`,
|
||||||
|
backgroundSize: 'contain',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
borderRadius: '50%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{display === TankopediaDisplay.StaticArmor && (
|
{display === TankopediaDisplay.StaticArmor && (
|
||||||
<Thicknesses thicknessRange={thicknessRange} />
|
<Thicknesses thicknessRange={thicknessRange} />
|
||||||
)}
|
)}
|
||||||
@@ -178,7 +196,10 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
}
|
}
|
||||||
secondaryDiscriminator={
|
secondaryDiscriminator={
|
||||||
<Text style={{ fontSize: '0.75em' }}>
|
<Text style={{ fontSize: '0.75em' }}>
|
||||||
{gun.gun_type!.value.base.shells[0].caliber}mm
|
{Math.round(
|
||||||
|
gun.gun_type!.value.base.shells[0].caliber,
|
||||||
|
)}
|
||||||
|
mm
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -289,7 +310,9 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<DynamicArmorSwitcher />
|
<Suspense>
|
||||||
|
<DynamicArmorSwitcher />
|
||||||
|
</Suspense>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -353,8 +376,9 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
<SegmentedControl.Root
|
<SegmentedControl.Root
|
||||||
value={`${display}`}
|
value={`${display}`}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
mutateTankopediaPersistent((draft) => {
|
mutateTankopediaEphemeral((draft) => {
|
||||||
draft.display = Number(value);
|
draft.display = Number(value);
|
||||||
|
draft.shootingRangeZoom = ShootingRangeZoom.Arcade;
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -379,6 +403,13 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</SegmentedControl.Item>
|
</SegmentedControl.Item>
|
||||||
|
{/* <SegmentedControl.Item value={`${TankopediaDisplay.ShootingRange}`}>
|
||||||
|
<Tooltip content="Shooting range">
|
||||||
|
<Flex height="100%" align="center">
|
||||||
|
<img src={imgur('q8ZPfzG')} style={{ height: '1.25em' }} />
|
||||||
|
</Flex>
|
||||||
|
</Tooltip>
|
||||||
|
</SegmentedControl.Item> */}
|
||||||
</SegmentedControl.Root>
|
</SegmentedControl.Root>
|
||||||
|
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
|
|||||||
+181
-24
@@ -1,44 +1,66 @@
|
|||||||
import { fetchModelDefinitions } from '@blitzkit/core';
|
|
||||||
import { OrbitControls } from '@react-three/drei';
|
import { OrbitControls } from '@react-three/drei';
|
||||||
import { invalidate, useThree } from '@react-three/fiber';
|
import { invalidate, useThree } from '@react-three/fiber';
|
||||||
|
import { clamp } from 'lodash-es';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { Vector3 } from 'three';
|
import { PerspectiveCamera, Vector3 } from 'three';
|
||||||
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
|
import { OrbitControls as OrbitControlsClass } from 'three-stdlib';
|
||||||
|
import { degToRad } from 'three/src/math/MathUtils.js';
|
||||||
|
import { awaitableModelDefinitions } from '../../../../../../core/awaitables/modelDefinitions';
|
||||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
|
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
|
||||||
|
import {
|
||||||
|
modelTransformEvent,
|
||||||
|
type ModelTransformEventData,
|
||||||
|
} from '../../../../../../core/blitzkit/modelTransform';
|
||||||
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
|
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
|
||||||
import { Duel } from '../../../../../../stores/duel';
|
import { Duel } from '../../../../../../stores/duel';
|
||||||
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
import {
|
||||||
|
SHOOTING_RANGE_ZOOM_COEFFICIENTS,
|
||||||
|
ShootingRangeZoom,
|
||||||
|
TankopediaEphemeral,
|
||||||
|
} from '../../../../../../stores/tankopediaEphemeral';
|
||||||
|
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
|
||||||
|
import { aimTarget } from './SceneProps/constants';
|
||||||
|
|
||||||
const initialPosition = [0, 4, -18] as const;
|
|
||||||
const poseDistances: Record<Pose, number> = {
|
const poseDistances: Record<Pose, number> = {
|
||||||
[Pose.HullDown]: 15,
|
[Pose.HullDown]: 15,
|
||||||
[Pose.FaceHug]: 5,
|
[Pose.FaceHug]: 5,
|
||||||
[Pose.Default]: -1,
|
[Pose.Default]: -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const modelDefinitions = await fetchModelDefinitions();
|
const modelDefinitions = await awaitableModelDefinitions;
|
||||||
|
|
||||||
|
const ARCADE_MODE_DISTANCE = 19;
|
||||||
|
// const ARCADE_MODE_ANGLE = Math.PI / 8;
|
||||||
|
const ARCADE_MODE_ANGLE = degToRad(10);
|
||||||
|
const ARCADE_MODE_FOV = 54;
|
||||||
|
|
||||||
export function Controls() {
|
export function Controls() {
|
||||||
|
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||||
|
const display = TankopediaEphemeral.use((state) => state.display);
|
||||||
const duelStore = Duel.useStore();
|
const duelStore = Duel.useStore();
|
||||||
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
||||||
const camera = useThree((state) => state.camera);
|
const camera = useThree((state) => state.camera as PerspectiveCamera);
|
||||||
const canvas = useThree((state) => state.gl.domElement);
|
const canvas = useThree((state) => state.gl.domElement);
|
||||||
const orbitControls = useRef<OrbitControlsClass>(null);
|
const orbitControls = useRef<OrbitControlsClass>(null);
|
||||||
const protagonist = Duel.use((state) => state.protagonist);
|
const protagonistTurret = Duel.use((state) => state.protagonist.turret);
|
||||||
const antagonist = Duel.use((state) => state.antagonist);
|
const antagonistTurret = Duel.use((state) => state.antagonist.turret);
|
||||||
|
const protagonistTrack = Duel.use((state) => state.protagonist.track);
|
||||||
|
const protagonistTank = Duel.use((state) => state.protagonist.tank);
|
||||||
|
const antagonistTank = Duel.use((state) => state.antagonist.tank);
|
||||||
|
const protagonistGun = Duel.use((state) => state.protagonist.gun);
|
||||||
const protagonistModelDefinition =
|
const protagonistModelDefinition =
|
||||||
modelDefinitions.models[protagonist.tank.id];
|
modelDefinitions.models[protagonistTank.id];
|
||||||
const protagonistTrackModelDefinition =
|
const protagonistTrackModelDefinition =
|
||||||
modelDefinitions.models[protagonist.tank.id].tracks[protagonist.track.id];
|
modelDefinitions.models[protagonistTank.id].tracks[protagonistTrack.id];
|
||||||
const antagonistModelDefinition = modelDefinitions.models[antagonist.tank.id];
|
const antagonistModelDefinition = modelDefinitions.models[antagonistTank.id];
|
||||||
const protagonistTurretModelDefinition =
|
const protagonistTurretModelDefinition =
|
||||||
protagonistModelDefinition.turrets[protagonist.turret.id];
|
protagonistModelDefinition.turrets[protagonistTurret.id];
|
||||||
const antagonistTurretModelDefinition =
|
const antagonistTurretModelDefinition =
|
||||||
antagonistModelDefinition.turrets[antagonist.turret.id];
|
antagonistModelDefinition.turrets[antagonistTurret.id];
|
||||||
const protagonistGunModelDefinition =
|
const protagonistGunModelDefinition =
|
||||||
protagonistTurretModelDefinition.guns[
|
protagonistTurretModelDefinition.guns[
|
||||||
protagonist.gun.gun_type!.value.base.id
|
protagonistGun.gun_type!.value.base.id
|
||||||
];
|
];
|
||||||
const protagonistHullOrigin = new Vector3(
|
const protagonistHullOrigin = new Vector3(
|
||||||
protagonistTrackModelDefinition.origin.x,
|
protagonistTrackModelDefinition.origin.x,
|
||||||
@@ -59,12 +81,23 @@ export function Controls() {
|
|||||||
protagonistTrackModelDefinition.origin.y +
|
protagonistTrackModelDefinition.origin.y +
|
||||||
antagonistModelDefinition.turret_origin.y +
|
antagonistModelDefinition.turret_origin.y +
|
||||||
antagonistTurretModelDefinition.gun_origin.y;
|
antagonistTurretModelDefinition.gun_origin.y;
|
||||||
const [autoRotate, setAutoRotate] = useState(true);
|
const [autoRotate, setAutoRotate] = useState(
|
||||||
|
display !== TankopediaDisplay.ShootingRange,
|
||||||
useEffect(() => {
|
);
|
||||||
camera.position.set(...initialPosition);
|
const gunHeight =
|
||||||
orbitControls.current?.target.set(0, 1.25, 0);
|
protagonistHullOrigin.y +
|
||||||
}, [camera]);
|
protagonistTurretOrigin.y +
|
||||||
|
protagonistGunOrigin.y;
|
||||||
|
const inspectModeInitialPosition = [-8, gunHeight + 4, -13] as const;
|
||||||
|
const protagonistGunOriginOnlyY = new Vector3(
|
||||||
|
0,
|
||||||
|
protagonistTurretModelDefinition.gun_origin.y,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const shellOrigin = protagonistHullOrigin
|
||||||
|
.clone()
|
||||||
|
.add(protagonistTurretOrigin)
|
||||||
|
.add(protagonistGunOriginOnlyY);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribeTankopediaEphemeral = tankopediaEphemeralStore.subscribe(
|
const unsubscribeTankopediaEphemeral = tankopediaEphemeralStore.subscribe(
|
||||||
@@ -153,7 +186,7 @@ export function Controls() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case Pose.Default: {
|
case Pose.Default: {
|
||||||
camera.position.set(...initialPosition);
|
camera.position.set(...inspectModeInitialPosition);
|
||||||
orbitControls.current?.target.set(0, 1.25, 0);
|
orbitControls.current?.target.set(0, 1.25, 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -168,21 +201,145 @@ export function Controls() {
|
|||||||
unsubscribeTankopediaEphemeral();
|
unsubscribeTankopediaEphemeral();
|
||||||
poseEvent.off(handlePoseEvent);
|
poseEvent.off(handlePoseEvent);
|
||||||
};
|
};
|
||||||
}, [camera, protagonist.tank.id, antagonist.tank.id]);
|
}, [camera, protagonistTank.id, antagonistTank.id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let isInitialIteration = true;
|
||||||
|
|
||||||
function handleDisturbance() {
|
function handleDisturbance() {
|
||||||
setAutoRotate(false);
|
setAutoRotate(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
|
if (display !== TankopediaDisplay.ShootingRange || event.repeat) return;
|
||||||
|
|
||||||
|
if (event.key === 'Shift') {
|
||||||
|
mutateTankopediaEphemeral((draft) => {
|
||||||
|
draft.shootingRangeZoom =
|
||||||
|
draft.shootingRangeZoom === ShootingRangeZoom.Arcade
|
||||||
|
? ShootingRangeZoom.Zoom0
|
||||||
|
: ShootingRangeZoom.Arcade;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWheel(event: WheelEvent) {
|
||||||
|
if (display !== TankopediaDisplay.ShootingRange) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
mutateTankopediaEphemeral((draft) => {
|
||||||
|
draft.shootingRangeZoom = clamp(
|
||||||
|
draft.shootingRangeZoom - Math.sign(event.deltaY),
|
||||||
|
ShootingRangeZoom.Arcade,
|
||||||
|
ShootingRangeZoom.Zoom2,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleScroll(event: Event) {
|
||||||
|
if (display !== TankopediaDisplay.ShootingRange) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
poseEvent.on(handleDisturbance);
|
poseEvent.on(handleDisturbance);
|
||||||
canvas.addEventListener('pointerdown', handleDisturbance);
|
canvas.addEventListener('pointerdown', handleDisturbance);
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
canvas.addEventListener('wheel', handleWheel);
|
||||||
|
document.body.addEventListener('scroll', handleScroll);
|
||||||
|
|
||||||
|
const sniperModeCameraOffset = new Vector3();
|
||||||
|
let pitch = 0;
|
||||||
|
let yaw = 0;
|
||||||
|
|
||||||
|
function updateCamera() {
|
||||||
|
if (!orbitControls.current) return;
|
||||||
|
|
||||||
|
orbitControls.current.rotateSpeed = 0.25;
|
||||||
|
|
||||||
|
if (display === TankopediaDisplay.ShootingRange) {
|
||||||
|
const { shootingRangeZoom } = tankopediaEphemeralStore.getState();
|
||||||
|
|
||||||
|
orbitControls.current.enablePan = false;
|
||||||
|
orbitControls.current.enableZoom = false;
|
||||||
|
orbitControls.current.rotateSpeed *=
|
||||||
|
SHOOTING_RANGE_ZOOM_COEFFICIENTS[shootingRangeZoom];
|
||||||
|
camera.fov =
|
||||||
|
ARCADE_MODE_FOV * SHOOTING_RANGE_ZOOM_COEFFICIENTS[shootingRangeZoom];
|
||||||
|
|
||||||
|
if (shootingRangeZoom === ShootingRangeZoom.Arcade) {
|
||||||
|
orbitControls.current.target.set(0, gunHeight + 3, 0);
|
||||||
|
|
||||||
|
if (isInitialIteration) {
|
||||||
|
isInitialIteration = false;
|
||||||
|
camera.position.set(
|
||||||
|
0,
|
||||||
|
gunHeight + ARCADE_MODE_DISTANCE * Math.sin(ARCADE_MODE_ANGLE),
|
||||||
|
ARCADE_MODE_DISTANCE * Math.cos(ARCADE_MODE_ANGLE),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
camera.position
|
||||||
|
.copy(aimTarget)
|
||||||
|
.sub(orbitControls.current.target)
|
||||||
|
.normalize()
|
||||||
|
.multiplyScalar(-ARCADE_MODE_DISTANCE)
|
||||||
|
.add(orbitControls.current.target);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
orbitControls.current.target.copy(shellOrigin);
|
||||||
|
|
||||||
|
camera.position
|
||||||
|
.copy(aimTarget)
|
||||||
|
.sub(shellOrigin)
|
||||||
|
.normalize()
|
||||||
|
.multiplyScalar(-Number.EPSILON)
|
||||||
|
.add(shellOrigin);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
camera.position.set(...inspectModeInitialPosition);
|
||||||
|
orbitControls.current.target.set(0, gunHeight / 2, 0);
|
||||||
|
orbitControls.current.enablePan = true;
|
||||||
|
orbitControls.current.enableZoom = true;
|
||||||
|
camera.fov = 25;
|
||||||
|
}
|
||||||
|
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleModelTransform(event: ModelTransformEventData) {
|
||||||
|
pitch = event.pitch;
|
||||||
|
yaw = event.yaw ?? yaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
modelTransformEvent.on(handleModelTransform);
|
||||||
|
|
||||||
|
updateCamera();
|
||||||
|
|
||||||
|
const unsubscribeDisplay = tankopediaEphemeralStore.subscribe(
|
||||||
|
(state) => state.display,
|
||||||
|
() => {
|
||||||
|
handleDisturbance();
|
||||||
|
updateCamera();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const unsubscribeShootingRangeZoom = tankopediaEphemeralStore.subscribe(
|
||||||
|
(state) => state.shootingRangeZoom,
|
||||||
|
updateCamera,
|
||||||
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
canvas.removeEventListener('pointerdown', handleDisturbance);
|
canvas.removeEventListener('pointerdown', handleDisturbance);
|
||||||
poseEvent.off(handleDisturbance);
|
poseEvent.off(handleDisturbance);
|
||||||
|
unsubscribeDisplay();
|
||||||
|
unsubscribeShootingRangeZoom();
|
||||||
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
canvas.removeEventListener('wheel', handleWheel);
|
||||||
|
document.body.removeEventListener('scroll', handleScroll);
|
||||||
|
modelTransformEvent.off(handleModelTransform);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [display === TankopediaDisplay.ShootingRange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OrbitControls
|
<OrbitControls
|
||||||
@@ -190,8 +347,8 @@ export function Controls() {
|
|||||||
minDistance={5}
|
minDistance={5}
|
||||||
ref={orbitControls}
|
ref={orbitControls}
|
||||||
enabled={tankopediaEphemeralStore.getState().controlsEnabled}
|
enabled={tankopediaEphemeralStore.getState().controlsEnabled}
|
||||||
rotateSpeed={0.25}
|
|
||||||
enableDamping={false}
|
enableDamping={false}
|
||||||
|
maxPolarAngle={degToRad(100)}
|
||||||
autoRotate={autoRotate}
|
autoRotate={autoRotate}
|
||||||
autoRotateSpeed={1 / 4}
|
autoRotateSpeed={1 / 4}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+23
-1
@@ -1,12 +1,16 @@
|
|||||||
import { Environment } from '@react-three/drei';
|
import { Environment } from '@react-three/drei';
|
||||||
|
import { times } from 'lodash-es';
|
||||||
import { useModel } from '../../../../../../hooks/useModel';
|
import { useModel } from '../../../../../../hooks/useModel';
|
||||||
import { Duel } from '../../../../../../stores/duel';
|
import { Duel } from '../../../../../../stores/duel';
|
||||||
|
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
||||||
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
|
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
|
||||||
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
|
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
|
||||||
|
|
||||||
|
const LIGHTS_COUNT = 5;
|
||||||
|
|
||||||
export function Lighting() {
|
export function Lighting() {
|
||||||
const protagonist = Duel.use((state) => state.protagonist);
|
const protagonist = Duel.use((state) => state.protagonist);
|
||||||
const display = TankopediaPersistent.use((state) => state.display);
|
const display = TankopediaEphemeral.use((state) => state.display);
|
||||||
const { hasPbr } = useModel(protagonist.tank.id);
|
const { hasPbr } = useModel(protagonist.tank.id);
|
||||||
const isBrighterLighting =
|
const isBrighterLighting =
|
||||||
!hasPbr && display !== TankopediaDisplay.StaticArmor;
|
!hasPbr && display !== TankopediaDisplay.StaticArmor;
|
||||||
@@ -22,6 +26,24 @@ export function Lighting() {
|
|||||||
background={showEnvironment}
|
background={showEnvironment}
|
||||||
environmentIntensity={isBrighterLighting ? 3 : 1}
|
environmentIntensity={isBrighterLighting ? 3 : 1}
|
||||||
/>
|
/>
|
||||||
|
{display === TankopediaDisplay.ShootingRange && (
|
||||||
|
<>
|
||||||
|
<directionalLight position={[0, -1, 0]} />
|
||||||
|
|
||||||
|
{times(LIGHTS_COUNT, (index) => (
|
||||||
|
<directionalLight
|
||||||
|
key={index}
|
||||||
|
castShadow
|
||||||
|
intensity={3 / LIGHTS_COUNT}
|
||||||
|
position={[
|
||||||
|
Math.sin(2 * Math.PI * (index / LIGHTS_COUNT)),
|
||||||
|
1,
|
||||||
|
Math.cos(2 * Math.PI * (index / LIGHTS_COUNT)),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
import { useFrame, useLoader } from '@react-three/fiber';
|
|
||||||
import { clamp } from 'lodash-es';
|
|
||||||
import { useRef } from 'react';
|
|
||||||
import { MeshStandardMaterial, TextureLoader } from 'three';
|
|
||||||
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
|
|
||||||
|
|
||||||
export function SceneProps() {
|
|
||||||
const show = TankopediaPersistent.use(
|
|
||||||
(state) => state.showGrid && !state.showEnvironment,
|
|
||||||
);
|
|
||||||
const texture = useLoader(TextureLoader, 'https://i.imgur.com/C28Z8nU.png');
|
|
||||||
const material = useRef<MeshStandardMaterial>(null);
|
|
||||||
|
|
||||||
useFrame(({ camera }) => {
|
|
||||||
if (!material.current) return;
|
|
||||||
material.current.opacity = clamp(0.5 * camera.position.y, 0, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
texture.anisotropy = 2;
|
|
||||||
|
|
||||||
if (!show) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<mesh rotation={[-Math.PI / 2, 0, 0]}>
|
|
||||||
<planeGeometry args={[10, 10]} />
|
|
||||||
<meshStandardMaterial ref={material} map={texture} transparent />
|
|
||||||
</mesh>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
import { imgur } from '@blitzkit/core';
|
||||||
|
import { Box, type BoxProps } from '@radix-ui/themes';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
import { Var } from '../../../../../../../../core/radix/var';
|
||||||
|
|
||||||
|
const DOTS = 32;
|
||||||
|
|
||||||
|
type TargetCircleProps = BoxProps & {
|
||||||
|
variant: 'client' | 'server';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TargetCircle = forwardRef<HTMLDivElement, TargetCircleProps>(
|
||||||
|
({ style, variant, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
ref={ref}
|
||||||
|
position="absolute"
|
||||||
|
top="50%"
|
||||||
|
left="50%"
|
||||||
|
minWidth={variant === 'client' ? '23px' : undefined}
|
||||||
|
minHeight={variant === 'client' ? '23px' : undefined}
|
||||||
|
style={{
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
borderRadius: '50%',
|
||||||
|
outline: `${variant === 'client' ? 2 : 1}px solid ${variant === 'client' ? Var('grass-11') : Var('gray-11')}`,
|
||||||
|
outlineOffset: `-${variant === 'client' ? 1 : 0.5}px`,
|
||||||
|
...style,
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{/* {variant === 'client' &&
|
||||||
|
times(DOTS, (index) => {
|
||||||
|
const angle = 2 * Math.PI * (index / DOTS);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: Var('grass-11'),
|
||||||
|
transform: `translate(-50%, -50%)`,
|
||||||
|
position: 'absolute',
|
||||||
|
}}
|
||||||
|
top={`${(0.5 * Math.sin(angle) + 0.5) * 100}%`}
|
||||||
|
left={`${(0.5 * Math.cos(angle) + 0.5) * 100}%`}
|
||||||
|
width={'calc(1rem / 6)'}
|
||||||
|
height={'calc(1rem / 6)'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})} */}
|
||||||
|
|
||||||
|
<Box
|
||||||
|
width="1rem"
|
||||||
|
height="1rem"
|
||||||
|
position="absolute"
|
||||||
|
top="50%"
|
||||||
|
left="50%"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url(${imgur('27Gwth4')})`,
|
||||||
|
backgroundSize: 'contain',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
borderRadius: '50%',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
import { Vector3 } from 'three';
|
||||||
|
|
||||||
|
export const aimTarget = new Vector3();
|
||||||
+282
@@ -0,0 +1,282 @@
|
|||||||
|
import { asset, I_HAT, imgur, J_HAT } from '@blitzkit/core';
|
||||||
|
import { Html } from '@react-three/drei';
|
||||||
|
import { useFrame, useLoader } from '@react-three/fiber';
|
||||||
|
import { clamp } from 'lodash-es';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import {
|
||||||
|
Group,
|
||||||
|
MeshStandardMaterial,
|
||||||
|
PerspectiveCamera,
|
||||||
|
TextureLoader,
|
||||||
|
Vector2,
|
||||||
|
Vector3,
|
||||||
|
} from 'three';
|
||||||
|
import { GLTFLoader } from 'three/examples/jsm/Addons.js';
|
||||||
|
import { degToRad, radToDeg } from 'three/src/math/MathUtils.js';
|
||||||
|
import { awaitableEquipmentDefinitions } from '../../../../../../../core/awaitables/equipmentDefinitions';
|
||||||
|
import { awaitableModelDefinitions } from '../../../../../../../core/awaitables/modelDefinitions';
|
||||||
|
import { awaitableProvisionDefinitions } from '../../../../../../../core/awaitables/provisionDefinitions';
|
||||||
|
import { applyPitchYawLimits } from '../../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
|
import { modelTransformEvent } from '../../../../../../../core/blitzkit/modelTransform';
|
||||||
|
import { tankCharacteristics } from '../../../../../../../core/blitzkit/tankCharacteristics';
|
||||||
|
import { Duel } from '../../../../../../../stores/duel';
|
||||||
|
import { TankopediaEphemeral } from '../../../../../../../stores/tankopediaEphemeral';
|
||||||
|
import { TankopediaPersistent } from '../../../../../../../stores/tankopediaPersistent';
|
||||||
|
import { TankopediaDisplay } from '../../../../../../../stores/tankopediaPersistent/constants';
|
||||||
|
import { TargetCircle } from './components/TargetCircle';
|
||||||
|
import { aimTarget } from './constants';
|
||||||
|
|
||||||
|
const [modelDefinitions, equipmentDefinitions, provisionDefinitions] =
|
||||||
|
await Promise.all([
|
||||||
|
awaitableModelDefinitions,
|
||||||
|
awaitableEquipmentDefinitions,
|
||||||
|
awaitableProvisionDefinitions,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const emptyVector = new Vector2();
|
||||||
|
|
||||||
|
export function SceneProps() {
|
||||||
|
const show = TankopediaPersistent.use(
|
||||||
|
(state) => state.showGrid && !state.showEnvironment,
|
||||||
|
);
|
||||||
|
const targetCircleWrapper = useRef<Group>(null);
|
||||||
|
const clientTargetCircle = useRef<HTMLDivElement>(null);
|
||||||
|
const serverTargetCircle = useRef<HTMLDivElement>(null);
|
||||||
|
const material = useRef<MeshStandardMaterial>(null);
|
||||||
|
const playground = useRef<Group>(null);
|
||||||
|
|
||||||
|
let lastPitch = Duel.use((state) => state.protagonist.pitch);
|
||||||
|
let lastYaw = Duel.use((state) => state.protagonist.yaw);
|
||||||
|
const display = TankopediaEphemeral.use((state) => state.display);
|
||||||
|
const turret = Duel.use((state) => state.protagonist.turret);
|
||||||
|
const track = Duel.use((state) => state.protagonist.track);
|
||||||
|
const tank = Duel.use((state) => state.protagonist.tank);
|
||||||
|
const gun = Duel.use((state) => state.protagonist.gun);
|
||||||
|
const shell = Duel.use((state) => state.protagonist.shell);
|
||||||
|
const engine = Duel.use((state) => state.protagonist.engine);
|
||||||
|
const tankModelDefinition = modelDefinitions.models[tank.id];
|
||||||
|
const trackModelDefinition =
|
||||||
|
modelDefinitions.models[tank.id].tracks[track.id];
|
||||||
|
const gunModelDefinition =
|
||||||
|
tankModelDefinition.turrets[turret.id].guns[gun.gun_type!.value.base.id];
|
||||||
|
const turretModelDefinition = tankModelDefinition.turrets[turret.id];
|
||||||
|
const hullOrigin = new Vector3(
|
||||||
|
trackModelDefinition.origin.x,
|
||||||
|
trackModelDefinition.origin.y,
|
||||||
|
-trackModelDefinition.origin.z,
|
||||||
|
);
|
||||||
|
const turretOrigin = new Vector3(
|
||||||
|
tankModelDefinition.turret_origin.x,
|
||||||
|
tankModelDefinition.turret_origin.y,
|
||||||
|
-tankModelDefinition.turret_origin.z,
|
||||||
|
);
|
||||||
|
const gunOriginOnlyY = new Vector3(0, turretModelDefinition.gun_origin.y, 0);
|
||||||
|
const shellOrigin = hullOrigin.clone().add(turretOrigin).add(gunOriginOnlyY);
|
||||||
|
const camouflage = Duel.use((state) => state.protagonist.camouflage);
|
||||||
|
const crewSkills = TankopediaEphemeral.use((state) => state.skills);
|
||||||
|
const consumables = Duel.use((state) => state.protagonist.consumables);
|
||||||
|
const provisions = Duel.use((state) => state.protagonist.provisions);
|
||||||
|
const equipmentMatrix = Duel.use(
|
||||||
|
(state) => state.protagonist.equipmentMatrix,
|
||||||
|
);
|
||||||
|
const characteristics = tankCharacteristics(
|
||||||
|
{
|
||||||
|
tank,
|
||||||
|
turret,
|
||||||
|
gun,
|
||||||
|
shell,
|
||||||
|
consumables,
|
||||||
|
applyDynamicArmor: false,
|
||||||
|
applyReactiveArmor: false,
|
||||||
|
applySpallLiner: false,
|
||||||
|
camouflage,
|
||||||
|
crewSkills,
|
||||||
|
engine,
|
||||||
|
equipmentMatrix,
|
||||||
|
provisions,
|
||||||
|
stockEngine: tank.engines[0],
|
||||||
|
stockGun: tank.turrets[0].guns[0],
|
||||||
|
stockTurret: tank.turrets[0],
|
||||||
|
stockTrack: tank.tracks[0],
|
||||||
|
track,
|
||||||
|
},
|
||||||
|
{ equipmentDefinitions, provisionDefinitions, tankModelDefinition },
|
||||||
|
);
|
||||||
|
let { dispersion } = characteristics;
|
||||||
|
const gunRotationSpeed = degToRad(characteristics.gunTraverseSpeed);
|
||||||
|
const mockTank = useLoader(GLTFLoader, asset(`3d/tanks/models/6929.glb`));
|
||||||
|
const texture = useLoader(TextureLoader, imgur('C28Z8nU'));
|
||||||
|
const path = new Vector3();
|
||||||
|
const direction = new Vector3();
|
||||||
|
texture.anisotropy = 2;
|
||||||
|
|
||||||
|
useFrame(({ camera }) => {
|
||||||
|
if (!material.current) return;
|
||||||
|
material.current.opacity = clamp(0.5 * camera.position.y, 0, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
let lastTime = 0;
|
||||||
|
let DEBUG_disable = false;
|
||||||
|
|
||||||
|
useFrame(({ raycaster, camera, clock, gl }) => {
|
||||||
|
if (
|
||||||
|
display !== TankopediaDisplay.ShootingRange ||
|
||||||
|
!playground.current ||
|
||||||
|
!targetCircleWrapper.current ||
|
||||||
|
DEBUG_disable ||
|
||||||
|
!clientTargetCircle.current ||
|
||||||
|
!serverTargetCircle.current
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
raycaster.setFromCamera(emptyVector, camera);
|
||||||
|
|
||||||
|
const cameraIntersections = raycaster.intersectObjects(
|
||||||
|
playground.current.children,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
let length: number;
|
||||||
|
|
||||||
|
if (cameraIntersections.length === 0) {
|
||||||
|
length = characteristics.shellRange;
|
||||||
|
direction.set(0, 0, -1).applyQuaternion(camera.quaternion);
|
||||||
|
aimTarget.copy(shellOrigin).addScaledVector(direction, length);
|
||||||
|
} else {
|
||||||
|
aimTarget.copy(cameraIntersections[0].point);
|
||||||
|
path.copy(aimTarget).sub(shellOrigin);
|
||||||
|
length = path.length();
|
||||||
|
direction.copy(path).normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
const deltaT = clock.elapsedTime - lastTime;
|
||||||
|
lastTime = clock.elapsedTime;
|
||||||
|
|
||||||
|
const desiredPitch = Math.asin(direction.y);
|
||||||
|
const desiredYaw = Math.atan2(-direction.x, -direction.z);
|
||||||
|
const deltaPitch = desiredPitch - lastPitch;
|
||||||
|
const deltaYaw = desiredYaw - lastYaw;
|
||||||
|
const deltaAngle = Math.sqrt(deltaPitch ** 2 + deltaYaw ** 2);
|
||||||
|
const maxPossibleRotationInTime = gunRotationSpeed * deltaT;
|
||||||
|
const clampedDeltaAngle = Math.min(maxPossibleRotationInTime, deltaAngle);
|
||||||
|
const clampedDeltaPitch =
|
||||||
|
deltaAngle === 0 ? 0 : deltaPitch * (clampedDeltaAngle / deltaAngle);
|
||||||
|
const clampedDeltaYaw =
|
||||||
|
deltaAngle === 0 ? 0 : deltaYaw * (clampedDeltaAngle / deltaAngle);
|
||||||
|
|
||||||
|
const [pitch, yaw] = applyPitchYawLimits(
|
||||||
|
lastPitch + clampedDeltaPitch,
|
||||||
|
lastYaw + clampedDeltaYaw,
|
||||||
|
gunModelDefinition.pitch,
|
||||||
|
turretModelDefinition.yaw,
|
||||||
|
);
|
||||||
|
|
||||||
|
const actualDeltaPitch = pitch - lastPitch;
|
||||||
|
const actualDeltaYaw = yaw - lastYaw;
|
||||||
|
const deltaRotation = Math.abs(actualDeltaPitch) + Math.abs(actualDeltaYaw);
|
||||||
|
const rotationSpeed = deltaRotation / deltaT;
|
||||||
|
const turretTraversePenalty =
|
||||||
|
characteristics.dispersionTurretTraversing * radToDeg(rotationSpeed);
|
||||||
|
|
||||||
|
const penalty = Math.sqrt(1 + turretTraversePenalty ** 2);
|
||||||
|
|
||||||
|
if (penalty === 1) {
|
||||||
|
const dispersionMod = dispersion - characteristics.dispersion;
|
||||||
|
const dispersionModDischarged =
|
||||||
|
dispersionMod * Math.exp(-deltaT / characteristics.aimTime) -
|
||||||
|
dispersionMod;
|
||||||
|
|
||||||
|
dispersion += dispersionModDischarged;
|
||||||
|
} else {
|
||||||
|
dispersion = Math.max(characteristics.dispersion * penalty, dispersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
modelTransformEvent.emit({ pitch, yaw });
|
||||||
|
lastPitch = pitch;
|
||||||
|
lastYaw = yaw;
|
||||||
|
|
||||||
|
const gunDirection = new Vector3(0, 0, -1)
|
||||||
|
.applyAxisAngle(I_HAT, pitch)
|
||||||
|
.applyAxisAngle(J_HAT, yaw);
|
||||||
|
|
||||||
|
raycaster.set(shellOrigin, gunDirection);
|
||||||
|
|
||||||
|
const gunIntersections = raycaster.intersectObjects(
|
||||||
|
playground.current.children,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
let gunTarget: Vector3;
|
||||||
|
|
||||||
|
if (gunIntersections.length === 0) {
|
||||||
|
gunTarget = shellOrigin
|
||||||
|
.clone()
|
||||||
|
.add(gunDirection.multiplyScalar(characteristics.shellRange));
|
||||||
|
} else {
|
||||||
|
gunTarget = gunIntersections[0].point;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetCircleWrapper.current.position.copy(gunTarget);
|
||||||
|
|
||||||
|
const distanceToTarget = gunTarget.distanceTo(shellOrigin);
|
||||||
|
const dispersionRadius = dispersion * (distanceToTarget / 100);
|
||||||
|
const dispersionSize = 2 * dispersionRadius;
|
||||||
|
const targetDistanceFromCamera = gunTarget.distanceTo(camera.position);
|
||||||
|
const angularSize =
|
||||||
|
2 * Math.atan(dispersionSize / (2 * targetDistanceFromCamera));
|
||||||
|
const verticalScreenSize =
|
||||||
|
2 * Math.tan(degToRad((camera as PerspectiveCamera).fov) / 2);
|
||||||
|
const screenSpaceSize =
|
||||||
|
gl.domElement.clientHeight * (angularSize / verticalScreenSize);
|
||||||
|
const targetCircleDotsSize = `${screenSpaceSize}px`;
|
||||||
|
|
||||||
|
clientTargetCircle.current.style.width = targetCircleDotsSize;
|
||||||
|
clientTargetCircle.current.style.height = targetCircleDotsSize;
|
||||||
|
serverTargetCircle.current.style.width = targetCircleDotsSize;
|
||||||
|
serverTargetCircle.current.style.height = targetCircleDotsSize;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{show && display !== TankopediaDisplay.ShootingRange && (
|
||||||
|
<mesh rotation={[-Math.PI / 2, 0, 0]}>
|
||||||
|
<planeGeometry args={[10, 10]} />
|
||||||
|
<meshStandardMaterial ref={material} map={texture} transparent />
|
||||||
|
</mesh>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{display === TankopediaDisplay.ShootingRange && (
|
||||||
|
<>
|
||||||
|
<group ref={targetCircleWrapper}>
|
||||||
|
<Html center occlude={false} zIndexRange={[0, 0]}>
|
||||||
|
<TargetCircle variant="client" ref={clientTargetCircle} />
|
||||||
|
<TargetCircle variant="server" ref={serverTargetCircle} />
|
||||||
|
</Html>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<group ref={playground}>
|
||||||
|
<mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
|
||||||
|
<planeGeometry args={[100, 100]} />
|
||||||
|
<meshStandardMaterial color={0x404040} />
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
<mesh
|
||||||
|
receiveShadow
|
||||||
|
castShadow
|
||||||
|
position={[10, 1.5, -10]}
|
||||||
|
rotation={[0, Math.PI / 4, 0]}
|
||||||
|
>
|
||||||
|
<boxGeometry args={[1, 3, 1]} />
|
||||||
|
<meshStandardMaterial color={0xff8040} />
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
<group position={[0, 0, -25]} rotation={[-Math.PI / 2, 0, Math.PI]}>
|
||||||
|
<primitive object={mockTank.scene} />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
+36
-11
@@ -1,6 +1,6 @@
|
|||||||
import { normalizeAngleRad } from '@blitzkit/core';
|
import { normalizeAngleRad } from '@blitzkit/core';
|
||||||
import { invalidate, useThree, type ThreeEvent } from '@react-three/fiber';
|
import { invalidate, useThree, type ThreeEvent } from '@react-three/fiber';
|
||||||
import { memo, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { Group, Mesh, MeshStandardMaterial, Vector2 } from 'three';
|
import { Group, Mesh, MeshStandardMaterial, Vector2 } from 'three';
|
||||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
|
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
|
||||||
@@ -10,16 +10,18 @@ import { useModel } from '../../../../../../hooks/useModel';
|
|||||||
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
|
import { useTankModelDefinition } from '../../../../../../hooks/useTankModelDefinition';
|
||||||
import { useTankTransform } from '../../../../../../hooks/useTankTransform';
|
import { useTankTransform } from '../../../../../../hooks/useTankTransform';
|
||||||
import { Duel } from '../../../../../../stores/duel';
|
import { Duel } from '../../../../../../stores/duel';
|
||||||
import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral';
|
import {
|
||||||
import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent';
|
ShootingRangeZoom,
|
||||||
|
TankopediaEphemeral,
|
||||||
|
} from '../../../../../../stores/tankopediaEphemeral';
|
||||||
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
|
import { TankopediaDisplay } from '../../../../../../stores/tankopediaPersistent/constants';
|
||||||
import { ModelTankWrapper } from '../../../../../Armor/components/ModelTankWrapper';
|
import { ModelTankWrapper } from '../../../../../Armor/components/ModelTankWrapper';
|
||||||
|
|
||||||
export const TankModel = memo(() => {
|
export function TankModel() {
|
||||||
const mutateDuel = Duel.useMutation();
|
const mutateDuel = Duel.useMutation();
|
||||||
const duelStore = Duel.useStore();
|
const duelStore = Duel.useStore();
|
||||||
const protagonist = Duel.use((draft) => draft.protagonist);
|
const protagonist = Duel.use((draft) => draft.protagonist);
|
||||||
const tankopediaPersistentStore = TankopediaPersistent.useStore();
|
const tankopediaEphemeralStore = TankopediaEphemeral.useStore();
|
||||||
const track = Duel.use((state) => state.protagonist.track);
|
const track = Duel.use((state) => state.protagonist.track);
|
||||||
const turret = Duel.use((state) => state.protagonist.turret);
|
const turret = Duel.use((state) => state.protagonist.turret);
|
||||||
const canvas = useThree((state) => state.gl.domElement);
|
const canvas = useThree((state) => state.gl.domElement);
|
||||||
@@ -35,6 +37,18 @@ export const TankModel = memo(() => {
|
|||||||
const { gltf } = useModel(protagonist.tank.id);
|
const { gltf } = useModel(protagonist.tank.id);
|
||||||
const nodes = Object.values(gltf.nodes);
|
const nodes = Object.values(gltf.nodes);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribeShootingRangeZoom = tankopediaEphemeralStore.subscribe(
|
||||||
|
(state) => state.shootingRangeZoom,
|
||||||
|
(zoom) => {
|
||||||
|
if (!hullContainer.current) return;
|
||||||
|
hullContainer.current.visible = zoom === ShootingRangeZoom.Arcade;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return unsubscribeShootingRangeZoom;
|
||||||
|
}, []);
|
||||||
|
|
||||||
useTankTransform(track, turret, turretContainer, gunContainer);
|
useTankTransform(track, turret, turretContainer, gunContainer);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -67,7 +81,7 @@ export const TankModel = memo(() => {
|
|||||||
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
||||||
if (
|
if (
|
||||||
isTrack &&
|
isTrack &&
|
||||||
tankopediaPersistentStore.getState().display ===
|
tankopediaEphemeralStore.getState().display ===
|
||||||
TankopediaDisplay.Model
|
TankopediaDisplay.Model
|
||||||
) {
|
) {
|
||||||
position.set(event.clientX, event.clientY);
|
position.set(event.clientX, event.clientY);
|
||||||
@@ -103,7 +117,7 @@ export const TankModel = memo(() => {
|
|||||||
return jsxTree(
|
return jsxTree(
|
||||||
node,
|
node,
|
||||||
{
|
{
|
||||||
mesh: { onPointerDown },
|
mesh: { onPointerDown, castShadow: true },
|
||||||
// group(group) {
|
// group(group) {
|
||||||
// return {
|
// return {
|
||||||
// visible:
|
// visible:
|
||||||
@@ -143,7 +157,12 @@ export const TankModel = memo(() => {
|
|||||||
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
if (!isTurret) return;
|
if (
|
||||||
|
!isTurret ||
|
||||||
|
tankopediaEphemeralStore.getState().display ===
|
||||||
|
TankopediaDisplay.ShootingRange
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
position.set(event.clientX, event.clientY);
|
position.set(event.clientX, event.clientY);
|
||||||
yaw = protagonist.yaw;
|
yaw = protagonist.yaw;
|
||||||
@@ -195,7 +214,7 @@ export const TankModel = memo(() => {
|
|||||||
return jsxTree(
|
return jsxTree(
|
||||||
node,
|
node,
|
||||||
{
|
{
|
||||||
mesh: { onPointerDown },
|
mesh: { onPointerDown, castShadow: true },
|
||||||
// group(group) {
|
// group(group) {
|
||||||
// return {
|
// return {
|
||||||
// visible:
|
// visible:
|
||||||
@@ -240,6 +259,12 @@ export const TankModel = memo(() => {
|
|||||||
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
function onPointerDown(event: ThreeEvent<PointerEvent>) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (
|
||||||
|
tankopediaEphemeralStore.getState().display ===
|
||||||
|
TankopediaDisplay.ShootingRange
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
mutateTankopediaEphemeral((draft) => {
|
mutateTankopediaEphemeral((draft) => {
|
||||||
draft.controlsEnabled = false;
|
draft.controlsEnabled = false;
|
||||||
});
|
});
|
||||||
@@ -290,7 +315,7 @@ export const TankModel = memo(() => {
|
|||||||
return jsxTree(
|
return jsxTree(
|
||||||
node,
|
node,
|
||||||
{
|
{
|
||||||
mesh: { onPointerDown },
|
mesh: { onPointerDown, castShadow: true },
|
||||||
// group(group) {
|
// group(group) {
|
||||||
// return {
|
// return {
|
||||||
// visible:
|
// visible:
|
||||||
@@ -316,4 +341,4 @@ export const TankModel = memo(() => {
|
|||||||
</group>
|
</group>
|
||||||
</ModelTankWrapper>
|
</ModelTankWrapper>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
+13
-11
@@ -14,7 +14,6 @@ import { useOnScreen } from '../../../../../hooks/useOnScreen';
|
|||||||
import { useTankModelDefinition } from '../../../../../hooks/useTankModelDefinition';
|
import { useTankModelDefinition } from '../../../../../hooks/useTankModelDefinition';
|
||||||
import { Duel } from '../../../../../stores/duel';
|
import { Duel } from '../../../../../stores/duel';
|
||||||
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
|
import { TankopediaEphemeral } from '../../../../../stores/tankopediaEphemeral';
|
||||||
import { TankopediaPersistent } from '../../../../../stores/tankopediaPersistent';
|
|
||||||
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
|
import { TankopediaDisplay } from '../../../../../stores/tankopediaPersistent/constants';
|
||||||
import { Armor } from '../../../../Armor';
|
import { Armor } from '../../../../Armor';
|
||||||
import { ArmorPlateDisplay } from '../../../../Armor/components/ArmorPlateDisplay';
|
import { ArmorPlateDisplay } from '../../../../Armor/components/ArmorPlateDisplay';
|
||||||
@@ -39,17 +38,15 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||||
const protagonist = Duel.use((state) => state.protagonist);
|
const protagonistGun = Duel.use((state) => state.protagonist.gun);
|
||||||
|
const protagonistTurret = Duel.use((state) => state.protagonist.turret);
|
||||||
const mutateDuel = Duel.useMutation();
|
const mutateDuel = Duel.useMutation();
|
||||||
const tankModelDefinition = useTankModelDefinition();
|
const tankModelDefinition = useTankModelDefinition();
|
||||||
const turretModelDefinition =
|
const turretModelDefinition =
|
||||||
tankModelDefinition.turrets[protagonist.turret.id];
|
tankModelDefinition.turrets[protagonistTurret.id];
|
||||||
const gunModelDefinition =
|
const gunModelDefinition =
|
||||||
turretModelDefinition.guns[protagonist.gun.gun_type!.value.base.id];
|
turretModelDefinition.guns[protagonistGun.gun_type!.value.base.id];
|
||||||
const display = TankopediaPersistent.useDeferred(
|
const display = TankopediaEphemeral.use((state) => state.display);
|
||||||
(state) => state.display,
|
|
||||||
TankopediaDisplay.Model,
|
|
||||||
);
|
|
||||||
const onScreen = useOnScreen(canvas);
|
const onScreen = useOnScreen(canvas);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => canvas.current!, []);
|
useImperativeHandle(ref, () => canvas.current!, []);
|
||||||
@@ -143,7 +140,7 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
hasImprovedVerticalStabilizer,
|
hasImprovedVerticalStabilizer,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, [protagonist.gun, protagonist.turret]);
|
}, [protagonistGun, protagonistTurret]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (display !== TankopediaDisplay.DynamicArmor) {
|
if (display !== TankopediaDisplay.DynamicArmor) {
|
||||||
@@ -161,7 +158,13 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Canvas
|
<Canvas
|
||||||
frameloop={onScreen ? 'demand' : 'never'}
|
frameloop={
|
||||||
|
onScreen
|
||||||
|
? display === TankopediaDisplay.ShootingRange
|
||||||
|
? 'always'
|
||||||
|
: 'demand'
|
||||||
|
: 'never'
|
||||||
|
}
|
||||||
gl={{
|
gl={{
|
||||||
clippingPlanes: [],
|
clippingPlanes: [],
|
||||||
localClippingEnabled: true,
|
localClippingEnabled: true,
|
||||||
@@ -177,7 +180,6 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
style={{ userSelect: 'none' }}
|
style={{ userSelect: 'none' }}
|
||||||
camera={{ fov: 25, far: 32 }}
|
|
||||||
>
|
>
|
||||||
<Controls />
|
<Controls />
|
||||||
<SceneProps />
|
<SceneProps />
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { fetchTankDefinitions, TankType } from '@blitzkit/core';
|
import { TankType } from '@blitzkit/core';
|
||||||
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
import { Box, Flex, Heading, Text } from '@radix-ui/themes';
|
||||||
import { times } from 'lodash-es';
|
import { times } from 'lodash-es';
|
||||||
import { Suspense, useEffect, useMemo, useRef } from 'react';
|
import { Suspense, useEffect, useMemo, useRef } from 'react';
|
||||||
import { NAVBAR_HEIGHT } from '../../../constants/navbar';
|
import { NAVBAR_HEIGHT } from '../../../constants/navbar';
|
||||||
|
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
|
||||||
import { Var } from '../../../core/radix/var';
|
import { Var } from '../../../core/radix/var';
|
||||||
import { useFullScreen } from '../../../hooks/useFullScreen';
|
import { useFullScreen } from '../../../hooks/useFullScreen';
|
||||||
import { Duel } from '../../../stores/duel';
|
import { Duel } from '../../../stores/duel';
|
||||||
@@ -14,7 +15,7 @@ import { Options } from './components/Options';
|
|||||||
import { TankSandbox } from './components/TankSandbox';
|
import { TankSandbox } from './components/TankSandbox';
|
||||||
import { TankSandboxLoader } from './components/TankSandboxLoader';
|
import { TankSandboxLoader } from './components/TankSandboxLoader';
|
||||||
|
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
const tankDefinitions = await awaitableTankDefinitions;
|
||||||
|
|
||||||
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
@@ -105,7 +106,11 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
|||||||
maxWidth="40rem"
|
maxWidth="40rem"
|
||||||
>
|
>
|
||||||
<Flex align="center" gap="2">
|
<Flex align="center" gap="2">
|
||||||
<Heading size={{ initial: '7', lg: '8' }}>
|
<Heading
|
||||||
|
color={treeColor}
|
||||||
|
trim="end"
|
||||||
|
size={{ initial: '7', lg: '8' }}
|
||||||
|
>
|
||||||
<Icon width="1em" height="1em" />
|
<Icon width="1em" height="1em" />
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
assertSecret,
|
assertSecret,
|
||||||
asset,
|
asset,
|
||||||
fetchModelDefinitions,
|
|
||||||
fetchProvisionDefinitions,
|
|
||||||
TankPriceType,
|
TankPriceType,
|
||||||
TankType,
|
TankType,
|
||||||
TIER_ROMAN_NUMERALS,
|
TIER_ROMAN_NUMERALS,
|
||||||
@@ -11,6 +9,8 @@ import strings from '@blitzkit/core/lang/en-US.json';
|
|||||||
import { ChevronLeftIcon, MixIcon, UpdateIcon } from '@radix-ui/react-icons';
|
import { ChevronLeftIcon, MixIcon, UpdateIcon } from '@radix-ui/react-icons';
|
||||||
import { Button, Code, Dialog, Flex, Link } from '@radix-ui/themes';
|
import { Button, Code, Dialog, Flex, Link } from '@radix-ui/themes';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { awaitableModelDefinitions } from '../../../core/awaitables/modelDefinitions';
|
||||||
|
import { awaitableProvisionDefinitions } from '../../../core/awaitables/provisionDefinitions';
|
||||||
import { tankToDuelMember } from '../../../core/blitzkit/tankToDuelMember';
|
import { tankToDuelMember } from '../../../core/blitzkit/tankToDuelMember';
|
||||||
import { App } from '../../../stores/app';
|
import { App } from '../../../stores/app';
|
||||||
import { Duel } from '../../../stores/duel';
|
import { Duel } from '../../../stores/duel';
|
||||||
@@ -20,8 +20,10 @@ import { ScienceIcon } from '../../ScienceIcon';
|
|||||||
import { TankSearch } from '../../TankSearch';
|
import { TankSearch } from '../../TankSearch';
|
||||||
import { Listing } from './components/Listing';
|
import { Listing } from './components/Listing';
|
||||||
|
|
||||||
const provisionDefinitions = await fetchProvisionDefinitions();
|
const [provisionDefinitions, modelDefinitions] = await Promise.all([
|
||||||
const modelDefinitions = await fetchModelDefinitions();
|
awaitableProvisionDefinitions,
|
||||||
|
awaitableModelDefinitions,
|
||||||
|
]);
|
||||||
|
|
||||||
export function MetaSection() {
|
export function MetaSection() {
|
||||||
const developerMode = App.useDeferred((state) => state.developerMode, false);
|
const developerMode = App.useDeferred((state) => state.developerMode, false);
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import {
|
import { asset, formatCompact, TIER_ROMAN_NUMERALS } from '@blitzkit/core';
|
||||||
asset,
|
|
||||||
fetchAverageDefinitions,
|
|
||||||
fetchTankDefinitions,
|
|
||||||
formatCompact,
|
|
||||||
TIER_ROMAN_NUMERALS,
|
|
||||||
} from '@blitzkit/core';
|
|
||||||
import { Box, Flex, Link, Skeleton, Text } from '@radix-ui/themes';
|
import { Box, Flex, Link, Skeleton, Text } from '@radix-ui/themes';
|
||||||
|
import { awaitableAverageDefinitions } from '../../../../core/awaitables/averageDefinitions';
|
||||||
|
import { awaitableTankDefinitions } from '../../../../core/awaitables/tankDefinitions';
|
||||||
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
|
import { TankopediaEphemeral } from '../../../../stores/tankopediaEphemeral';
|
||||||
import type { MaybeSkeletonComponentProps } from '../../../../types/maybeSkeletonComponentProps';
|
import type { MaybeSkeletonComponentProps } from '../../../../types/maybeSkeletonComponentProps';
|
||||||
|
|
||||||
@@ -15,8 +11,10 @@ type NodeProps = MaybeSkeletonComponentProps & {
|
|||||||
highlight?: boolean;
|
highlight?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
const [tankDefinitions, averageDefinitions] = await Promise.all([
|
||||||
const averageDefinitions = await fetchAverageDefinitions();
|
awaitableTankDefinitions,
|
||||||
|
awaitableAverageDefinitions,
|
||||||
|
]);
|
||||||
|
|
||||||
export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
||||||
const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier);
|
const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { asset, fetchTankDefinitions, imgur, TankType } from '@blitzkit/core';
|
import { asset, imgur, TankType } from '@blitzkit/core';
|
||||||
import { CaretLeftIcon, CaretRightIcon, PlusIcon } from '@radix-ui/react-icons';
|
import { CaretLeftIcon, CaretRightIcon, PlusIcon } from '@radix-ui/react-icons';
|
||||||
import { Flex, Heading, IconButton, ScrollArea, Text } from '@radix-ui/themes';
|
import { Flex, Heading, IconButton, ScrollArea, Text } from '@radix-ui/themes';
|
||||||
import { Fragment, useEffect, useMemo, useRef, useState } from 'react';
|
import { Fragment, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
|
||||||
import { Duel } from '../../../stores/duel';
|
import { Duel } from '../../../stores/duel';
|
||||||
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
|
||||||
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
|
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
|
||||||
@@ -10,15 +11,12 @@ import { Node } from './components/Node';
|
|||||||
|
|
||||||
type Line = number[];
|
type Line = number[];
|
||||||
|
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
const tankDefinitions = await awaitableTankDefinitions;
|
||||||
|
|
||||||
export const XP_MULTIPLIERS = [1, 2, 3, 4, 5, 10];
|
export const XP_MULTIPLIERS = [1, 2, 3, 4, 5, 10];
|
||||||
|
|
||||||
export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||||
const master = Duel.use((state) => state.protagonist.tank);
|
const master = Duel.use((state) => state.protagonist.tank);
|
||||||
|
|
||||||
if (master.type !== TankType.RESEARCHABLE || master.deprecated) return null;
|
|
||||||
|
|
||||||
const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier);
|
const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier);
|
||||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||||
const container = useRef<HTMLDivElement>(null);
|
const container = useRef<HTMLDivElement>(null);
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { fetchReviews, youtubers } from '@blitzkit/core';
|
import { youtubers } from '@blitzkit/core';
|
||||||
import { Box, Flex, Heading, Link, Skeleton, Text } from '@radix-ui/themes';
|
import { Box, Flex, Heading, Link, Skeleton, Text } from '@radix-ui/themes';
|
||||||
|
import { awaitableReviews } from '../../core/awaitables/reviews';
|
||||||
import { Var } from '../../core/radix/var';
|
import { Var } from '../../core/radix/var';
|
||||||
import { Duel } from '../../stores/duel';
|
import { Duel } from '../../stores/duel';
|
||||||
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
|
import type { MaybeSkeletonComponentProps } from '../../types/maybeSkeletonComponentProps';
|
||||||
import { PageWrapper } from '../PageWrapper';
|
import { PageWrapper } from '../PageWrapper';
|
||||||
import { VerifiedIcon } from '../VerifiedIcon';
|
import { VerifiedIcon } from '../VerifiedIcon';
|
||||||
|
|
||||||
const reviews = await fetchReviews();
|
const reviews = await awaitableReviews;
|
||||||
|
|
||||||
export function VideoSection({ skeleton }: MaybeSkeletonComponentProps) {
|
export function VideoSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||||
const tank = Duel.use((state) => state.protagonist.tank);
|
const tank = Duel.use((state) => state.protagonist.tank);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { fetchTankDefinitions } from '@blitzkit/core';
|
|
||||||
import { Flex, Table } from '@radix-ui/themes';
|
import { Flex, Table } from '@radix-ui/themes';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
|
||||||
import { TierList } from '../../../stores/tierList';
|
import { TierList } from '../../../stores/tierList';
|
||||||
import { tierListRowElements } from '../Table/constants';
|
import { tierListRowElements } from '../Table/constants';
|
||||||
import { TierListTile } from '../Tile';
|
import { TierListTile } from '../Tile';
|
||||||
@@ -10,7 +10,7 @@ interface TierListRowProps {
|
|||||||
index: number;
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
const tankDefinitions = await awaitableTankDefinitions;
|
||||||
|
|
||||||
export function TierListRow({ index }: TierListRowProps) {
|
export function TierListRow({ index }: TierListRowProps) {
|
||||||
const tanks = TierList.use((state) => state.rows[index].tanks);
|
const tanks = TierList.use((state) => state.rows[index].tanks);
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import {
|
import { metaSortTank } from '@blitzkit/core';
|
||||||
fetchGameDefinitions,
|
|
||||||
fetchTankDefinitions,
|
|
||||||
metaSortTank,
|
|
||||||
} from '@blitzkit/core';
|
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { times } from 'lodash-es';
|
import { times } from 'lodash-es';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
import { awaitableGameDefinitions } from '../../core/awaitables/gameDefinitions';
|
||||||
|
import { awaitableTankDefinitions } from '../../core/awaitables/tankDefinitions';
|
||||||
import { filterTank } from '../../core/blitzkit/filterTank';
|
import { filterTank } from '../../core/blitzkit/filterTank';
|
||||||
import { $tankFilters } from '../../stores/tankFilters';
|
import { $tankFilters } from '../../stores/tankFilters';
|
||||||
import { TierList } from '../../stores/tierList';
|
import { TierList } from '../../stores/tierList';
|
||||||
@@ -13,8 +11,11 @@ import { SkeletonTankCard } from '../TankSearch/components/SkeletonTankCard';
|
|||||||
import { TankCardWrapper } from '../TankSearch/components/TankCardWrapper';
|
import { TankCardWrapper } from '../TankSearch/components/TankCardWrapper';
|
||||||
import { TierListTile } from './Tile';
|
import { TierListTile } from './Tile';
|
||||||
|
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
const [tankDefinitions, gameDefinitions] = await Promise.all([
|
||||||
const gameDefinitions = await fetchGameDefinitions();
|
awaitableTankDefinitions,
|
||||||
|
awaitableGameDefinitions,
|
||||||
|
]);
|
||||||
|
|
||||||
const tanks = Object.values(tankDefinitions.tanks);
|
const tanks = Object.values(tankDefinitions.tanks);
|
||||||
|
|
||||||
const PREVIEW_COUNT = 32;
|
const PREVIEW_COUNT = 32;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { fetchTankDefinitions } from '@blitzkit/core';
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { awaitableTankDefinitions } from '../../core/awaitables/tankDefinitions';
|
||||||
import { generateTierListParams } from '../../core/blitzkit/generateTierListParams';
|
import { generateTierListParams } from '../../core/blitzkit/generateTierListParams';
|
||||||
import { TierList } from '../../stores/tierList';
|
import { TierList } from '../../stores/tierList';
|
||||||
|
|
||||||
const tankDefinitions = await fetchTankDefinitions();
|
const tankDefinitions = await awaitableTankDefinitions;
|
||||||
|
|
||||||
export function URLManager() {
|
export function URLManager() {
|
||||||
const rows = TierList.use((state) => state.rows);
|
const rows = TierList.use((state) => state.rows);
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { fetchAverageDefinitions } from '@blitzkit/core';
|
||||||
|
|
||||||
|
export const awaitableAverageDefinitions = fetchAverageDefinitions();
|
||||||
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user