feat: move tankopedia to tankDefinitions

This commit is contained in:
TrèsAbhi committed 2023-12-05 17:36:18 -06:00
1 parent cfe71e15a4
commit ad0fc69596
25 files changed
+270 -295

No files matched your search

+1
View File
@@ -28,6 +28,7 @@
"playerinfo", "playerinfo",
"ratingsleaderboard", "ratingsleaderboard",
"ratingsplot", "ratingsplot",
"researchable",
"Resvg", "Resvg",
"roboto", "roboto",
"searchclans", "searchclans",
+1 -1
View File
@@ -9,7 +9,7 @@
"build:website": "next build", "build:website": "next build",
"build:docs": "vitepress build docs", "build:docs": "vitepress build docs",
"build:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard", "build:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard",
"build:tankopedia": "vite-node --script scripts/buildTankopedia", "build:assets": "vite-node --script scripts/buildAssets",
"build:ratingsplot": "vite-node --script scripts/buildRatingsPlot", "build:ratingsplot": "vite-node --script scripts/buildRatingsPlot",
"dev:bot": "yarn build:bot && yarn start:bot", "dev:bot": "yarn build:bot && yarn start:bot",
"dev:docs": "vitepress dev docs", "dev:docs": "vitepress dev docs",
@@ -2,13 +2,16 @@ import { Octokit } from '@octokit/rest';
import decompress from 'decompress'; import decompress from 'decompress';
import { config } from 'dotenv'; import { config } from 'dotenv';
import { writeFileSync } from 'fs'; import { writeFileSync } from 'fs';
import { cp, mkdir, readFile, readdir, rm, writeFile } from 'fs/promises'; import { readFile, readdir, rm } from 'fs/promises';
import { load } from 'js-yaml'; import { load } from 'js-yaml';
import { argv, env } from 'process'; import { argv, env } from 'process';
import { ElementCompact, xml2js } from 'xml-js'; import { ElementCompact, xml2js } from 'xml-js';
import { parse } from 'yaml'; import { parse } from 'yaml';
import { NATION_IDS } from '../src/constants/nations'; import { NATION_IDS } from '../src/constants/nations';
import { BlitzkriegTankopedia, Tier } from '../src/core/blitzkrieg/tankopedia'; import {
TankDefinitions,
Tier,
} from '../src/core/blitzkrieg/definitions/tanks';
import commitMultipleFiles, { FileChange } from './commitMultipleFiles'; import commitMultipleFiles, { FileChange } from './commitMultipleFiles';
config(); config();
@@ -34,27 +37,19 @@ const directoriesOfInterest = {
}; };
const directoriesOfInterestArray = Object.values(directoriesOfInterest); const directoriesOfInterestArray = Object.values(directoriesOfInterest);
console.log('Removing old tankopedia files...');
await rm('dist/tankopedia', { recursive: true, force: true });
if (!noRewrite) { if (!noRewrite) {
console.log('Removing old temp files...');
await rm(TEMP, { recursive: true, force: true }); await rm(TEMP, { recursive: true, force: true });
console.log('Extracting Blitz files...');
await decompress(apkFile, TEMP, { await decompress(apkFile, TEMP, {
filter: (file) => filter: (file) =>
directoriesOfInterestArray.some((dir) => file.path.startsWith(dir)), directoriesOfInterestArray.some((dir) => file.path.startsWith(dir)),
}); });
} }
console.log('Creating new tankopedia folder');
await mkdir('dist/tankopedia');
const strings = await readFile( const strings = await readFile(
`${TEMP}/${directoriesOfInterest.strings}/${LANGUAGE}.yaml`, `${TEMP}/${directoriesOfInterest.strings}/${LANGUAGE}.yaml`,
'utf-8', 'utf-8',
).then((data) => load(data) as Record<string, string>); ).then((data) => load(data) as Record<string, string>);
const tankopedia: BlitzkriegTankopedia = {}; const tankDefinitions: TankDefinitions = {};
const tankIds: number[] = []; const tankIds: number[] = [];
const images: Record<number, { big: string; small: string }> = {}; const images: Record<number, { big: string; small: string }> = {};
const nations = await readdir( const nations = await readdir(
@@ -76,7 +71,6 @@ Promise.all(
await Promise.all( await Promise.all(
Object.entries<ElementCompact>(list.root).map( Object.entries<ElementCompact>(list.root).map(
async ([vehicle, vehicleData]) => { async ([vehicle, vehicleData]) => {
// Tankopedia
const nationVehicleId = parseInt(list.root[vehicle].id._text); const nationVehicleId = parseInt(list.root[vehicle].id._text);
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1; const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
const name = strings[vehicleData.userString._text]; const name = strings[vehicleData.userString._text];
@@ -94,7 +88,7 @@ Promise.all(
} }
tankIds.push(id); tankIds.push(id);
tankopedia[id] = { tankDefinitions[id] = {
id, id,
name, name,
name_short, name_short,
@@ -121,101 +115,91 @@ Promise.all(
.replace(/~res:\//, '') .replace(/~res:\//, '')
.replace(/\..+/, '')}.packed.webp`, .replace(/\..+/, '')}.packed.webp`,
}; };
if (!publish) {
cp(images[id].small, `dist/tankopedia/icons/small/${id}.webp`);
cp(images[id].big, `dist/tankopedia/icons/big/${id}.webp`);
}
}, },
), ),
); );
}), }),
).then(async () => { ).then(async () => {
if (publish) { if (!publish) return;
const octokit = new Octokit({ auth: env.GH_TOKEN });
// const tankopediaChange: FileChange = {
// content: JSON.stringify(tankopedia),
// path: 'tankopedia.json',
// encoding: 'utf-8',
// };
// const iconsChange = (
// await Promise.all(
// tankIds.map(async (id) => {
// try {
// const [contentSmall, contentBig] = await Promise.all([
// readFile(images[id].small, { encoding: 'base64' }),
// readFile(images[id].big, { encoding: 'base64' }),
// ]);
// return [ const octokit = new Octokit({ auth: env.GH_TOKEN });
// { // const tankDefinitionsChange: FileChange = {
// content: contentBig, // content: JSON.stringify(tankDefinitions),
// path: `icons/big/${id}.webp`, // path: 'definitions/tanks.json',
// encoding: 'base64', // encoding: 'utf-8',
// }, // };
// { // const iconsChange = (
// content: contentSmall, // await Promise.all(
// path: `icons/small/${id}.webp`, // tankIds.map(async (id) => {
// encoding: 'base64', // try {
// }, // const [contentSmall, contentBig] = await Promise.all([
// ]; // readFile(images[id].small, { encoding: 'base64' }),
// } catch (error) { // readFile(images[id].big, { encoding: 'base64' }),
// console.log(error); // ]);
// return undefined;
// }
// }),
// )
// ).flat() as FileChange[];
const flags = await readdir(`${TEMP}/${directoriesOfInterest.flags}`);
// const scratchedFlags = await Promise.all(
// flags
// .filter((flag) => flag.startsWith('flag_tutor-tank_'))
// .map(async (flag) => {
// const content = await readFile(
// `${TEMP}/${directoriesOfInterest.flags}/${flag}`,
// { encoding: 'base64' },
// );
// const name = flag.match(/flag_tutor-tank_(.+)\.packed\.webp/)![1];
// return { // return [
// content, // {
// encoding: 'base64', // content: contentBig,
// path: `flags/scratched/${name}.webp`, // path: `icons/big/${id}.webp`,
// } satisfies FileChange; // encoding: 'base64',
// }), // },
// ); // {
const circleFlags = await Promise.all( // content: contentSmall,
flags // path: `icons/small/${id}.webp`,
.filter((flag) => flag.startsWith('flag_profile-stat_')) // encoding: 'base64',
.map(async (flag) => { // },
const content = await readFile( // ];
`${TEMP}/${directoriesOfInterest.flags}/${flag}`, // } catch (error) {
{ encoding: 'base64' }, // console.log(error);
); // return undefined;
const name = flag.match(/flag_profile-stat_(.+)\.packed\.webp/)![1]; // }
// }),
// )
// ).flat() as FileChange[];
const flags = await readdir(`${TEMP}/${directoriesOfInterest.flags}`);
// const scratchedFlags = await Promise.all(
// flags
// .filter((flag) => flag.startsWith('flag_tutor-tank_'))
// .map(async (flag) => {
// const content = await readFile(
// `${TEMP}/${directoriesOfInterest.flags}/${flag}`,
// { encoding: 'base64' },
// );
// const name = flag.match(/flag_tutor-tank_(.+)\.packed\.webp/)![1];
return { // return {
content, // content,
encoding: 'base64', // encoding: 'base64',
path: `flags/circle/${name}.webp`, // path: `flags/scratched/${name}.webp`,
} satisfies FileChange; // } satisfies FileChange;
}), // }),
); // );
const circleFlags = await Promise.all(
flags
.filter((flag) => flag.startsWith('flag_profile-stat_'))
.map(async (flag) => {
const content = await readFile(
`${TEMP}/${directoriesOfInterest.flags}/${flag}`,
{ encoding: 'base64' },
);
const name = flag.match(/flag_profile-stat_(.+)\.packed\.webp/)![1];
console.log('Writing files...'); return {
await commitMultipleFiles( content,
octokit, encoding: 'base64',
'tresabhi', path: `flags/circle/${name}.webp`,
'blitzkrieg-assets', } satisfies FileChange;
'main', }),
new Date().toString(), );
circleFlags,
true, console.log('Writing files...');
); await commitMultipleFiles(
} else { octokit,
writeFile( 'tresabhi',
'dist/tankopedia/tankopedia.json', 'blitzkrieg-assets',
JSON.stringify(tankopedia, null, 2), 'main',
); new Date().toString(),
} circleFlags,
true,
);
}); });
+94 -92
View File
@@ -7,9 +7,9 @@ import { TreeTypeEnum } from '../../../components/Tanks';
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID'; import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
import fetchBlitz from '../../../core/blitz/fetchBlitz'; import fetchBlitz from '../../../core/blitz/fetchBlitz';
import { import {
BlitzkriegTankopediaEntry, TankDefinition,
tankopedia, tankDefinitions,
} from '../../../core/blitzkrieg/tankopedia'; } from '../../../core/blitzkrieg/definitions/tanks';
import { tankAverages } from '../../../core/blitzstars/tankAverages'; import { tankAverages } from '../../../core/blitzstars/tankAverages';
import calculateWN8 from '../../../core/statistics/calculateWN8'; import calculateWN8 from '../../../core/statistics/calculateWN8';
import { deltaTankStats } from '../../../core/statistics/deltaTankStats'; import { deltaTankStats } from '../../../core/statistics/deltaTankStats';
@@ -30,7 +30,7 @@ export default function SessionPage() {
list: { list: {
stats: IndividualTankStats; stats: IndividualTankStats;
tankopedia?: BlitzkriegTankopediaEntry; tankDefinitions?: TankDefinition;
career: IndividualTankStats; career: IndividualTankStats;
currentWN8?: number; currentWN8?: number;
careerWN8?: number; careerWN8?: number;
@@ -88,7 +88,7 @@ export default function SessionPage() {
}), }),
{}, {},
); );
const awaitedTankopedia = await tankopedia; const awaitedTankDefinitions = await tankDefinitions;
const awaitedTankAverages = await tankAverages; const awaitedTankAverages = await tankAverages;
const careerWN8 = const careerWN8 =
careerRaw[id].reduce( careerRaw[id].reduce(
@@ -119,7 +119,7 @@ export default function SessionPage() {
).map((stats) => { ).map((stats) => {
return { return {
stats, stats,
tankopedia: awaitedTankopedia[stats.tank_id], tankDefinitions: awaitedTankDefinitions[stats.tank_id],
career: career[stats.tank_id], career: career[stats.tank_id],
currentWN8: awaitedTankAverages[stats.tank_id] currentWN8: awaitedTankAverages[stats.tank_id]
? calculateWN8( ? calculateWN8(
@@ -238,95 +238,97 @@ export default function SessionPage() {
.sort( .sort(
(a, b) => b.stats.last_battle_time - a.stats.last_battle_time, (a, b) => b.stats.last_battle_time - a.stats.last_battle_time,
) )
.map(({ stats, tankopedia, career, careerWN8, currentWN8 }) => { .map(
const rowStats: Record< ({ stats, tankDefinitions, career, careerWN8, currentWN8 }) => {
CustomColumnDisplay, const rowStats: Record<
Breakdown.RowStatItem | undefined CustomColumnDisplay,
> = { Breakdown.RowStatItem | undefined
battles: { > = {
title: 'Battles', battles: {
current: stats.all.battles.toLocaleString(), title: 'Battles',
career: career.all.battles.toLocaleString(), current: stats.all.battles.toLocaleString(),
}, career: career.all.battles.toLocaleString(),
winrate: { },
title: 'Winrate', winrate: {
current: `${( title: 'Winrate',
100 * current: `${(
(stats.all.wins / stats.all.battles) 100 *
).toFixed(2)}%`, (stats.all.wins / stats.all.battles)
career: `${( ).toFixed(2)}%`,
100 * career: `${(
(career.all.wins / career.all.battles) 100 *
).toFixed(2)}%`, (career.all.wins / career.all.battles)
delta: ).toFixed(2)}%`,
stats.all.wins / stats.all.battles - delta:
career.all.wins / career.all.battles, stats.all.wins / stats.all.battles -
}, career.all.wins / career.all.battles,
wn8: { },
title: 'WN8', wn8: {
current: title: 'WN8',
currentWN8 === undefined current:
? undefined currentWN8 === undefined
: Math.round(currentWN8).toLocaleString(), ? undefined
career: : Math.round(currentWN8).toLocaleString(),
careerWN8 === undefined career:
? undefined careerWN8 === undefined
: Math.round(careerWN8).toLocaleString(), ? undefined
percentile: : Math.round(careerWN8).toLocaleString(),
currentWN8 === undefined percentile:
? undefined currentWN8 === undefined
: getWN8Percentile(currentWN8), ? undefined
}, : getWN8Percentile(currentWN8),
damage: { },
title: 'Damage', damage: {
current: Math.round( title: 'Damage',
stats.all.damage_dealt / stats.all.battles, current: Math.round(
).toLocaleString(), stats.all.damage_dealt / stats.all.battles,
career: Math.round( ).toLocaleString(),
career.all.damage_dealt / career.all.battles, career: Math.round(
).toLocaleString(), career.all.damage_dealt / career.all.battles,
delta: ).toLocaleString(),
stats.all.damage_dealt / stats.all.battles - delta:
career.all.damage_dealt / career.all.battles, stats.all.damage_dealt / stats.all.battles -
}, career.all.damage_dealt / career.all.battles,
none: undefined, },
}; none: undefined,
};
return ( return (
<Breakdown.Row <Breakdown.Row
color={session.color} color={session.color}
key={stats.tank_id} key={stats.tank_id}
minimized={!session.showCareer} minimized={!session.showCareer}
title={ title={
tankopedia?.name_short ?? tankDefinitions?.name_short ??
tankopedia?.name ?? tankDefinitions?.name ??
`Unknown tank ${stats.tank_id}` `Unknown tank ${stats.tank_id}`
} }
type="tank" type="tank"
tankType={tankopedia?.type} tankType={tankDefinitions?.type}
treeType={(() => { treeType={(() => {
if (tankopedia?.tree_type === 'collector') if (tankDefinitions?.tree_type === 'collector')
return TreeTypeEnum.Collector; return TreeTypeEnum.Collector;
if (tankopedia?.tree_type === 'premium') if (tankDefinitions?.tree_type === 'premium')
return TreeTypeEnum.Premium; return TreeTypeEnum.Premium;
})()} })()}
stats={session.customColumns.map((customColumn) => { stats={session.customColumns.map((customColumn) => {
const rowStat = rowStats[customColumn.display]; const rowStat = rowStats[customColumn.display];
if (!rowStat) return undefined; if (!rowStat) return undefined;
return { return {
...rowStat, ...rowStat,
delta: delta:
customColumn.showDelta && customColumn.showDelta &&
rowStat.percentile === undefined rowStat.percentile === undefined
? rowStat.delta ? rowStat.delta
: undefined, : undefined,
}; };
})} })}
/> />
); );
})} },
)}
</Breakdown.Root> </Breakdown.Root>
</div> </div>
</ContextMenu.Trigger> </ContextMenu.Trigger>
@@ -15,7 +15,7 @@ import {
NATIONS, NATIONS,
TIERS, TIERS,
TIER_ROMAN_NUMERALS, TIER_ROMAN_NUMERALS,
} from '../../../../core/blitzkrieg/tankopedia'; } from '../../../../core/blitzkrieg/definitions/tanks';
import mutateTankopedia, { import mutateTankopedia, {
TankopediaSortBy, TankopediaSortBy,
TankopediaSortDirection, TankopediaSortDirection,
@@ -1,12 +1,12 @@
import { CaretLeftIcon, CaretRightIcon } from '@radix-ui/react-icons'; import { CaretLeftIcon, CaretRightIcon } from '@radix-ui/react-icons';
import { Button, Flex, TextField } from '@radix-ui/themes'; import { Button, Flex, TextField } from '@radix-ui/themes';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { BlitzkriegTankopediaEntry } from '../../../../core/blitzkrieg/tankopedia'; import { TankDefinition } from '../../../../core/blitzkrieg/definitions/tanks';
interface PageTurnerProps { interface PageTurnerProps {
page: number; page: number;
setPage: (page: number) => void; setPage: (page: number) => void;
searchedList: BlitzkriegTankopediaEntry[]; searchedList: TankDefinition[];
tanksPerPage: number; tanksPerPage: number;
} }
+6 -6
View File
@@ -7,15 +7,15 @@ import { go } from 'fuzzysort';
import { Suspense, use, useEffect, useMemo, useRef, useState } from 'react'; import { Suspense, use, useEffect, useMemo, useRef, useState } from 'react';
import PageWrapper from '../../../components/PageWrapper'; import PageWrapper from '../../../components/PageWrapper';
import { asset } from '../../../core/blitzkrieg/asset'; import { asset } from '../../../core/blitzkrieg/asset';
import { tankIcon } from '../../../core/blitzkrieg/tankIcon';
import { import {
BlitzkriegTankopediaEntry,
TANK_ICONS, TANK_ICONS,
TANK_ICONS_COLLECTOR, TANK_ICONS_COLLECTOR,
TANK_ICONS_PREMIUM, TANK_ICONS_PREMIUM,
TIER_ROMAN_NUMERALS, TIER_ROMAN_NUMERALS,
tanks, TankDefinition,
} from '../../../core/blitzkrieg/tankopedia'; tanksDefinitionsArray,
} from '../../../core/blitzkrieg/definitions/tanks';
import { tankIcon } from '../../../core/blitzkrieg/tankIcon';
import { theme } from '../../../stitches.config'; import { theme } from '../../../stitches.config';
import { useTankopedia } from '../../../stores/tankopedia'; import { useTankopedia } from '../../../stores/tankopedia';
import { Options } from './components/Options'; import { Options } from './components/Options';
@@ -26,7 +26,7 @@ const TANKS_PER_PAGE = 3 * 2 ** 4;
export default function Page() { export default function Page() {
const tankopediaState = useTankopedia(); const tankopediaState = useTankopedia();
const awaitedTanks = use(tanks); const awaitedTanks = use(tanksDefinitionsArray);
const input = useRef<HTMLInputElement>(null); const input = useRef<HTMLInputElement>(null);
const searchableTanks = useMemo( const searchableTanks = useMemo(
() => () =>
@@ -102,7 +102,7 @@ export default function Page() {
'name', 'name',
'name_short', 'name_short',
'id', 'id',
] satisfies (keyof BlitzkriegTankopediaEntry)[], ] satisfies (keyof TankDefinition)[],
}).map(({ obj }) => obj), }).map(({ obj }) => obj),
); );
} }
+5 -5
View File
@@ -9,8 +9,8 @@ import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import getTankStats from '../core/blitz/getTankStats'; import getTankStats from '../core/blitz/getTankStats';
import getTreeType from '../core/blitz/getTreeType'; import getTreeType from '../core/blitz/getTreeType';
import resolveTankName from '../core/blitz/resolveTankName'; import resolveTankName from '../core/blitz/resolveTankName';
import { tankDefinitions } from '../core/blitzkrieg/definitions/tanks';
import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription'; import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription';
import { tankopedia } from '../core/blitzkrieg/tankopedia';
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton'; import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod'; import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
import { tankAverages } from '../core/blitzstars/tankAverages'; import { tankAverages } from '../core/blitzstars/tankAverages';
@@ -44,7 +44,7 @@ async function render(
{ start, end, name }: ResolvedPeriod, { start, end, name }: ResolvedPeriod,
filters: StatFilters, filters: StatFilters,
) { ) {
const awaitedTankopedia = await tankopedia; const awaitedTankDefinitions = await tankDefinitions;
const awaitedTankAverages = await tankAverages; const awaitedTankAverages = await tankAverages;
const statsInPeriod = await getStatsInPeriod(region, id, start, end); const statsInPeriod = await getStatsInPeriod(region, id, start, end);
const { filteredOrder } = await filterStats(statsInPeriod, filters); const { filteredOrder } = await filterStats(statsInPeriod, filters);
@@ -183,7 +183,7 @@ async function render(
await Promise.all( await Promise.all(
filteredOrder.map(async (id, index) => { filteredOrder.map(async (id, index) => {
const tankopediaEntry = awaitedTankopedia[id]; const tankDefinition = awaitedTankDefinitions[id];
const current = orderedCurrentStats[index]; const current = orderedCurrentStats[index];
const career = orderedCareerStats[index]; const career = orderedCareerStats[index];
const currentWN8 = orderedCurrentWN8[index]; const currentWN8 = orderedCurrentWN8[index];
@@ -193,8 +193,8 @@ async function render(
<Breakdown.Row <Breakdown.Row
key={id} key={id}
type="tank" type="tank"
tankType={tankopediaEntry?.type} tankType={tankDefinition?.type}
treeType={tankopediaEntry ? await getTreeType(id) : undefined} treeType={tankDefinition ? await getTreeType(id) : undefined}
title={await resolveTankName(id)} title={await resolveTankName(id)}
stats={[ stats={[
{ {
+8 -8
View File
@@ -1,8 +1,8 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { tankDefinitions } from '../core/blitzkrieg/definitions/tanks';
import getPeriodNow from '../core/blitzkrieg/getPeriodNow'; import getPeriodNow from '../core/blitzkrieg/getPeriodNow';
import getTimeDaysAgo from '../core/blitzkrieg/getTimeDaysAgo'; import getTimeDaysAgo from '../core/blitzkrieg/getTimeDaysAgo';
import { tankopedia } from '../core/blitzkrieg/tankopedia';
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod'; import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
import { tankAverages } from '../core/blitzstars/tankAverages'; import { tankAverages } from '../core/blitzstars/tankAverages';
import addUsernameChoices from '../core/discord/addUsernameChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices';
@@ -66,11 +66,11 @@ export const eligibleCommand: CommandRegistry = {
(await entries.reduce<PossiblyPromise<number>>( (await entries.reduce<PossiblyPromise<number>>(
async (accumulator, [tankIdString, stats]) => { async (accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString); const tankId = parseInt(tankIdString);
const tankopediaEntry = (await tankopedia)[tankId]; const tankDefinition = (await tankDefinitions)[tankId];
if (!tankopediaEntry) return accumulator; if (!tankDefinition) return accumulator;
const tankTier = tankopediaEntry.tier; const tankTier = tankDefinition.tier;
return (await accumulator) + tankTier * stats.battles; return (await accumulator) + tankTier * stats.battles;
}, },
@@ -79,9 +79,9 @@ export const eligibleCommand: CommandRegistry = {
(await entries.reduce<PossiblyPromise<number>>( (await entries.reduce<PossiblyPromise<number>>(
async (accumulator, [tankIdString, stats]) => { async (accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString); const tankId = parseInt(tankIdString);
const tankopediaEntry = (await tankopedia)[tankId]; const tankDefinition = (await tankDefinitions)[tankId];
if (!tankopediaEntry) return accumulator; if (!tankDefinition) return accumulator;
return stats.battles; return stats.battles;
}, },
@@ -110,7 +110,7 @@ export const eligibleCommand: CommandRegistry = {
async (accumulator, [tankIdString, stats]) => { async (accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString); const tankId = parseInt(tankIdString);
if ((await tankopedia)[tankId]?.tier === 10) { if ((await tankDefinitions)[tankId]?.tier === 10) {
const tankStats = diffed[tankId]; const tankStats = diffed[tankId];
return (await accumulator) + tankStats.damage_dealt; return (await accumulator) + tankStats.damage_dealt;
} else return accumulator; } else return accumulator;
@@ -121,7 +121,7 @@ export const eligibleCommand: CommandRegistry = {
async (accumulator, [tankIdString, stats]) => { async (accumulator, [tankIdString, stats]) => {
const tankId = parseInt(tankIdString); const tankId = parseInt(tankIdString);
if ((await tankopedia)[tankId]?.tier === 10) { if ((await tankDefinitions)[tankId]?.tier === 10) {
const tankStats = diffed[tankId]; const tankStats = diffed[tankId];
return (await accumulator) + tankStats.battles; return (await accumulator) + tankStats.battles;
} else return accumulator; } else return accumulator;
+15 -15
View File
@@ -3,20 +3,20 @@ import NoData, { NoDataType } from '../components/NoData';
import * as Tanks from '../components/Tanks'; import * as Tanks from '../components/Tanks';
import TitleBar from '../components/TitleBar'; import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper'; import Wrapper from '../components/Wrapper';
import { encyclopediaInfo } from '../core/blitz/encyclopediaInfo';
import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import getTankStats from '../core/blitz/getTankStats'; import getTankStats from '../core/blitz/getTankStats';
import getTreeType from '../core/blitz/getTreeType'; import getTreeType from '../core/blitz/getTreeType';
import resolveTankName from '../core/blitz/resolveTankName'; import resolveTankName from '../core/blitz/resolveTankName';
import { tankopediaInfo } from '../core/blitz/tankopediaInfo'; import {
TIER_ROMAN_NUMERALS,
TankDefinition,
Tier,
tankDefinitions,
} from '../core/blitzkrieg/definitions/tanks';
import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL'; import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL';
import { tankIcon } from '../core/blitzkrieg/tankIcon'; import { tankIcon } from '../core/blitzkrieg/tankIcon';
import {
BlitzkriegTankopediaEntry,
TIER_ROMAN_NUMERALS,
Tier,
tankopedia,
} from '../core/blitzkrieg/tankopedia';
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';
@@ -42,21 +42,21 @@ export const ownedTanksCommand: CommandRegistry = {
const filteredTanks = ( const filteredTanks = (
await Promise.all( await Promise.all(
tankStats.map(async (tankData) => ({ tankStats.map(async (tankData) => ({
tankopedia: (await tankopedia)[tankData.tank_id]!, tankDefinitions: (await tankDefinitions)[tankData.tank_id]!,
id: tankData.tank_id, id: tankData.tank_id,
})), })),
) )
).filter((tank) => tank.tankopedia?.tier === tier); ).filter((tank) => tank.tankDefinitions?.tier === tier);
const clanAccountInfo = await getClanAccountInfo(server, id, ['clan']); const clanAccountInfo = await getClanAccountInfo(server, id, ['clan']);
const groupedTanks: Record<string, BlitzkriegTankopediaEntry[]> = {}; const groupedTanks: Record<string, TankDefinition[]> = {};
const nations: string[] = []; const nations: string[] = [];
filteredTanks.forEach((tank) => { filteredTanks.forEach((tank) => {
if (groupedTanks[tank.tankopedia.nation] === undefined) { if (groupedTanks[tank.tankDefinitions.nation] === undefined) {
groupedTanks[tank.tankopedia.nation] = [tank.tankopedia]; groupedTanks[tank.tankDefinitions.nation] = [tank.tankDefinitions];
nations.push(tank.tankopedia.nation); nations.push(tank.tankDefinitions.nation);
} else { } else {
groupedTanks[tank.tankopedia.nation].push(tank.tankopedia); groupedTanks[tank.tankDefinitions.nation].push(tank.tankDefinitions);
} }
}); });
@@ -87,7 +87,7 @@ export const ownedTanksCommand: CommandRegistry = {
return ( return (
<Tanks.Root> <Tanks.Root>
<Tanks.Title> <Tanks.Title>
{(await tankopediaInfo).vehicle_nations[nation]} {(await encyclopediaInfo).vehicle_nations[nation]}
</Tanks.Title> </Tanks.Title>
<Tanks.Row> <Tanks.Row>
+1 -1
View File
@@ -1,7 +1,7 @@
import { SlashCommandBuilder } from 'discord.js'; import { SlashCommandBuilder } from 'discord.js';
import { go } from 'fuzzysort'; import { go } from 'fuzzysort';
import markdownEscape from 'markdown-escape'; import markdownEscape from 'markdown-escape';
import { tankNamesDiacritics } from '../core/blitzkrieg/tankopedia'; import { tankNamesDiacritics } from '../core/blitzkrieg/definitions/tanks';
import addTankChoices from '../core/discord/addTankChoices'; import addTankChoices from '../core/discord/addTankChoices';
import embedInfo from '../core/discord/embedInfo'; import embedInfo from '../core/discord/embedInfo';
import { CommandRegistry } from '../events/interactionCreate'; import { CommandRegistry } from '../events/interactionCreate';
+1 -1
View File
@@ -2,7 +2,7 @@ import {
TANK_ICONS, TANK_ICONS,
TANK_ICONS_COLLECTOR, TANK_ICONS_COLLECTOR,
TANK_ICONS_PREMIUM, TANK_ICONS_PREMIUM,
} from '../../../core/blitzkrieg/tankopedia'; } from '../../../core/blitzkrieg/definitions/tanks';
import { theme } from '../../../stitches.config'; import { theme } from '../../../stitches.config';
export enum TreeTypeEnum { export enum TreeTypeEnum {
+1 -1
View File
@@ -1,4 +1,4 @@
import { Tier } from '../core/blitzkrieg/tankopedia'; import { Tier } from '../core/blitzkrieg/definitions/tanks';
import { theme } from '../stitches.config'; import { theme } from '../stitches.config';
export type TierWeightsRecord = Partial<Record<Tier, number>>; export type TierWeightsRecord = Partial<Record<Tier, number>>;
@@ -1,7 +1,7 @@
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID'; import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import fetchBlitz from './fetchBlitz'; import fetchBlitz from './fetchBlitz';
export interface TankopediaInfo { export interface EncyclopediaInfo {
achievement_sections: Record<string, { name: string; order: number }>; achievement_sections: Record<string, { name: string; order: number }>;
tanks_updated_at: number; tanks_updated_at: number;
languages: Record<string, string>; languages: Record<string, string>;
@@ -10,6 +10,6 @@ export interface TankopediaInfo {
game_version: string; game_version: string;
} }
export const tankopediaInfo = fetchBlitz<TankopediaInfo>( export const encyclopediaInfo = fetchBlitz<EncyclopediaInfo>(
`https://api.wotblitz.com/wotb/encyclopedia/info/?application_id=${WARGAMING_APPLICATION_ID}`, `https://api.wotblitz.com/wotb/encyclopedia/info/?application_id=${WARGAMING_APPLICATION_ID}`,
); );
+2 -2
View File
@@ -1,8 +1,8 @@
import { TreeTypeEnum } from '../../components/Tanks'; import { TreeTypeEnum } from '../../components/Tanks';
import { tankopedia } from '../blitzkrieg/tankopedia'; import { tankDefinitions } from '../blitzkrieg/definitions/tanks';
export default async function getTreeType(id: number) { export default async function getTreeType(id: number) {
const entry = (await tankopedia)[id]; const entry = (await tankDefinitions)[id];
if (entry.tree_type === 'collector') return TreeTypeEnum.Collector; if (entry.tree_type === 'collector') return TreeTypeEnum.Collector;
if (entry.tree_type === 'premium') return TreeTypeEnum.Premium; if (entry.tree_type === 'premium') return TreeTypeEnum.Premium;
+6 -3
View File
@@ -1,12 +1,15 @@
import { go } from 'fuzzysort'; import { go } from 'fuzzysort';
import { tankopedia, tanks } from '../blitzkrieg/tankopedia'; import {
tankDefinitions,
tanksDefinitionsArray,
} from '../blitzkrieg/definitions/tanks';
import { UserError } from '../blitzkrieg/userError'; import { UserError } from '../blitzkrieg/userError';
export default async function resolveTankId(tank: string | number) { export default async function resolveTankId(tank: string | number) {
const number = typeof tank === 'string' ? parseInt(tank) : tank; const number = typeof tank === 'string' ? parseInt(tank) : tank;
if (Number.isNaN(number)) { if (Number.isNaN(number)) {
const searchResult = go(`${tank}`, await tanks, { const searchResult = go(`${tank}`, await tanksDefinitionsArray, {
keys: ['name'], keys: ['name'],
limit: 1, limit: 1,
}); });
@@ -19,7 +22,7 @@ export default async function resolveTankId(tank: string | number) {
return searchResult[0].obj.id; return searchResult[0].obj.id;
} }
} else { } else {
if ((await tankopedia)[number]) { if ((await tankDefinitions)[number]) {
return number; return number;
} else { } else {
throw new UserError('Tank not found', { throw new UserError('Tank not found', {
+3 -3
View File
@@ -1,8 +1,8 @@
import { tankopedia } from '../blitzkrieg/tankopedia'; import { tankDefinitions } from '../blitzkrieg/definitions/tanks';
export default async function resolveTankName(tankId: number) { export default async function resolveTankName(tankId: number) {
const awaitedTankopedia = await tankopedia; const awaitedTankDefinitions = await tankDefinitions;
const entry = awaitedTankopedia[tankId]; const entry = awaitedTankDefinitions[tankId];
return entry?.name_short ?? entry?.name ?? `Unknown Tank ${tankId}`; return entry?.name_short ?? entry?.name ?? `Unknown Tank ${tankId}`;
} }
@@ -1,24 +1,8 @@
import { deburr } from 'lodash'; import { deburr } from 'lodash';
import { TankType, TreeTypeString } from '../../components/Tanks'; import { TankType, TreeTypeString } from '../../../components/Tanks';
import { asset } from './asset'; import { asset } from '../asset';
export interface TankopediaEntry { export interface TankDefinition {
name: string;
nation: string;
is_premium: boolean;
is_collectible: boolean;
tier: Tier;
cost: { price_credit: number; price_gold: number };
images: { preview: string; normal: string };
tank_id: number;
type: string;
description: string;
}
export interface Tankopedia {
[id: number]: TankopediaEntry | undefined;
}
export interface BlitzkriegTankopediaEntry {
id: number; id: number;
nation: string; nation: string;
name: string; name: string;
@@ -28,15 +12,16 @@ export interface BlitzkriegTankopediaEntry {
type: TankType; type: TankType;
} }
export type BlitzkriegTankopedia = Record<number, BlitzkriegTankopediaEntry>; export type TankDefinitions = Record<number, TankDefinition>;
export const tankopedia = fetch(asset('tankopedia.json'), { export const tankDefinitions = fetch(asset('definitions/tanks.json'), {
cache: 'no-store', cache: 'no-store',
}).then(async (response) => response.json() as Promise<BlitzkriegTankopedia>); }).then(async (response) => response.json() as Promise<TankDefinitions>);
const entries = new Promise<BlitzkriegTankopediaEntry[]>(async (resolve) => {
resolve(Object.entries(await tankopedia).map(([, entry]) => entry)); const entries = new Promise<TankDefinition[]>(async (resolve) => {
resolve(Object.entries(await tankDefinitions).map(([, entry]) => entry));
}); });
export const tanks = new Promise<BlitzkriegTankopediaEntry[]>( export const tanksDefinitionsArray = new Promise<TankDefinition[]>(
async (resolve) => { async (resolve) => {
resolve((await entries).map((entry) => entry)); resolve((await entries).map((entry) => entry));
}, },
@@ -47,7 +32,7 @@ export const tankNames = new Promise<string[]>(async (resolve) => {
export const tankNamesDiacritics = tankNames.then((tankNames) => export const tankNamesDiacritics = tankNames.then((tankNames) =>
Promise.all( Promise.all(
tankNames.map(async (tankName, index) => { tankNames.map(async (tankName, index) => {
const { id } = (await tanks)[index]; const { id } = (await tanksDefinitionsArray)[index];
const name = tankName ?? `Unknown Tank ${id}`; const name = tankName ?? `Unknown Tank ${id}`;
const diacriticless = deburr(name); const diacriticless = deburr(name);
@@ -99,7 +84,7 @@ export const TIER_ROMAN_NUMERALS: Record<Tier, string> = {
10: 'X', 10: 'X',
}; };
export const NATIONS = tanks.then((tanks) => { export const NATIONS = tanksDefinitionsArray.then((tanks) => {
const nationsObject: Record<string, true> = {}; const nationsObject: Record<string, true> = {};
tanks.forEach(({ nation }) => { tanks.forEach(({ nation }) => {
+5 -5
View File
@@ -1,8 +1,8 @@
import { TREE_TYPE_NAMES } from '../../components/Tanks'; import { TREE_TYPE_NAMES } from '../../components/Tanks';
import { encyclopediaInfo } from '../blitz/encyclopediaInfo';
import resolveTankName from '../blitz/resolveTankName'; import resolveTankName from '../blitz/resolveTankName';
import { tankopediaInfo } from '../blitz/tankopediaInfo';
import { StatFilters } from '../statistics/filterStats'; import { StatFilters } from '../statistics/filterStats';
import { TIER_ROMAN_NUMERALS, Tier } from './tankopedia'; import { TIER_ROMAN_NUMERALS, Tier } from './definitions/tanks';
export async function filtersToDescription({ export async function filtersToDescription({
nation, nation,
@@ -11,13 +11,13 @@ export async function filtersToDescription({
treeType, treeType,
tank, tank,
}: StatFilters) { }: StatFilters) {
const awaitedTankopediaInfo = await tankopediaInfo; const awaitedEncyclopediaInfo = await encyclopediaInfo;
const info: string[] = []; const info: string[] = [];
if (tank) return await resolveTankName(tank); if (tank) return await resolveTankName(tank);
if (nation) info.push(awaitedTankopediaInfo.vehicle_nations[nation]); if (nation) info.push(awaitedEncyclopediaInfo.vehicle_nations[nation]);
if (tier) info.push(`Tier ${TIER_ROMAN_NUMERALS[tier as Tier]}`); if (tier) info.push(`Tier ${TIER_ROMAN_NUMERALS[tier as Tier]}`);
if (tankType) info.push(awaitedTankopediaInfo.vehicle_types[tankType]); if (tankType) info.push(awaitedEncyclopediaInfo.vehicle_types[tankType]);
if (treeType) info.push(TREE_TYPE_NAMES[treeType]); if (treeType) info.push(TREE_TYPE_NAMES[treeType]);
return info.length === 0 ? 'No filters' : info.join(', '); return info.length === 0 ? 'No filters' : info.join(', ');
+2 -2
View File
@@ -1,6 +1,6 @@
import { AllStats } from '../blitz/getAccountInfo'; import { AllStats } from '../blitz/getAccountInfo';
import { context } from '../blitzkrieg/context'; import { context } from '../blitzkrieg/context';
import { tankopedia } from '../blitzkrieg/tankopedia'; import { tankDefinitions } from '../blitzkrieg/definitions/tanks';
export interface SpecialStats { export interface SpecialStats {
winrate: number; winrate: number;
@@ -80,7 +80,7 @@ export interface IndividualTankAverage {
last_update: number; last_update: number;
} }
tankopedia; tankDefinitions;
export type TankAverages = Record<number, IndividualTankAverage>; export type TankAverages = Record<number, IndividualTankAverage>;
+4 -4
View File
@@ -4,7 +4,7 @@ import {
SlashCommandSubcommandGroupBuilder, SlashCommandSubcommandGroupBuilder,
} from 'discord.js'; } from 'discord.js';
import { TREE_TYPE_NAMES, TreeTypeString } from '../../components/Tanks'; import { TREE_TYPE_NAMES, TreeTypeString } from '../../components/Tanks';
import { tankopediaInfo } from '../blitz/tankopediaInfo'; import { encyclopediaInfo } from '../blitz/encyclopediaInfo';
import addPeriodSubCommands from './addPeriodSubCommands'; import addPeriodSubCommands from './addPeriodSubCommands';
import addTankChoices from './addTankChoices'; import addTankChoices from './addTankChoices';
import addTierChoices from './addTierChoices'; import addTierChoices from './addTierChoices';
@@ -17,7 +17,7 @@ export default async function addFilterOptions<
option: SlashCommandSubcommandBuilder, option: SlashCommandSubcommandBuilder,
) => SlashCommandSubcommandBuilder = (option) => option, ) => SlashCommandSubcommandBuilder = (option) => option,
): Promise<OptionType> { ): Promise<OptionType> {
const awaitedTankopediaInfo = await tankopediaInfo; const awaitedEncyclopediaInfo = await encyclopediaInfo;
addPeriodSubCommands(option, (option) => { addPeriodSubCommands(option, (option) => {
extra(option); extra(option);
@@ -28,7 +28,7 @@ export default async function addFilterOptions<
.setName('nation') .setName('nation')
.setDescription('Nation') .setDescription('Nation')
.addChoices( .addChoices(
...Object.entries(awaitedTankopediaInfo.vehicle_nations).map( ...Object.entries(awaitedEncyclopediaInfo.vehicle_nations).map(
([type, name]) => ({ name, value: type }), ([type, name]) => ({ name, value: type }),
), ),
) )
@@ -40,7 +40,7 @@ export default async function addFilterOptions<
.setName('tank-type') .setName('tank-type')
.setDescription('Tank type') .setDescription('Tank type')
.addChoices( .addChoices(
...Object.entries(awaitedTankopediaInfo.vehicle_types).map( ...Object.entries(awaitedEncyclopediaInfo.vehicle_types).map(
([type, name]) => ({ ([type, name]) => ({
name: name.replace(' Tank', ''), name: name.replace(' Tank', ''),
value: type, value: type,
+1 -1
View File
@@ -4,7 +4,7 @@ import {
CacheType, CacheType,
} from 'discord.js'; } from 'discord.js';
import { go } from 'fuzzysort'; import { go } from 'fuzzysort';
import { tankNamesDiacritics } from '../blitzkrieg/tankopedia'; import { tankNamesDiacritics } from '../blitzkrieg/definitions/tanks';
import { import {
DISCORD_CHOICES_MAX_NAME_SIZE, DISCORD_CHOICES_MAX_NAME_SIZE,
OVERFLOW_SUFFIX, OVERFLOW_SUFFIX,
+8 -8
View File
@@ -1,5 +1,5 @@
import { TreeTypeString } from '../../components/Tanks'; import { TreeTypeString } from '../../components/Tanks';
import { tankopedia } from '../blitzkrieg/tankopedia'; import { tankDefinitions } from '../blitzkrieg/definitions/tanks';
import { DiffedTankStats } from '../blitzstars/getStatsInPeriod'; import { DiffedTankStats } from '../blitzstars/getStatsInPeriod';
import { tankAverages } from '../blitzstars/tankAverages'; import { tankAverages } from '../blitzstars/tankAverages';
import calculateWN8 from './calculateWN8'; import calculateWN8 from './calculateWN8';
@@ -20,10 +20,10 @@ export async function filterStats(
// tank, if provided, takes priority over all other filters // tank, if provided, takes priority over all other filters
if (filters.tank) filters = { tank: filters.tank }; if (filters.tank) filters = { tank: filters.tank };
const awaitedTankopedia = await tankopedia; const awaitedTankDefinitions = await tankDefinitions;
const awaitedTankAverages = await tankAverages; const awaitedTankAverages = await tankAverages;
const filteredOrder = order.filter((id) => { const filteredOrder = order.filter((id) => {
const entry = awaitedTankopedia[id]; const entry = awaitedTankDefinitions[id];
return ( return (
entry && entry &&
@@ -44,9 +44,9 @@ export async function filterStats(
awaitedTankAverages[id] ? accumulator + diff[id].battles : accumulator, awaitedTankAverages[id] ? accumulator + diff[id].battles : accumulator,
0, 0,
); );
const battlesOfTanksWithTankopediaEntry = filteredOrder.reduce<number>( const battlesOfTanksWithTankDefinition = filteredOrder.reduce<number>(
(accumulator, id) => (accumulator, id) =>
awaitedTankopedia[id] ? accumulator + diff[id].battles : accumulator, awaitedTankDefinitions[id] ? accumulator + diff[id].battles : accumulator,
0, 0,
); );
const supplementary = { const supplementary = {
@@ -63,11 +63,11 @@ export async function filterStats(
tier: tier:
filteredOrder.reduce<number>( filteredOrder.reduce<number>(
(accumulator, id) => (accumulator, id) =>
awaitedTankopedia[id] awaitedTankDefinitions[id]
? accumulator + awaitedTankopedia[id]!.tier * diff[id].battles ? accumulator + awaitedTankDefinitions[id]!.tier * diff[id].battles
: accumulator, : accumulator,
0, 0,
) / battlesOfTanksWithTankopediaEntry, ) / battlesOfTanksWithTankDefinition,
}; };
return { stats, supplementary, filteredOrder }; return { stats, supplementary, filteredOrder };
+3 -3
View File
@@ -1,16 +1,16 @@
import { TierWeightsRecord } from '../../components/TierWeights'; import { TierWeightsRecord } from '../../components/TierWeights';
import { AllStats } from '../blitz/getAccountInfo'; import { AllStats } from '../blitz/getAccountInfo';
import { tankopedia } from '../blitzkrieg/tankopedia'; import { tankDefinitions } from '../blitzkrieg/definitions/tanks';
export async function getTierWeights( export async function getTierWeights(
stats: Record<number, AllStats>, stats: Record<number, AllStats>,
order: number[] = Object.keys(stats).map(parseInt), order: number[] = Object.keys(stats).map(parseInt),
) { ) {
const awaitedTankopedia = await tankopedia; const awaitedTankDefinitions = await tankDefinitions;
const tierWeights: TierWeightsRecord = {}; const tierWeights: TierWeightsRecord = {};
order.forEach((id) => { order.forEach((id) => {
const entry = awaitedTankopedia[id]; const entry = awaitedTankDefinitions[id];
if (!entry) return; if (!entry) return;
if (!tierWeights[entry.tier]) tierWeights[entry.tier] = 0; if (!tierWeights[entry.tier]) tierWeights[entry.tier] = 0;
+1 -1
View File
@@ -2,7 +2,7 @@ import { produce } from 'immer';
import { create } from 'zustand'; import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware'; import { devtools, persist } from 'zustand/middleware';
import { TankType, TreeTypeString } from '../components/Tanks'; import { TankType, TreeTypeString } from '../components/Tanks';
import { Tier } from '../core/blitzkrieg/tankopedia'; import { Tier } from '../core/blitzkrieg/definitions/tanks';
export type TankopediaSortBy = 'tier' | 'name'; export type TankopediaSortBy = 'tier' | 'name';
export type TankopediaSortDirection = 'ascending' | 'descending'; export type TankopediaSortDirection = 'ascending' | 'descending';