mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: started work on rating stats
This commit is contained in:
1 parent
18edd349f2
commit
5f28b541e3
10 files changed
+154
-96
No files matched your search
@@ -19,7 +19,7 @@ import { RatingInfo, RatingPlayer } from '../../../commands/ratingLeaderboard';
|
|||||||
import PageWrapper from '../../../components/PageWrapper';
|
import PageWrapper from '../../../components/PageWrapper';
|
||||||
import { Skeleton } from '../../../components/Skeleton';
|
import { Skeleton } from '../../../components/Skeleton';
|
||||||
import { LEAGUES } from '../../../constants/leagues';
|
import { LEAGUES } from '../../../constants/leagues';
|
||||||
import { FIRST_ARCHIVED_RATING_SEASON } from '../../../constants/rating';
|
import { FIRST_MINIMAL_ARCHIVED_RATING_SEASON } from '../../../constants/rating';
|
||||||
import {
|
import {
|
||||||
REGIONS,
|
REGIONS,
|
||||||
Region,
|
Region,
|
||||||
@@ -582,7 +582,7 @@ export default function Page() {
|
|||||||
|
|
||||||
{latestArchivedSeasonNumber &&
|
{latestArchivedSeasonNumber &&
|
||||||
range(
|
range(
|
||||||
FIRST_ARCHIVED_RATING_SEASON,
|
FIRST_MINIMAL_ARCHIVED_RATING_SEASON,
|
||||||
latestArchivedSeasonNumber + 1,
|
latestArchivedSeasonNumber + 1,
|
||||||
)
|
)
|
||||||
.map((season) => (
|
.map((season) => (
|
||||||
@@ -714,11 +714,7 @@ export default function Page() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (!ratingInfo || ratingInfo.detail || !leaderboard)
|
||||||
!ratingInfo ||
|
|
||||||
ratingInfo.detail ||
|
|
||||||
!leaderboard
|
|
||||||
)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const minScore =
|
const minScore =
|
||||||
@@ -951,9 +947,7 @@ export default function Page() {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<PageTurner
|
<PageTurner
|
||||||
totalPlayers={
|
totalPlayers={(ratingInfo?.detail ? undefined : ratingInfo?.count) ?? 0}
|
||||||
(ratingInfo?.detail ? undefined : ratingInfo?.count) ?? 0
|
|
||||||
}
|
|
||||||
leaderboard={leaderboard[region][season] ?? {}}
|
leaderboard={leaderboard[region][season] ?? {}}
|
||||||
rowsPerPage={ROWS_PER_PAGE}
|
rowsPerPage={ROWS_PER_PAGE}
|
||||||
page={page}
|
page={page}
|
||||||
@@ -1090,9 +1084,7 @@ export default function Page() {
|
|||||||
</Table.Root>
|
</Table.Root>
|
||||||
|
|
||||||
<PageTurner
|
<PageTurner
|
||||||
totalPlayers={
|
totalPlayers={(ratingInfo?.detail ? undefined : ratingInfo?.count) ?? 0}
|
||||||
(ratingInfo?.detail ? undefined : ratingInfo?.count) ?? 0
|
|
||||||
}
|
|
||||||
rowsPerPage={ROWS_PER_PAGE}
|
rowsPerPage={ROWS_PER_PAGE}
|
||||||
leaderboard={leaderboard[region][season] ?? {}}
|
leaderboard={leaderboard[region][season] ?? {}}
|
||||||
page={page}
|
page={page}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import CommandWrapper from '../components/CommandWrapper';
|
|||||||
import * as Leaderboard from '../components/Leaderboard';
|
import * as Leaderboard from '../components/Leaderboard';
|
||||||
import TitleBar from '../components/TitleBar';
|
import TitleBar from '../components/TitleBar';
|
||||||
import { LEAGUES } from '../constants/leagues';
|
import { LEAGUES } from '../constants/leagues';
|
||||||
import { FIRST_ARCHIVED_RATING_SEASON } from '../constants/rating';
|
import { FIRST_MINIMAL_ARCHIVED_RATING_SEASON } from '../constants/rating';
|
||||||
import { Region } from '../constants/regions';
|
import { Region } from '../constants/regions';
|
||||||
import { getAccountInfo } from '../core/blitz/getAccountInfo';
|
import { getAccountInfo } from '../core/blitz/getAccountInfo';
|
||||||
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
|
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
|
||||||
@@ -134,7 +134,7 @@ export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
|||||||
const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber();
|
const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber();
|
||||||
const onGoingSeason = await isOnGoingRatingSeason();
|
const onGoingSeason = await isOnGoingRatingSeason();
|
||||||
const seasonNumbers = range(
|
const seasonNumbers = range(
|
||||||
FIRST_ARCHIVED_RATING_SEASON,
|
FIRST_MINIMAL_ARCHIVED_RATING_SEASON,
|
||||||
latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1,
|
latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
import markdownEscape from 'markdown-escape';
|
||||||
|
import AllStatsOverview from '../components/AllStatsOverview';
|
||||||
|
import CommandWrapper from '../components/CommandWrapper';
|
||||||
|
import NoData from '../components/NoData';
|
||||||
|
import TitleBar from '../components/TitleBar';
|
||||||
|
import { getAccountInfo } from '../core/blitz/getAccountInfo';
|
||||||
|
import { getRatingNeighbors } from '../core/blitz/getRatingNeighbors';
|
||||||
|
import { getArchivedRatingLeaderboard } from '../core/blitzkrieg/getArchivedRatingLeaderboard';
|
||||||
|
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||||
|
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||||
|
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
||||||
|
import { translator } from '../core/localization/translator';
|
||||||
|
import { deltaBkrlBlitzStats } from '../core/statistics/deltaBkrlBlitzStats';
|
||||||
|
import { BkrlFormat } from '../core/streams/bkrl';
|
||||||
|
import { CommandRegistry } from '../events/interactionCreate';
|
||||||
|
|
||||||
|
export const ratingStatsCommand = new Promise<CommandRegistry>((resolve) => {
|
||||||
|
resolve({
|
||||||
|
command:
|
||||||
|
createLocalizedCommand('rating-stats').addStringOption(
|
||||||
|
addUsernameChoices,
|
||||||
|
),
|
||||||
|
|
||||||
|
inProduction: true,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
|
async handler(interaction) {
|
||||||
|
const { t, translate } = translator(interaction.locale);
|
||||||
|
const { id, region } = await resolvePlayerFromCommand(interaction);
|
||||||
|
const leaderboard = await getArchivedRatingLeaderboard(region, 53);
|
||||||
|
|
||||||
|
if (leaderboard.format === BkrlFormat.Minimal) {
|
||||||
|
throw new Error(
|
||||||
|
'Encountered bkrl minimal format in latest season. Wait till next cron?',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountInfo = await getAccountInfo(region, id, [
|
||||||
|
'statistics.rating',
|
||||||
|
]);
|
||||||
|
const statsA = leaderboard.entries.find((entry) => entry.id === id);
|
||||||
|
const statsB1 = accountInfo.statistics.rating!;
|
||||||
|
const [statsB2] = (await getRatingNeighbors(region, id, 0)).neighbors;
|
||||||
|
|
||||||
|
if (!statsA) {
|
||||||
|
return t`bot.commands.rating_stats.errors.unrecorded_stats`;
|
||||||
|
}
|
||||||
|
if (!statsB1) {
|
||||||
|
return translate('bot.commands.rating_stats.errors.no_participation', [
|
||||||
|
markdownEscape(accountInfo.nickname),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const delta = deltaBkrlBlitzStats(statsA, accountInfo.statistics.rating!);
|
||||||
|
const scoreDiff = statsB2.score - statsA.score;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommandWrapper>
|
||||||
|
<TitleBar
|
||||||
|
title={accountInfo.nickname}
|
||||||
|
// image={clanImage} // TODO: add league icon here
|
||||||
|
description={t`bot.commands.rating_stats.body.subtitle`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{delta.battles === 0 && (
|
||||||
|
<NoData type="battles_in_season" locale={interaction.locale} />
|
||||||
|
)}
|
||||||
|
{delta.battles > 0 && (
|
||||||
|
<AllStatsOverview
|
||||||
|
locale={interaction.locale}
|
||||||
|
stats={{
|
||||||
|
battles: delta.battles,
|
||||||
|
damage_dealt: delta.damageDealt,
|
||||||
|
damage_received: delta.damageReceived,
|
||||||
|
frags: delta.kills,
|
||||||
|
hits: delta.hits,
|
||||||
|
shots: delta.shots,
|
||||||
|
survived_battles: delta.survived,
|
||||||
|
wins: delta.wins,
|
||||||
|
}}
|
||||||
|
supplementaryStats={{ WN8: 3000, tier: 10 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CommandWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
// return `# ${markdownEscape(accountInfo.nickname)}\n\n${markdownTable([
|
||||||
|
// [
|
||||||
|
// 'score',
|
||||||
|
// `${statsB2.score}${scoreDiff === 0 ? '' : ` (${scoreDiff > 0 ? '+' : ''}${scoreDiff})`}`,
|
||||||
|
// ],
|
||||||
|
// ['winrate', `${(100 * (delta.wins / delta.battles)).toFixed(0)}%`],
|
||||||
|
// ['battles', delta.battles],
|
||||||
|
// ['wins / losses', `${delta.wins} / ${delta.battles - delta.wins}`],
|
||||||
|
// ['wn8', 'impossible'],
|
||||||
|
// ['survival', `${(100 * (delta.survived / delta.battles)).toFixed(0)}%`],
|
||||||
|
// ['accuracy', `${(100 * (delta.hits / delta.shots)).toFixed(0)}%`],
|
||||||
|
// ['average damage', (delta.damageDealt / delta.battles).toFixed(0)],
|
||||||
|
// ['damager per tier', 'impossible'],
|
||||||
|
// ['damage ratio', (delta.damageDealt / delta.damageReceived).toFixed(2)],
|
||||||
|
// ['average kills', (delta.kills / delta.battles).toFixed(2)],
|
||||||
|
// ['average tier', 'impossible'],
|
||||||
|
// ['average spots', 'possible'],
|
||||||
|
// ['average xp', 'possible'],
|
||||||
|
// [
|
||||||
|
// 'kills to death ratio',
|
||||||
|
// (delta.kills / (delta.wins - delta.survived)).toFixed(2),
|
||||||
|
// ],
|
||||||
|
// ])}`;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import { SlashCommandBuilder } from 'discord.js';
|
|
||||||
import markdownEscape from 'markdown-escape';
|
|
||||||
import { getAccountInfo } from '../core/blitz/getAccountInfo';
|
|
||||||
import { getRatingNeighbors } from '../core/blitz/getRatingNeighbors';
|
|
||||||
import { getArchivedRatingLeaderboard } from '../core/blitzkrieg/getArchivedRatingLeaderboard';
|
|
||||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
|
||||||
import markdownTable from '../core/discord/markdownTable';
|
|
||||||
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
|
||||||
import { deltaBkrlBlitzStats } from '../core/statistics/deltaBkrlBlitzStats';
|
|
||||||
import { BkrlFormat } from '../core/streams/bkrl';
|
|
||||||
import { CommandRegistry } from '../events/interactionCreate';
|
|
||||||
|
|
||||||
export const testCommand = new Promise<CommandRegistry>((resolve) => {
|
|
||||||
resolve({
|
|
||||||
command: new SlashCommandBuilder()
|
|
||||||
.setName('test')
|
|
||||||
.setDescription('test')
|
|
||||||
.addStringOption(addUsernameChoices),
|
|
||||||
|
|
||||||
inProduction: false,
|
|
||||||
inPublic: true,
|
|
||||||
|
|
||||||
async handler(interaction) {
|
|
||||||
const { id, region } = await resolvePlayerFromCommand(interaction);
|
|
||||||
const leaderboard = await getArchivedRatingLeaderboard(region, 53);
|
|
||||||
|
|
||||||
if (leaderboard.format === BkrlFormat.Minimal) {
|
|
||||||
return 'bkrl minimal format not supported';
|
|
||||||
}
|
|
||||||
|
|
||||||
const statsA = leaderboard.entries.find((entry) => entry.id === id);
|
|
||||||
|
|
||||||
if (!statsA) {
|
|
||||||
return `Didn't find player: ${id}, region: ${region}`;
|
|
||||||
}
|
|
||||||
const accountInfo = await getAccountInfo(region, id, [
|
|
||||||
'statistics.rating',
|
|
||||||
]);
|
|
||||||
const statsB1 = accountInfo.statistics.rating!;
|
|
||||||
|
|
||||||
if (!statsB1) {
|
|
||||||
return 'User did not participate in this season';
|
|
||||||
}
|
|
||||||
|
|
||||||
const delta = deltaBkrlBlitzStats(statsA, accountInfo.statistics.rating!);
|
|
||||||
const [statsB2] = (await getRatingNeighbors(region, id, 0)).neighbors;
|
|
||||||
const scoreDiff = statsB2.score - statsA.score;
|
|
||||||
|
|
||||||
return `# ${markdownEscape(accountInfo.nickname)}\n\n${markdownTable([
|
|
||||||
[
|
|
||||||
'score',
|
|
||||||
`${statsB2.score}${scoreDiff === 0 ? '' : ` (${scoreDiff > 0 ? '+' : ''}${scoreDiff})`}`,
|
|
||||||
],
|
|
||||||
['winrate', `${(100 * (delta.wins / delta.battles)).toFixed(0)}%`],
|
|
||||||
['battles', delta.battles],
|
|
||||||
['wins / losses', `${delta.wins} / ${delta.battles - delta.wins}`],
|
|
||||||
['wn8', 'impossible'],
|
|
||||||
['survival', `${(100 * (delta.survived / delta.battles)).toFixed(0)}%`],
|
|
||||||
['accuracy', `${(100 * (delta.hits / delta.shots)).toFixed(0)}%`],
|
|
||||||
['average damage', (delta.damageDealt / delta.battles).toFixed(0)],
|
|
||||||
['damager per tier', 'impossible'],
|
|
||||||
['damage ratio', (delta.damageDealt / delta.damageReceived).toFixed(2)],
|
|
||||||
['average kills', (delta.kills / delta.battles).toFixed(2)],
|
|
||||||
['average tier', 'impossible'],
|
|
||||||
['average spots', 'possible'],
|
|
||||||
['average xp', 'possible'],
|
|
||||||
[
|
|
||||||
'kills to death ratio',
|
|
||||||
(delta.kills / (delta.wins - delta.survived)).toFixed(2),
|
|
||||||
],
|
|
||||||
])}`;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Locale } from 'discord.js';
|
import { Locale } from 'discord.js';
|
||||||
import { AllStats, SupplementaryStats } from '../../core/blitz/getAccountInfo';
|
import { SupplementaryStats } from '../../core/blitz/getAccountInfo';
|
||||||
import { translator } from '../../core/localization/translator';
|
import { translator } from '../../core/localization/translator';
|
||||||
import { WN8Display } from './components/WN8Display';
|
import { WN8Display } from './components/WN8Display';
|
||||||
import {
|
import {
|
||||||
@@ -8,7 +8,16 @@ import {
|
|||||||
} from './components/WN8SurroundingStat';
|
} from './components/WN8SurroundingStat';
|
||||||
|
|
||||||
export interface AllStatsOverviewProps {
|
export interface AllStatsOverviewProps {
|
||||||
stats: AllStats;
|
stats: {
|
||||||
|
battles: number;
|
||||||
|
wins: number;
|
||||||
|
damage_dealt: number;
|
||||||
|
damage_received: number;
|
||||||
|
survived_battles: number;
|
||||||
|
hits: number;
|
||||||
|
shots: number;
|
||||||
|
frags: number;
|
||||||
|
};
|
||||||
supplementaryStats: SupplementaryStats;
|
supplementaryStats: SupplementaryStats;
|
||||||
locale: Locale;
|
locale: Locale;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ import { translator } from '../../core/localization/translator';
|
|||||||
import { theme } from '../../stitches.config';
|
import { theme } from '../../stitches.config';
|
||||||
|
|
||||||
interface NoDataProps {
|
interface NoDataProps {
|
||||||
type: 'battles_in_period' | 'players_in_period' | 'tanks_found';
|
type:
|
||||||
|
| 'battles_in_period'
|
||||||
|
| 'players_in_period'
|
||||||
|
| 'tanks_found'
|
||||||
|
| 'battles_in_season';
|
||||||
locale: Locale;
|
locale: Locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export const FIRST_ARCHIVED_RATING_SEASON = 49;
|
export const FIRST_MINIMAL_ARCHIVED_RATING_SEASON = 49;
|
||||||
|
export const FIRST_COMPREHENSIVE1_ARCHIVED_RATING_SEASON = 53;
|
||||||
@@ -24,7 +24,6 @@ export interface AllStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SupplementaryStats {
|
export interface SupplementaryStats {
|
||||||
// if undefined, WN8 is not available for tanks without averages
|
|
||||||
WN8?: number;
|
WN8?: number;
|
||||||
tier?: number;
|
tier?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ import { permissionsCommand } from '../../commands/permissions';
|
|||||||
import { pingCommand } from '../../commands/ping';
|
import { pingCommand } from '../../commands/ping';
|
||||||
import { playerInfoCommand } from '../../commands/playerInfo';
|
import { playerInfoCommand } from '../../commands/playerInfo';
|
||||||
import { ratingLeaderboardCommand } from '../../commands/ratingLeaderboard';
|
import { ratingLeaderboardCommand } from '../../commands/ratingLeaderboard';
|
||||||
|
import { ratingStatsCommand } from '../../commands/ratingStats';
|
||||||
import { researchCommand } from '../../commands/research';
|
import { researchCommand } from '../../commands/research';
|
||||||
import { searchClansCommand } from '../../commands/searchClans';
|
import { searchClansCommand } from '../../commands/searchClans';
|
||||||
import { searchPlayersCommand } from '../../commands/searchPlayers';
|
import { searchPlayersCommand } from '../../commands/searchPlayers';
|
||||||
import { statsCommand } from '../../commands/stats';
|
import { statsCommand } from '../../commands/stats';
|
||||||
import { testCommand } from '../../commands/test';
|
|
||||||
import { todayCommand } from '../../commands/today';
|
import { todayCommand } from '../../commands/today';
|
||||||
import getClientId from '../../core/blitzkrieg/getClientId';
|
import getClientId from '../../core/blitzkrieg/getClientId';
|
||||||
import isDev from '../../core/blitzkrieg/isDev';
|
import isDev from '../../core/blitzkrieg/isDev';
|
||||||
@@ -96,7 +96,7 @@ export const COMMANDS_RAW: Promise<CommandRegistry>[] = [
|
|||||||
ratingLeaderboardCommand,
|
ratingLeaderboardCommand,
|
||||||
todayCommand,
|
todayCommand,
|
||||||
researchCommand,
|
researchCommand,
|
||||||
testCommand,
|
ratingStatsCommand,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => {
|
export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => {
|
||||||
|
|||||||
@@ -174,6 +174,7 @@
|
|||||||
|
|
||||||
"no_data": {
|
"no_data": {
|
||||||
"battles_in_period": "No battles played in this period",
|
"battles_in_period": "No battles played in this period",
|
||||||
|
"battles_in_season": "No battles played in this season",
|
||||||
"players_in_period": "No players in this period",
|
"players_in_period": "No players in this period",
|
||||||
"tanks_found": "No tanks found"
|
"tanks_found": "No tanks found"
|
||||||
},
|
},
|
||||||
@@ -538,6 +539,20 @@
|
|||||||
"created": "Created",
|
"created": "Created",
|
||||||
"last_battle": "Last battle"
|
"last_battle": "Last battle"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"rating_stats": {
|
||||||
|
"$": "rating-stats",
|
||||||
|
"description": "Rating player's statistics",
|
||||||
|
|
||||||
|
"body": {
|
||||||
|
"subtitle": "Today's rating statistics"
|
||||||
|
},
|
||||||
|
|
||||||
|
"errors": {
|
||||||
|
"no_participation": "# %s hasn't played rating\n\nThis player hasn't participated in the rating gamemode this season.",
|
||||||
|
"unrecorded_stats": "# Statistics unavailable\n\nRatings statistics are currently unavailable. If there is an on-going season, statistics should be available withing 24 hours."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user