diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index c910ad0f6..5fd68784b 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -19,7 +19,7 @@ import { RatingInfo, RatingPlayer } from '../../../commands/ratingLeaderboard'; import PageWrapper from '../../../components/PageWrapper'; import { Skeleton } from '../../../components/Skeleton'; import { LEAGUES } from '../../../constants/leagues'; -import { FIRST_ARCHIVED_RATING_SEASON } from '../../../constants/rating'; +import { FIRST_MINIMAL_ARCHIVED_RATING_SEASON } from '../../../constants/rating'; import { REGIONS, Region, @@ -582,7 +582,7 @@ export default function Page() { {latestArchivedSeasonNumber && range( - FIRST_ARCHIVED_RATING_SEASON, + FIRST_MINIMAL_ARCHIVED_RATING_SEASON, latestArchivedSeasonNumber + 1, ) .map((season) => ( @@ -714,11 +714,7 @@ export default function Page() { ); } } else { - if ( - !ratingInfo || - ratingInfo.detail || - !leaderboard - ) + if (!ratingInfo || ratingInfo.detail || !leaderboard) return; const minScore = @@ -951,9 +947,7 @@ export default function Page() { ( const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber(); const onGoingSeason = await isOnGoingRatingSeason(); const seasonNumbers = range( - FIRST_ARCHIVED_RATING_SEASON, + FIRST_MINIMAL_ARCHIVED_RATING_SEASON, latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1, ); diff --git a/src/commands/ratingStats.tsx b/src/commands/ratingStats.tsx new file mode 100644 index 000000000..8a12bdc83 --- /dev/null +++ b/src/commands/ratingStats.tsx @@ -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((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 ( + + + + {delta.battles === 0 && ( + + )} + {delta.battles > 0 && ( + + )} + + ); + + // 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), + // ], + // ])}`; + }, + }); +}); diff --git a/src/commands/test.ts b/src/commands/test.ts deleted file mode 100644 index 2e23ff1ae..000000000 --- a/src/commands/test.ts +++ /dev/null @@ -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((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), - ], - ])}`; - }, - }); -}); diff --git a/src/components/AllStatsOverview/index.tsx b/src/components/AllStatsOverview/index.tsx index 6868b03c0..6834b1121 100644 --- a/src/components/AllStatsOverview/index.tsx +++ b/src/components/AllStatsOverview/index.tsx @@ -1,5 +1,5 @@ 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 { WN8Display } from './components/WN8Display'; import { @@ -8,7 +8,16 @@ import { } from './components/WN8SurroundingStat'; 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; locale: Locale; } diff --git a/src/components/NoData/index.tsx b/src/components/NoData/index.tsx index bc4774558..4a23defcb 100644 --- a/src/components/NoData/index.tsx +++ b/src/components/NoData/index.tsx @@ -3,7 +3,11 @@ import { translator } from '../../core/localization/translator'; import { theme } from '../../stitches.config'; 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; } diff --git a/src/constants/rating.ts b/src/constants/rating.ts index 8fc379603..1cea51187 100644 --- a/src/constants/rating.ts +++ b/src/constants/rating.ts @@ -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; diff --git a/src/core/blitz/getAccountInfo.ts b/src/core/blitz/getAccountInfo.ts index 3f15faaa0..e9cab4285 100644 --- a/src/core/blitz/getAccountInfo.ts +++ b/src/core/blitz/getAccountInfo.ts @@ -24,7 +24,6 @@ export interface AllStats { } export interface SupplementaryStats { - // if undefined, WN8 is not available for tanks without averages WN8?: number; tier?: number; } diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts index b0f2a58a8..04823b0d5 100644 --- a/src/events/interactionCreate/index.ts +++ b/src/events/interactionCreate/index.ts @@ -26,11 +26,11 @@ import { permissionsCommand } from '../../commands/permissions'; import { pingCommand } from '../../commands/ping'; import { playerInfoCommand } from '../../commands/playerInfo'; import { ratingLeaderboardCommand } from '../../commands/ratingLeaderboard'; +import { ratingStatsCommand } from '../../commands/ratingStats'; import { researchCommand } from '../../commands/research'; import { searchClansCommand } from '../../commands/searchClans'; import { searchPlayersCommand } from '../../commands/searchPlayers'; import { statsCommand } from '../../commands/stats'; -import { testCommand } from '../../commands/test'; import { todayCommand } from '../../commands/today'; import getClientId from '../../core/blitzkrieg/getClientId'; import isDev from '../../core/blitzkrieg/isDev'; @@ -96,7 +96,7 @@ export const COMMANDS_RAW: Promise[] = [ ratingLeaderboardCommand, todayCommand, researchCommand, - testCommand, + ratingStatsCommand, ]; export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => { diff --git a/src/lang/en-US.json b/src/lang/en-US.json index a2fdaae3c..f3bca9607 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -174,6 +174,7 @@ "no_data": { "battles_in_period": "No battles played in this period", + "battles_in_season": "No battles played in this season", "players_in_period": "No players in this period", "tanks_found": "No tanks found" }, @@ -538,6 +539,20 @@ "created": "Created", "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." + } } } },