mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
Dev (#106)
This commit is contained in:
1 parent
9d715c23e5
commit
93bf4a4742
29 files changed
+523
-134
No files matched your search
Vendored
+4
@@ -18,6 +18,8 @@
|
|||||||
"BINORMAL",
|
"BINORMAL",
|
||||||
"Binormals",
|
"Binormals",
|
||||||
"BKRL",
|
"BKRL",
|
||||||
|
"BKRTS",
|
||||||
|
"BKRTSMANIFEST",
|
||||||
"bkwd",
|
"bkwd",
|
||||||
"blitzstars",
|
"blitzstars",
|
||||||
"Camo",
|
"Camo",
|
||||||
@@ -108,6 +110,8 @@
|
|||||||
"researchable",
|
"researchable",
|
||||||
"Resvg",
|
"Resvg",
|
||||||
"roboto",
|
"roboto",
|
||||||
|
"rtsc",
|
||||||
|
"RTSM",
|
||||||
"SCPG",
|
"SCPG",
|
||||||
"scroller",
|
"scroller",
|
||||||
"searchclans",
|
"searchclans",
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Blitzkrieg Regular Tank Stats
|
||||||
|
|
||||||
|
A straightforward way of storing the entire World of Tanks Blitz tank statistics for players to disk.
|
||||||
|
|
||||||
|
## Manifest `manifest.rtsm`
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
primary RegularTankStatisticsManifest {
|
||||||
|
utf8"RTSM" magic;
|
||||||
|
Header header;
|
||||||
|
primary Body body;
|
||||||
|
}
|
||||||
|
|
||||||
|
Header {
|
||||||
|
uint16 version; // always 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Body {
|
||||||
|
uint32 time;
|
||||||
|
uint8 base;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Chunk `*.rtsc`
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
primary RegularTankStatisticsChunk {
|
||||||
|
utf8"RTSC" magic;
|
||||||
|
Header header;
|
||||||
|
primary Body body<header.playerCount>;
|
||||||
|
}
|
||||||
|
|
||||||
|
Header {
|
||||||
|
uint16 version; // always 1
|
||||||
|
uint32 playerCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
Body<uint32 playerCount> {
|
||||||
|
Player[playerCount] players;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player {
|
||||||
|
uint32 id;
|
||||||
|
uint16 tankCount;
|
||||||
|
Tank[tankCount] tanks;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tank {
|
||||||
|
uint32 id;
|
||||||
|
uint32 battleLifeTime;
|
||||||
|
uint32 lastBattleTime;
|
||||||
|
uint8 markOfMastery;
|
||||||
|
uint32 battles;
|
||||||
|
uint32 capturePoints;
|
||||||
|
uint32 damageDealt;
|
||||||
|
uint32 damageReceived;
|
||||||
|
uint32 droppedCapturePoints;
|
||||||
|
uint32 frags;
|
||||||
|
uint32 frags8p;
|
||||||
|
uint32 hits;
|
||||||
|
uint32 losses;
|
||||||
|
uint8 maxFrags;
|
||||||
|
uint16 maxXp;
|
||||||
|
uint32 shots;
|
||||||
|
uint32 spotted;
|
||||||
|
uint32 survivedBattles;
|
||||||
|
uint32 winAndSurvived;
|
||||||
|
uint32 wins;
|
||||||
|
uint32 xp;
|
||||||
|
}
|
||||||
|
```
|
||||||
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
"build:docs": "vitepress build docs",
|
"build:docs": "vitepress build docs",
|
||||||
"build:ratingleaderboard": "vite-node --script scripts/buildRatingLeaderboard",
|
"build:ratingleaderboard": "vite-node --script scripts/buildRatingLeaderboard",
|
||||||
"build:assets": "vite-node --max_old_space_size=16384 --script scripts/buildAssets/index",
|
"build:assets": "vite-node --max_old_space_size=16384 --script scripts/buildAssets/index",
|
||||||
"build:test": "vite-node --script scripts/buildTest",
|
"test": "vite-node --script scripts/test",
|
||||||
"dev:bot": "vite-node --script scripts/build && yarn start:bot",
|
"dev:bot": "vite-node --script scripts/build && yarn start:bot",
|
||||||
"dev:docs": "vitepress dev docs",
|
"dev:docs": "vitepress dev docs",
|
||||||
"dev:website": "next dev",
|
"dev:website": "next dev",
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { chunk, times } from 'lodash';
|
||||||
|
import { compress } from 'lz4js';
|
||||||
|
import { argv } from 'process';
|
||||||
|
import { Region, REGIONS } from '../src/constants/regions';
|
||||||
|
import getTankStats from '../src/core/blitz/getTankStats';
|
||||||
|
import { idToRegion } from '../src/core/blitz/idToRegion';
|
||||||
|
import { commitAssets } from '../src/core/blitzkrieg/commitAssets';
|
||||||
|
import { FileChange } from '../src/core/blitzkrieg/commitMultipleFiles';
|
||||||
|
import {
|
||||||
|
RtscWritePlayer,
|
||||||
|
RtscWriteStream,
|
||||||
|
RtsmWriteStream,
|
||||||
|
} from '../src/core/streams/rts';
|
||||||
|
import usersRaw from '../test.users.json';
|
||||||
|
|
||||||
|
const PLAYERS_PER_CHUNK = 256;
|
||||||
|
const CHUNKS_PER_BATCH = 128;
|
||||||
|
|
||||||
|
const production = argv.includes('--production');
|
||||||
|
const region = argv
|
||||||
|
.find((argument) => argument.startsWith('--region'))
|
||||||
|
?.split('=')[1] as Region | undefined;
|
||||||
|
|
||||||
|
if (!region || !REGIONS.includes(region)) {
|
||||||
|
throw new Error('No valid region specified');
|
||||||
|
}
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
const todayPath = `${today.getUTCFullYear()}/${today.getUTCMonth() + 1}/${today.getUTCDate()}`;
|
||||||
|
const users = usersRaw.filter(({ blitz }) => idToRegion(blitz) === region);
|
||||||
|
const base = Math.ceil(users.length / PLAYERS_PER_CHUNK);
|
||||||
|
const chunks = times(base, (offset) => ({
|
||||||
|
offset,
|
||||||
|
users: users.filter((user) => user.blitz % base === offset),
|
||||||
|
}));
|
||||||
|
const batches = chunk(chunks, CHUNKS_PER_BATCH);
|
||||||
|
const manifestWriteStream = new RtsmWriteStream();
|
||||||
|
|
||||||
|
console.log(`Found ${users.length} players in ${region}`);
|
||||||
|
|
||||||
|
await commitAssets(
|
||||||
|
`regular tank stats manifests ${todayPath}`,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
encoding: 'base64',
|
||||||
|
path: `regions/${region}/regular/tanks/${todayPath}/manifest.rtsm`,
|
||||||
|
content: Buffer.from(
|
||||||
|
compress(manifestWriteStream.rtsm(base).uint8Array),
|
||||||
|
).toString('base64'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
production,
|
||||||
|
);
|
||||||
|
|
||||||
|
let batchesDone = 0;
|
||||||
|
for (const batch of batches) {
|
||||||
|
console.log(`Batch ${++batchesDone} of ${batches.length}`);
|
||||||
|
|
||||||
|
const changes: FileChange[] = [];
|
||||||
|
|
||||||
|
let chunksDone = 0;
|
||||||
|
for (const chunk of batch) {
|
||||||
|
console.log(`Chunk ${++chunksDone} of ${chunks.length}`);
|
||||||
|
|
||||||
|
let playersDone = 0;
|
||||||
|
const players = await Promise.all(
|
||||||
|
chunk.users.map(async (user) => {
|
||||||
|
const tanks = await getTankStats(region, user.blitz);
|
||||||
|
|
||||||
|
console.log(`Player ${++playersDone} of ${chunk.users.length}`);
|
||||||
|
|
||||||
|
if (tanks === null) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: user.blitz,
|
||||||
|
tanks,
|
||||||
|
} satisfies RtscWritePlayer;
|
||||||
|
}),
|
||||||
|
).then((players) => players.filter((player) => player !== null));
|
||||||
|
const writeStream = new RtscWriteStream();
|
||||||
|
|
||||||
|
writeStream.rtsc(players);
|
||||||
|
changes.push({
|
||||||
|
encoding: 'base64',
|
||||||
|
content: Buffer.from(compress(writeStream.uint8Array)).toString('base64'),
|
||||||
|
path: `regions/${region}/regular/tanks/${todayPath}/${chunk.offset}.rtsc.lz4`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not await; commit in background
|
||||||
|
commitAssets(
|
||||||
|
`regular tank stats ${todayPath} batch ${batches.indexOf(batch) + 1}`,
|
||||||
|
changes,
|
||||||
|
production,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
import { ContextMenu } from '@radix-ui/themes';
|
import { ContextMenu } from '@radix-ui/themes';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import * as Breakdown from '../../../components/Breakdown';
|
import * as Breakdown from '../../../components/Breakdown';
|
||||||
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
|
|
||||||
import fetchBlitz from '../../../core/blitz/fetchBlitz';
|
import fetchBlitz from '../../../core/blitz/fetchBlitz';
|
||||||
import { resetSession } from '../../../core/blitzkrieg/resetSession';
|
import { resetSession } from '../../../core/blitzkrieg/resetSession';
|
||||||
import {
|
import {
|
||||||
@@ -88,10 +87,10 @@ export default function SessionPage({ naked = true }: SessionPageProps) {
|
|||||||
async function recalculateDiff() {
|
async function recalculateDiff() {
|
||||||
if (session.isTracking) {
|
if (session.isTracking) {
|
||||||
const { id, region } = session;
|
const { id, region } = session;
|
||||||
const careerRaw = await fetchBlitz<TanksStats>(
|
const careerRaw = await fetchBlitz<TanksStats>(region, 'tanks/stats', {
|
||||||
`https://api.wotblitz.${region}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
account_id: id,
|
||||||
);
|
});
|
||||||
const career = careerRaw[id].reduce<
|
const career = careerRaw[id]!.reduce<
|
||||||
Record<number, IndividualTankStats>
|
Record<number, IndividualTankStats>
|
||||||
>(
|
>(
|
||||||
(accumulator, curr) => ({
|
(accumulator, curr) => ({
|
||||||
@@ -103,7 +102,7 @@ export default function SessionPage({ naked = true }: SessionPageProps) {
|
|||||||
const awaitedTankDefinitions = await tankDefinitions;
|
const awaitedTankDefinitions = await tankDefinitions;
|
||||||
const awaitedTankAverages = await tankAverages;
|
const awaitedTankAverages = await tankAverages;
|
||||||
const careerWN8 =
|
const careerWN8 =
|
||||||
careerRaw[id].reduce(
|
careerRaw[id]!.reduce(
|
||||||
(accumulator, { tank_id, all }) =>
|
(accumulator, { tank_id, all }) =>
|
||||||
accumulator +
|
accumulator +
|
||||||
(awaitedTankAverages[tank_id] && all.battles > 0
|
(awaitedTankAverages[tank_id] && all.battles > 0
|
||||||
@@ -112,7 +111,7 @@ export default function SessionPage({ naked = true }: SessionPageProps) {
|
|||||||
: 0),
|
: 0),
|
||||||
0,
|
0,
|
||||||
) /
|
) /
|
||||||
careerRaw[id].reduce(
|
careerRaw[id]!.reduce(
|
||||||
(accumulator, { tank_id, all }) =>
|
(accumulator, { tank_id, all }) =>
|
||||||
accumulator +
|
accumulator +
|
||||||
(awaitedTankAverages[tank_id] && all.battles > 0
|
(awaitedTankAverages[tank_id] && all.battles > 0
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import {
|
|||||||
Region,
|
Region,
|
||||||
UNLOCALIZED_REGION_NAMES,
|
UNLOCALIZED_REGION_NAMES,
|
||||||
} from '../../../constants/regions';
|
} from '../../../constants/regions';
|
||||||
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
|
|
||||||
import fetchBlitz from '../../../core/blitz/fetchBlitz';
|
import fetchBlitz from '../../../core/blitz/fetchBlitz';
|
||||||
import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo';
|
import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo';
|
||||||
import getRatingInfo from '../../../core/blitz/getRatingInfo';
|
import getRatingInfo from '../../../core/blitz/getRatingInfo';
|
||||||
@@ -818,7 +817,9 @@ export default function Page() {
|
|||||||
} else {
|
} else {
|
||||||
setSearchLoading(true);
|
setSearchLoading(true);
|
||||||
const accountList = await fetchBlitz<AccountList>(
|
const accountList = await fetchBlitz<AccountList>(
|
||||||
`https://api.wotblitz.${region}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}`,
|
region,
|
||||||
|
'account/list',
|
||||||
|
{ search: encodedSearch },
|
||||||
);
|
);
|
||||||
const playerKeys = Object.keys(
|
const playerKeys = Object.keys(
|
||||||
leaderboard[region][season],
|
leaderboard[region][season],
|
||||||
|
|||||||
@@ -177,16 +177,18 @@ export function TankSandbox() {
|
|||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
</Tabs.Root>
|
</Tabs.Root>
|
||||||
|
|
||||||
<Text
|
{wideFormat && (
|
||||||
color="gray"
|
<Text
|
||||||
style={{
|
color="gray"
|
||||||
position: 'absolute',
|
style={{
|
||||||
left: '50%',
|
position: 'absolute',
|
||||||
top: 11,
|
left: '50%',
|
||||||
}}
|
top: 11,
|
||||||
>
|
}}
|
||||||
<BlitzkriegWormWide height={32} />
|
>
|
||||||
</Text>
|
<BlitzkriegWormWide height={32} />
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { translator } from '../../core/localization/translator';
|
|||||||
import calculateWN8 from '../../core/statistics/calculateWN8';
|
import calculateWN8 from '../../core/statistics/calculateWN8';
|
||||||
import { StatFilters, filterStats } from '../../core/statistics/filterStats';
|
import { StatFilters, filterStats } from '../../core/statistics/filterStats';
|
||||||
import getWN8Percentile from '../../core/statistics/getWN8Percentile';
|
import getWN8Percentile from '../../core/statistics/getWN8Percentile';
|
||||||
|
import { UserError } from '../../hooks/userError';
|
||||||
|
|
||||||
const ROWS_PER_PAGE = 8;
|
const ROWS_PER_PAGE = 8;
|
||||||
const MAX_PAGES = 9;
|
const MAX_PAGES = 9;
|
||||||
@@ -37,7 +38,7 @@ export async function renderBreakdown(
|
|||||||
const { filteredOrder } = await filterStats(statsInPeriod, filters);
|
const { filteredOrder } = await filterStats(statsInPeriod, filters);
|
||||||
const accountInfo = await getAccountInfo(region, id);
|
const accountInfo = await getAccountInfo(region, id);
|
||||||
const clanData = await getClanAccountInfo(region, id, ['clan']);
|
const clanData = await getClanAccountInfo(region, id, ['clan']);
|
||||||
const tankStats = await getTankStats(region, id, locale);
|
const tankStats = await getTankStats(region, id);
|
||||||
const filterDescriptions = await filtersToDescription(filters, locale);
|
const filterDescriptions = await filtersToDescription(filters, locale);
|
||||||
const orderedCurrentStats: AllStats[] = [];
|
const orderedCurrentStats: AllStats[] = [];
|
||||||
const orderedCareerStats: AllStats[] = [];
|
const orderedCareerStats: AllStats[] = [];
|
||||||
@@ -45,6 +46,10 @@ export async function renderBreakdown(
|
|||||||
const orderedCareerWN8: (number | undefined)[] = [];
|
const orderedCareerWN8: (number | undefined)[] = [];
|
||||||
const orderedCareerWN8Full: (number | undefined)[] = [];
|
const orderedCareerWN8Full: (number | undefined)[] = [];
|
||||||
|
|
||||||
|
if (tankStats === null) {
|
||||||
|
throw new UserError(t`bot.common.errors.no_tank_stats`);
|
||||||
|
}
|
||||||
|
|
||||||
filteredOrder.forEach((id) => {
|
filteredOrder.forEach((id) => {
|
||||||
const tankAveragesEntry = awaitedTankAverages[id];
|
const tankAveragesEntry = awaitedTankAverages[id];
|
||||||
const current = statsInPeriod.diff[id];
|
const current = statsInPeriod.diff[id];
|
||||||
|
|||||||
@@ -31,11 +31,16 @@ export const ownedTanksCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
.addStringOption(addUsernameChoices),
|
.addStringOption(addUsernameChoices),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const { translate } = translator(interaction.locale);
|
const { t, translate } = translator(interaction.locale);
|
||||||
const tier = Number(interaction.options.getString('tier'));
|
const tier = Number(interaction.options.getString('tier'));
|
||||||
const { id, region } = await resolvePlayerFromCommand(interaction);
|
const { id, region } = await resolvePlayerFromCommand(interaction);
|
||||||
const accountInfo = await getAccountInfo(region, id);
|
const accountInfo = await getAccountInfo(region, id);
|
||||||
const tankStats = await getTankStats(region, id, interaction.locale);
|
const tankStats = await getTankStats(region, id);
|
||||||
|
|
||||||
|
if (tankStats === null) {
|
||||||
|
return t`bot.common.errors.no_tank_stats`;
|
||||||
|
}
|
||||||
|
|
||||||
const filteredTanks = (
|
const filteredTanks = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
tankStats.map(async (tankData) => ({
|
tankStats.map(async (tankData) => ({
|
||||||
|
|||||||
@@ -126,7 +126,11 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tankStats = await getTankStats(region, id, interaction.locale);
|
const tankStats = await getTankStats(region, id);
|
||||||
|
|
||||||
|
if (tankStats === null) {
|
||||||
|
return t`bot.common.errors.no_tank_stats`;
|
||||||
|
}
|
||||||
|
|
||||||
if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) {
|
if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) {
|
||||||
return translate('bot.commands.research.errors.already_researched', [
|
return translate('bot.commands.research.errors.already_researched', [
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Locale } from 'discord.js';
|
import { Locale } from 'discord.js';
|
||||||
import markdownEscape from 'markdown-escape';
|
import markdownEscape from 'markdown-escape';
|
||||||
import { Region } from '../constants/regions';
|
import { Region } from '../constants/regions';
|
||||||
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
|
||||||
import fetchBlitz from '../core/blitz/fetchBlitz';
|
import fetchBlitz from '../core/blitz/fetchBlitz';
|
||||||
import addClanChoices from '../core/discord/addClanChoices';
|
import addClanChoices from '../core/discord/addClanChoices';
|
||||||
import addRegionChoices from '../core/discord/addRegionChoices';
|
import addRegionChoices from '../core/discord/addRegionChoices';
|
||||||
@@ -49,17 +48,18 @@ export const searchClansCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const { translate } = translator(interaction.locale);
|
const { translate } = translator(interaction.locale);
|
||||||
const server = interaction.options.getString('region') as Region;
|
const region = interaction.options.getString('region') as Region;
|
||||||
const clan = interaction.options.getString('clan')!;
|
const clan = interaction.options.getString('clan')!;
|
||||||
const limit = interaction.options.getInteger('limit') ?? DEFAULT_LIMIT;
|
const limit = interaction.options.getInteger('limit') ?? DEFAULT_LIMIT;
|
||||||
const clanList = await fetchBlitz<ClanList>(
|
const clanList = await fetchBlitz<ClanList>(region, 'clans/list', {
|
||||||
`https://api.wotblitz.${server}/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${clan}&limit=${limit}`,
|
search: clan,
|
||||||
);
|
limit,
|
||||||
|
});
|
||||||
|
|
||||||
return embedInfo(
|
return embedInfo(
|
||||||
translate('bot.commands.search_clans.body.title', [
|
translate('bot.commands.search_clans.body.title', [
|
||||||
markdownEscape(clan),
|
markdownEscape(clan),
|
||||||
translate(`common.regions.normal.${server}`),
|
translate(`common.regions.normal.${region}`),
|
||||||
]),
|
]),
|
||||||
clanList.length === 0
|
clanList.length === 0
|
||||||
? translate('bot.commands.search_clans.body.no_results')
|
? translate('bot.commands.search_clans.body.no_results')
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Locale } from 'discord.js';
|
import { Locale } from 'discord.js';
|
||||||
import markdownEscape from 'markdown-escape';
|
import markdownEscape from 'markdown-escape';
|
||||||
import { Region } from '../constants/regions';
|
import { Region } from '../constants/regions';
|
||||||
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
|
|
||||||
import fetchBlitz from '../core/blitz/fetchBlitz';
|
import fetchBlitz from '../core/blitz/fetchBlitz';
|
||||||
import { AccountList } from '../core/blitz/searchPlayersAcrossRegions';
|
import { AccountList } from '../core/blitz/searchPlayersAcrossRegions';
|
||||||
import { usernamePattern } from '../core/blitz/searchPlayersAcrossRegions/constants';
|
import { usernamePattern } from '../core/blitz/searchPlayersAcrossRegions/constants';
|
||||||
@@ -50,22 +49,21 @@ export const searchPlayersCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
const { translate } = translator(interaction.locale);
|
const { translate } = translator(interaction.locale);
|
||||||
const server = interaction.options.getString('region') as Region;
|
const region = interaction.options.getString('region') as Region;
|
||||||
const name = interaction.options.getString('username')!;
|
const name = interaction.options.getString('username')!;
|
||||||
const limit = interaction.options.getInteger('limit') ?? 25;
|
const limit = interaction.options.getInteger('limit') ?? 25;
|
||||||
const trimmedSearch = name.trim();
|
const trimmedSearch = name.trim();
|
||||||
const players = usernamePattern.test(trimmedSearch)
|
const players = usernamePattern.test(trimmedSearch)
|
||||||
? await fetchBlitz<AccountList>(
|
? await fetchBlitz<AccountList>(region, 'account/list', {
|
||||||
`https://api.wotblitz.${server}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodeURIComponent(
|
search: trimmedSearch,
|
||||||
trimmedSearch,
|
limit,
|
||||||
)}&limit=${limit}`,
|
})
|
||||||
)
|
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return embedInfo(
|
return embedInfo(
|
||||||
translate('bot.commands.search_players.body.title', [
|
translate('bot.commands.search_players.body.title', [
|
||||||
markdownEscape(trimmedSearch),
|
markdownEscape(trimmedSearch),
|
||||||
translate(`common.regions.normal.${server}`),
|
translate(`common.regions.normal.${region}`),
|
||||||
]),
|
]),
|
||||||
`\`\`\`${
|
`\`\`\`${
|
||||||
players.length === 0
|
players.length === 0
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Region } from '../../constants/regions';
|
||||||
|
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
||||||
import { patientFetch } from '../blitzkrieg/patientFetch';
|
import { patientFetch } from '../blitzkrieg/patientFetch';
|
||||||
|
|
||||||
type BlitzResponse<Data extends object> =
|
type BlitzResponse<Data extends object> =
|
||||||
@@ -24,31 +26,54 @@ async function manageQueue() {
|
|||||||
manageQueue();
|
manageQueue();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
const { url, resolve } = queue.shift()!;
|
const request = queue.shift()!;
|
||||||
const data = (await patientFetch(url)
|
const data = (await patientFetch(request.url)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.catch((error) => ({ status: 'error', error }))) as BlitzResponse<
|
.catch((error) => ({ status: 'error', error }))) as BlitzResponse<
|
||||||
typeof resolve
|
typeof request.resolve
|
||||||
>;
|
>;
|
||||||
|
|
||||||
if (data.status === 'ok') {
|
if (data.status === 'ok') {
|
||||||
resolve(data.data);
|
request.resolve(data.data);
|
||||||
} else {
|
} else {
|
||||||
clearTimeout(timeout);
|
if (data.error.message === 'REQUEST_LIMIT_EXCEEDED') {
|
||||||
inProgress--;
|
console.log(
|
||||||
manageQueue();
|
`Rate limit exceeded, putting "${request.url}" back in queue...`,
|
||||||
|
);
|
||||||
|
|
||||||
throw new Error(`Wargaming response error status: "${data.status}"`, {
|
clearTimeout(timeout);
|
||||||
cause: `Message: "${data.error.message}"\nURL: "${url}"`,
|
setTimeout(() => {
|
||||||
});
|
queue.push(request);
|
||||||
|
inProgress--;
|
||||||
|
manageQueue();
|
||||||
|
}, 1000 / MAX_CALLS_PER_SECOND);
|
||||||
|
} else {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
inProgress--;
|
||||||
|
manageQueue();
|
||||||
|
|
||||||
|
throw new Error(`Wargaming response error status: "${data.status}"`, {
|
||||||
|
cause: `Message: "${data.error.message}"\nURL: "${request.url}"`,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function fetchBlitz<Data extends object>(url: string) {
|
export default function fetchBlitz<Data extends object>(
|
||||||
|
region: Region,
|
||||||
|
path: string,
|
||||||
|
params: Record<string, string | number | undefined> = {},
|
||||||
|
) {
|
||||||
return new Promise<Data>((resolve) => {
|
return new Promise<Data>((resolve) => {
|
||||||
queue.push({
|
queue.push({
|
||||||
url,
|
url: `https://api.wotblitz.${region}/wotb/${path}/?${Object.entries({
|
||||||
|
application_id: WARGAMING_APPLICATION_ID,
|
||||||
|
...params,
|
||||||
|
})
|
||||||
|
.filter(([_, value]) => value !== undefined)
|
||||||
|
.map(([key, value]) => `${key}=${encodeURIComponent(value!)}`)
|
||||||
|
.join('&')}`,
|
||||||
resolve(data) {
|
resolve(data) {
|
||||||
resolve(data as Data);
|
resolve(data as Data);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
|
||||||
import fetchBlitz from './fetchBlitz';
|
import fetchBlitz from './fetchBlitz';
|
||||||
import { normalizeIds } from './normalizeIds';
|
import { normalizeIds } from './normalizeIds';
|
||||||
|
|
||||||
@@ -23,9 +22,9 @@ export async function getAccountAchievements<
|
|||||||
: IndividualAccountAchievements[],
|
: IndividualAccountAchievements[],
|
||||||
>(region: Region, ids: Ids) {
|
>(region: Region, ids: Ids) {
|
||||||
const object = await fetchBlitz<AccountAchievements>(
|
const object = await fetchBlitz<AccountAchievements>(
|
||||||
`https://api.wotblitz.${region}/wotb/account/achievements/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${normalizeIds(
|
region,
|
||||||
ids,
|
'account/achievements',
|
||||||
)}`,
|
{ account_id: normalizeIds(ids) },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (typeof ids === 'number') {
|
if (typeof ids === 'number') {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
|
||||||
import fetchBlitz from './fetchBlitz';
|
import fetchBlitz from './fetchBlitz';
|
||||||
import { normalizeIds } from './normalizeIds';
|
import { normalizeIds } from './normalizeIds';
|
||||||
|
|
||||||
@@ -97,11 +96,10 @@ export async function getAccountInfo<
|
|||||||
? IndividualAccountInfo
|
? IndividualAccountInfo
|
||||||
: IndividualAccountInfo[],
|
: IndividualAccountInfo[],
|
||||||
>(region: Region, ids: Ids, extra: string[] = []) {
|
>(region: Region, ids: Ids, extra: string[] = []) {
|
||||||
const object = await fetchBlitz<AccountInfo>(
|
const object = await fetchBlitz<AccountInfo>(region, 'account/info', {
|
||||||
`https://api.wotblitz.${region}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${normalizeIds(
|
account_id: normalizeIds(ids),
|
||||||
ids,
|
extra: extra.length === 0 ? undefined : extra.join(','),
|
||||||
)}${extra.length === 0 ? '' : `&extra=${extra.join(',')}`}`,
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (typeof ids === 'number') {
|
if (typeof ids === 'number') {
|
||||||
return object[ids as number] as ReturnType;
|
return object[ids as number] as ReturnType;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
|
||||||
import fetchBlitz from './fetchBlitz';
|
import fetchBlitz from './fetchBlitz';
|
||||||
import { normalizeExtra } from './normalizeExtra';
|
|
||||||
import { normalizeIds } from './normalizeIds';
|
import { normalizeIds } from './normalizeIds';
|
||||||
|
|
||||||
type IndividualClanAccountInfo = {
|
type IndividualClanAccountInfo = {
|
||||||
@@ -30,11 +28,14 @@ export async function getClanAccountInfo<
|
|||||||
ReturnType = Ids extends number
|
ReturnType = Ids extends number
|
||||||
? IndividualClanAccountInfo
|
? IndividualClanAccountInfo
|
||||||
: IndividualClanAccountInfo[],
|
: IndividualClanAccountInfo[],
|
||||||
>(region: Region, ids: Ids, extra?: string[]) {
|
>(region: Region, ids: Ids, extra: string[] = []) {
|
||||||
const object = await fetchBlitz<ClanAccountInfo>(
|
const object = await fetchBlitz<ClanAccountInfo>(
|
||||||
`https://api.wotblitz.${region}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${normalizeIds(
|
region,
|
||||||
ids,
|
'clans/accountinfo',
|
||||||
)}${normalizeExtra(extra)}`,
|
{
|
||||||
|
account_id: normalizeIds(ids),
|
||||||
|
extra: extra.length === 0 ? undefined : extra.join(','),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (typeof ids === 'number') {
|
if (typeof ids === 'number') {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
|
||||||
import fetchBlitz from './fetchBlitz';
|
import fetchBlitz from './fetchBlitz';
|
||||||
import { normalizeIds } from './normalizeIds';
|
import { normalizeIds } from './normalizeIds';
|
||||||
|
|
||||||
@@ -40,11 +39,9 @@ export async function getClanInfo<
|
|||||||
Ids extends number | number[],
|
Ids extends number | number[],
|
||||||
ReturnType = Ids extends number ? IndividualClanInfo : IndividualClanInfo[],
|
ReturnType = Ids extends number ? IndividualClanInfo : IndividualClanInfo[],
|
||||||
>(region: Region, ids: Ids) {
|
>(region: Region, ids: Ids) {
|
||||||
const object = await fetchBlitz<ClanInfo>(
|
const object = await fetchBlitz<ClanInfo>(region, 'clans/info', {
|
||||||
`https://api.wotblitz.${region}/wotb/clans/info/?application_id=${WARGAMING_APPLICATION_ID}&clan_id=${normalizeIds(
|
clan_id: normalizeIds(ids),
|
||||||
ids,
|
});
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (typeof ids === 'number') {
|
if (typeof ids === 'number') {
|
||||||
return object[ids as number] as ReturnType;
|
return object[ids as number] as ReturnType;
|
||||||
|
|||||||
@@ -1,24 +1,11 @@
|
|||||||
import { Locale } from 'discord.js';
|
|
||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
|
||||||
import { UserError } from '../../hooks/userError';
|
|
||||||
import { TanksStats } from '../../types/tanksStats';
|
import { TanksStats } from '../../types/tanksStats';
|
||||||
import { translator } from '../localization/translator';
|
|
||||||
import fetchBlitz from './fetchBlitz';
|
import fetchBlitz from './fetchBlitz';
|
||||||
|
|
||||||
export default async function getTankStats(
|
export default async function getTankStats(region: Region, id: number) {
|
||||||
region: Region,
|
const tankStats = await fetchBlitz<TanksStats>(region, 'tanks/stats', {
|
||||||
id: number,
|
account_id: id,
|
||||||
locale: Locale,
|
});
|
||||||
) {
|
|
||||||
const { t } = translator(locale);
|
|
||||||
const tankStats = await fetchBlitz<TanksStats>(
|
|
||||||
`https://api.wotblitz.${region}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tankStats[id] === null) {
|
|
||||||
throw new UserError(t`bot.common.errors.no_tank_stats`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tankStats[id];
|
return tankStats[id];
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
|
|
||||||
export function idToRange(id: number): Region {
|
export function idToRegion(id: number): Region {
|
||||||
if (id >= 42e8) {
|
if (id >= 42e8) {
|
||||||
throw new Error('bot id');
|
throw new Error('bot id');
|
||||||
} else if (id >= 31e8) {
|
} else if (id >= 31e8) {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
|
||||||
import { ClanList } from '../../types/clanList';
|
import { ClanList } from '../../types/clanList';
|
||||||
import fetchBlitz from './fetchBlitz';
|
import fetchBlitz from './fetchBlitz';
|
||||||
|
|
||||||
@@ -15,9 +14,10 @@ export default async function searchClansAcrossRegions(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetchBlitz<ClanList>(
|
fetchBlitz<ClanList>('com', 'clans/list', {
|
||||||
`https://api.wotblitz.com/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
|
search,
|
||||||
).then((value) =>
|
limit: normalizedLimit,
|
||||||
|
}).then((value) =>
|
||||||
value
|
value
|
||||||
.map((account) => ({
|
.map((account) => ({
|
||||||
...account,
|
...account,
|
||||||
@@ -25,9 +25,10 @@ export default async function searchClansAcrossRegions(
|
|||||||
}))
|
}))
|
||||||
.splice(0, normalizedLimit),
|
.splice(0, normalizedLimit),
|
||||||
),
|
),
|
||||||
fetchBlitz<ClanList>(
|
fetchBlitz<ClanList>('eu', 'clans/list', {
|
||||||
`https://api.wotblitz.eu/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
|
search,
|
||||||
).then((value) =>
|
limit: normalizedLimit,
|
||||||
|
}).then((value) =>
|
||||||
value
|
value
|
||||||
.map((account) => ({
|
.map((account) => ({
|
||||||
...account,
|
...account,
|
||||||
@@ -35,9 +36,10 @@ export default async function searchClansAcrossRegions(
|
|||||||
}))
|
}))
|
||||||
.splice(0, normalizedLimit),
|
.splice(0, normalizedLimit),
|
||||||
),
|
),
|
||||||
fetchBlitz<ClanList>(
|
fetchBlitz<ClanList>('asia', 'clans/list', {
|
||||||
`https://api.wotblitz.asia/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`,
|
search,
|
||||||
).then((value) =>
|
limit: normalizedLimit,
|
||||||
|
}).then((value) =>
|
||||||
value
|
value
|
||||||
.map((account) => ({
|
.map((account) => ({
|
||||||
...account,
|
...account,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Region } from '../../../constants/regions';
|
import { Region } from '../../../constants/regions';
|
||||||
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
|
|
||||||
import fetchBlitz from '../fetchBlitz';
|
import fetchBlitz from '../fetchBlitz';
|
||||||
import { usernamePattern } from './constants';
|
import { usernamePattern } from './constants';
|
||||||
|
|
||||||
@@ -18,14 +17,14 @@ export default async function searchPlayersAcrossRegions(
|
|||||||
) {
|
) {
|
||||||
const trimmedSearch = search.trim();
|
const trimmedSearch = search.trim();
|
||||||
const normalizedLimit = Math.floor(limit / 3);
|
const normalizedLimit = Math.floor(limit / 3);
|
||||||
const encodedSearch = encodeURIComponent(trimmedSearch);
|
|
||||||
|
|
||||||
if (usernamePattern.test(trimmedSearch)) {
|
if (usernamePattern.test(trimmedSearch)) {
|
||||||
return (
|
return (
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetchBlitz<AccountList>(
|
fetchBlitz<AccountList>('com', 'account/list', {
|
||||||
`https://api.wotblitz.com/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`,
|
search,
|
||||||
).then(
|
limit: normalizedLimit,
|
||||||
|
}).then(
|
||||||
(value) =>
|
(value) =>
|
||||||
value &&
|
value &&
|
||||||
value.map((account) => ({
|
value.map((account) => ({
|
||||||
@@ -33,9 +32,10 @@ export default async function searchPlayersAcrossRegions(
|
|||||||
region: 'com' as Region,
|
region: 'com' as Region,
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
fetchBlitz<AccountList>(
|
fetchBlitz<AccountList>('eu', 'account/list', {
|
||||||
`https://api.wotblitz.eu/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`,
|
search,
|
||||||
).then(
|
limit: normalizedLimit,
|
||||||
|
}).then(
|
||||||
(value) =>
|
(value) =>
|
||||||
value &&
|
value &&
|
||||||
value.map((account) => ({
|
value.map((account) => ({
|
||||||
@@ -43,9 +43,10 @@ export default async function searchPlayersAcrossRegions(
|
|||||||
region: 'eu' as Region,
|
region: 'eu' as Region,
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
fetchBlitz<AccountList>(
|
fetchBlitz<AccountList>('asia', 'account/list', {
|
||||||
`https://api.wotblitz.asia/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=${normalizedLimit}`,
|
search,
|
||||||
).then(
|
limit: normalizedLimit,
|
||||||
|
}).then(
|
||||||
(value) =>
|
(value) =>
|
||||||
value &&
|
value &&
|
||||||
value.map((account) => ({
|
value.map((account) => ({
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export async function commitAssets(
|
|||||||
message: string,
|
message: string,
|
||||||
changes: FileChange[],
|
changes: FileChange[],
|
||||||
production: boolean,
|
production: boolean,
|
||||||
|
verbose = true,
|
||||||
) {
|
) {
|
||||||
console.log(`Committing ${message}...`);
|
console.log(`Committing ${message}...`);
|
||||||
|
|
||||||
@@ -15,6 +16,6 @@ export async function commitAssets(
|
|||||||
production ? 'main' : 'dev',
|
production ? 'main' : 'dev',
|
||||||
`${message} - ${new Date().toDateString()}`,
|
`${message} - ${new Date().toDateString()}`,
|
||||||
changes,
|
changes,
|
||||||
true,
|
verbose,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { usersDatabase } from '../../databases/users';
|
import { usersDatabase } from '../../databases/users';
|
||||||
import { idToRange } from '../blitz/idToRegion';
|
import { idToRegion } from '../blitz/idToRegion';
|
||||||
|
|
||||||
export async function flagUserActivity(blitz: number) {
|
export async function flagUserActivity(blitz: number) {
|
||||||
await usersDatabase.users.upsert({
|
await usersDatabase.users.upsert({
|
||||||
@@ -26,5 +26,5 @@ export async function getBlitzFromDiscord(discord: bigint) {
|
|||||||
if (unique === null) return null;
|
if (unique === null) return null;
|
||||||
|
|
||||||
await flagUserActivity(unique.blitz);
|
await flagUserActivity(unique.blitz);
|
||||||
return { id: unique.blitz, region: idToRange(unique.blitz) };
|
return { id: unique.blitz, region: idToRegion(unique.blitz) };
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Locale } from 'discord.js';
|
import { Locale } from 'discord.js';
|
||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
|
import { UserError } from '../../hooks/userError';
|
||||||
import {
|
import {
|
||||||
GetHistoriesOptions,
|
GetHistoriesOptions,
|
||||||
Histories,
|
Histories,
|
||||||
@@ -8,6 +9,7 @@ import {
|
|||||||
} from '../../types/histories';
|
} from '../../types/histories';
|
||||||
import getTankStats from '../blitz/getTankStats';
|
import getTankStats from '../blitz/getTankStats';
|
||||||
import { emptyAllStats } from '../blitzstars/getStatsInPeriod/constants';
|
import { emptyAllStats } from '../blitzstars/getStatsInPeriod/constants';
|
||||||
|
import { translator } from '../localization/translator';
|
||||||
|
|
||||||
export interface TankHistory extends History {
|
export interface TankHistory extends History {
|
||||||
tank_id: number;
|
tank_id: number;
|
||||||
@@ -38,6 +40,7 @@ export default async function getTankHistories(
|
|||||||
locale: Locale,
|
locale: Locale,
|
||||||
options?: Partial<GetTankHistoriesOptions>,
|
options?: Partial<GetTankHistoriesOptions>,
|
||||||
) {
|
) {
|
||||||
|
const { t } = translator(locale);
|
||||||
const mergedOptions = { ...getTankHistoriesDefaultOptions, ...options };
|
const mergedOptions = { ...getTankHistoriesDefaultOptions, ...options };
|
||||||
const tankHistoriesResponse = await fetch(
|
const tankHistoriesResponse = await fetch(
|
||||||
`https://www.blitzstars.com/api/tankhistories/for/${id}/`,
|
`https://www.blitzstars.com/api/tankhistories/for/${id}/`,
|
||||||
@@ -78,7 +81,13 @@ export default async function getTankHistories(
|
|||||||
const latestNodes: TankHistories = [];
|
const latestNodes: TankHistories = [];
|
||||||
|
|
||||||
if (mergedOptions.includeLatestHistories) {
|
if (mergedOptions.includeLatestHistories) {
|
||||||
(await getTankStats(server, id, locale)).forEach((tankHistory) => {
|
const tankStats = await getTankStats(server, id);
|
||||||
|
|
||||||
|
if (tankStats === null) {
|
||||||
|
throw new UserError(t`bot.common.errors.no_tank_stats`);
|
||||||
|
}
|
||||||
|
|
||||||
|
tankStats.forEach((tankHistory) => {
|
||||||
if (
|
if (
|
||||||
tankHistory.all.battles > (lastBattles[tankHistory.tank_id] ?? 0) &&
|
tankHistory.all.battles > (lastBattles[tankHistory.tank_id] ?? 0) &&
|
||||||
(mergedOptions.tankId === undefined ||
|
(mergedOptions.tankId === undefined ||
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
|
|
||||||
import { useSession } from '../../stores/session';
|
import { useSession } from '../../stores/session';
|
||||||
import { NormalizedTankStats, TanksStats } from '../../types/tanksStats';
|
import { NormalizedTankStats, TanksStats } from '../../types/tanksStats';
|
||||||
import fetchBlitz from '../blitz/fetchBlitz';
|
import fetchBlitz from '../blitz/fetchBlitz';
|
||||||
@@ -7,11 +6,9 @@ import fetchBlitz from '../blitz/fetchBlitz';
|
|||||||
export async function setSession(region: Region, id: number, nickname: string) {
|
export async function setSession(region: Region, id: number, nickname: string) {
|
||||||
// TODO: generalize this function and also check for more of these kind
|
// TODO: generalize this function and also check for more of these kind
|
||||||
const rawTankStats = (
|
const rawTankStats = (
|
||||||
await fetchBlitz<TanksStats>(
|
await fetchBlitz<TanksStats>(region, 'tanks/stats', { account_id: id })
|
||||||
`https://api.wotblitz.${region}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
|
||||||
)
|
|
||||||
)[id];
|
)[id];
|
||||||
const tankStats = rawTankStats.reduce<NormalizedTankStats>(
|
const tankStats = rawTankStats!.reduce<NormalizedTankStats>(
|
||||||
(accumulator, tank) => ({
|
(accumulator, tank) => ({
|
||||||
...accumulator,
|
...accumulator,
|
||||||
[tank.tank_id]: tank,
|
[tank.tank_id]: tank,
|
||||||
|
|||||||
@@ -291,7 +291,11 @@ export function tankCharacteristics(
|
|||||||
);
|
);
|
||||||
const dpmMaximum =
|
const dpmMaximum =
|
||||||
gun.type === 'autoReloader'
|
gun.type === 'autoReloader'
|
||||||
? gun.reload.at(-1)! < gun.reload.at(-2)!
|
? (
|
||||||
|
gun.count > 2
|
||||||
|
? gun.reload.at(-1)! < gun.reload.at(-2)!
|
||||||
|
: gun.reload.at(-1)! + gun.intraClip < gun.reload.at(-2)!
|
||||||
|
)
|
||||||
? ((damageCoefficient * shell.damage.armor) /
|
? ((damageCoefficient * shell.damage.armor) /
|
||||||
(gun.reload.at(-1)! * reloadCoefficient +
|
(gun.reload.at(-1)! * reloadCoefficient +
|
||||||
gun.intraClip * intraClipCoefficient)) *
|
gun.intraClip * intraClipCoefficient)) *
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
import { times } from 'lodash';
|
||||||
|
import { IndividualTankStats } from '../../types/tanksStats';
|
||||||
|
import { AllStats } from '../blitz/getAccountInfo';
|
||||||
|
import { ReadStream, WriteStream } from './buffer';
|
||||||
|
|
||||||
|
export interface BlitzkriegTankStats extends AllStats {
|
||||||
|
id: number;
|
||||||
|
battle_life_time: number;
|
||||||
|
last_battle_time: number;
|
||||||
|
mark_of_mastery: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RtsmReadStream extends ReadStream {
|
||||||
|
rtsm() {
|
||||||
|
this.magic();
|
||||||
|
this.header();
|
||||||
|
return this.body();
|
||||||
|
}
|
||||||
|
|
||||||
|
magic() {
|
||||||
|
const magic = this.utf8(4);
|
||||||
|
|
||||||
|
if (magic !== 'RTSM') {
|
||||||
|
throw new Error(`Invalid RTSM magic number: "${magic}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header() {
|
||||||
|
return {
|
||||||
|
version: this.uint16(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
body() {
|
||||||
|
return {
|
||||||
|
time: this.uint32(),
|
||||||
|
base: this.uint8(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RtsmWriteStream extends WriteStream {
|
||||||
|
rtsm(base: number) {
|
||||||
|
this.magic();
|
||||||
|
this.header();
|
||||||
|
this.body(base);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
magic() {
|
||||||
|
this.utf8('RTSM');
|
||||||
|
}
|
||||||
|
|
||||||
|
header() {
|
||||||
|
this.uint16(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body(base: number) {
|
||||||
|
this.uint32(Math.round(Date.now() / 1000));
|
||||||
|
this.uint8(base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RtscReadStream extends ReadStream {
|
||||||
|
rtsc() {
|
||||||
|
this.magic();
|
||||||
|
const header = this.header();
|
||||||
|
return this.body(header.playerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
magic() {
|
||||||
|
const magic = this.utf8(4);
|
||||||
|
|
||||||
|
if (magic !== 'RTSC') {
|
||||||
|
throw new Error(`Invalid RTSC magic number: "${magic}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header() {
|
||||||
|
return {
|
||||||
|
version: this.uint16(),
|
||||||
|
playerCount: this.uint32(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
body(playerCount: number) {
|
||||||
|
return times(playerCount, () => this.player());
|
||||||
|
}
|
||||||
|
|
||||||
|
player() {
|
||||||
|
const id = this.uint32();
|
||||||
|
const tankCount = this.uint16();
|
||||||
|
const tanks = times(tankCount, () => this.tank());
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
tankCount,
|
||||||
|
tanks,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tank() {
|
||||||
|
return {
|
||||||
|
id: this.uint32(),
|
||||||
|
battle_life_time: this.uint32(),
|
||||||
|
last_battle_time: this.uint32(),
|
||||||
|
mark_of_mastery: this.uint8(),
|
||||||
|
battles: this.uint32(),
|
||||||
|
capture_points: this.uint32(),
|
||||||
|
damage_dealt: this.uint32(),
|
||||||
|
damage_received: this.uint32(),
|
||||||
|
dropped_capture_points: this.uint32(),
|
||||||
|
frags: this.uint32(),
|
||||||
|
frags8p: this.uint32(),
|
||||||
|
hits: this.uint32(),
|
||||||
|
losses: this.uint32(),
|
||||||
|
max_frags: this.uint8(),
|
||||||
|
max_xp: this.uint16(),
|
||||||
|
shots: this.uint32(),
|
||||||
|
spotted: this.uint32(),
|
||||||
|
survived_battles: this.uint32(),
|
||||||
|
win_and_survived: this.uint32(),
|
||||||
|
wins: this.uint32(),
|
||||||
|
xp: this.uint32(),
|
||||||
|
} satisfies BlitzkriegTankStats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RtscWritePlayer {
|
||||||
|
id: number;
|
||||||
|
tanks: IndividualTankStats[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RtscWriteStream extends WriteStream {
|
||||||
|
rtsc(players: RtscWritePlayer[]) {
|
||||||
|
this.magic();
|
||||||
|
this.header(players.length);
|
||||||
|
this.body(players);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
magic() {
|
||||||
|
this.utf8('RTSC');
|
||||||
|
}
|
||||||
|
|
||||||
|
header(playerCount: number) {
|
||||||
|
this.uint16(1);
|
||||||
|
this.uint32(playerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
body(players: RtscWritePlayer[]) {
|
||||||
|
players.forEach((player) => {
|
||||||
|
this.uint32(player.id);
|
||||||
|
this.uint16(player.tanks.length);
|
||||||
|
|
||||||
|
player.tanks.forEach((tank) => {
|
||||||
|
this.uint32(tank.tank_id);
|
||||||
|
this.uint32(tank.battle_life_time);
|
||||||
|
this.uint32(tank.last_battle_time);
|
||||||
|
this.uint8(tank.mark_of_mastery);
|
||||||
|
this.uint32(tank.all.battles);
|
||||||
|
this.uint32(tank.all.capture_points);
|
||||||
|
this.uint32(tank.all.damage_dealt);
|
||||||
|
this.uint32(tank.all.damage_received);
|
||||||
|
this.uint32(tank.all.dropped_capture_points);
|
||||||
|
this.uint32(tank.all.frags);
|
||||||
|
this.uint32(tank.all.frags8p);
|
||||||
|
this.uint32(tank.all.hits);
|
||||||
|
this.uint32(tank.all.losses);
|
||||||
|
this.uint8(tank.all.max_frags);
|
||||||
|
this.uint16(tank.all.max_xp);
|
||||||
|
this.uint32(tank.all.shots);
|
||||||
|
this.uint32(tank.all.spotted);
|
||||||
|
this.uint32(tank.all.survived_battles);
|
||||||
|
this.uint32(tank.all.win_and_survived);
|
||||||
|
this.uint32(tank.all.wins);
|
||||||
|
this.uint32(tank.all.xp);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-12
@@ -62,6 +62,18 @@
|
|||||||
|
|
||||||
"bot": {
|
"bot": {
|
||||||
"common": {
|
"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": "# I don't know who you are\nUse the `/link` command to get started.",
|
||||||
|
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"%s\".",
|
||||||
|
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"%s\". Try typing the name of the tank instead of a number.",
|
||||||
|
"no_tank_stats": "# No tank stats available\nThis player doesn't have any stats available. This may not be the player you are looking for.",
|
||||||
|
"get_help": "Help Discord server",
|
||||||
|
"uncaught_error": "# Blitzkrieg ran into an error!\nWe're so sorry about this. Feel free to [join the official Discord server](https://discord.gg/nDt7AjGJQH) to get help.",
|
||||||
|
"preview": "# This command is in preview\n\nYou can only access this command in [the official Discord server](https://discord.gg/nDt7AjGJQH). The command will be generally available soon."
|
||||||
|
},
|
||||||
|
|
||||||
"wn8": {
|
"wn8": {
|
||||||
"$": "WN8",
|
"$": "WN8",
|
||||||
"very_bad": "Very bad",
|
"very_bad": "Very bad",
|
||||||
@@ -183,18 +195,6 @@
|
|||||||
"refresh": "Refresh"
|
"refresh": "Refresh"
|
||||||
},
|
},
|
||||||
|
|
||||||
"errors": {
|
|
||||||
"clan_not_found": "# Could not find clan\nCouldn't find \"%s\". Try selecting a clan from the search result.",
|
|
||||||
"player_not_found": "# Could not find user\nI couldn't find user \"%s\". Try picking an user from the search result, typing in a valid username, or using the `/link` command.",
|
|
||||||
"player_not_linked": "# I don't know who you are\nUse the `/link` command to get started.",
|
|
||||||
"tank_name_not_found": "# Tank not found\nCould not find tank by the name \"%s\".",
|
|
||||||
"tank_id_not_found": "# Tank not found\nCould not find tank by the ID \"%s\". Try typing the name of the tank instead of a number.",
|
|
||||||
"no_tank_stats": "# No tank stats available\nThis player doesn't have any stats available. This may not be the player you are looking for.",
|
|
||||||
"get_help": "Help Discord server",
|
|
||||||
"uncaught_error": "# Blitzkrieg ran into an error!\nWe're so sorry about this. Feel free to [join the official Discord server](https://discord.gg/nDt7AjGJQH) to get help.",
|
|
||||||
"preview": "# This command is in preview\n\nYou can only access this command in [the official Discord server](https://discord.gg/nDt7AjGJQH). The command will be generally available soon."
|
|
||||||
},
|
|
||||||
|
|
||||||
"hero_stats": {
|
"hero_stats": {
|
||||||
"battles": "Battles",
|
"battles": "Battles",
|
||||||
"winrate": "Winrate",
|
"winrate": "Winrate",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export interface IndividualTankStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TanksStats {
|
export interface TanksStats {
|
||||||
[accountId: number]: IndividualTankStats[];
|
[accountId: number]: null | IndividualTankStats[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NormalizedTankStats {
|
export interface NormalizedTankStats {
|
||||||
|
|||||||
Reference in new issue
Block a user