mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 11:39:50 +02:00
feat: updated /tankstats
This commit is contained in:
1 parent
50369e9225
commit
831300cf69
13 files changed
+300
-272
No files matched your search
@@ -79,8 +79,8 @@ export default {
|
||||
: tanks
|
||||
.map(
|
||||
(tank) =>
|
||||
`${TANK_TYPE_EMOJIS[tank.type]} ${resolveTankName(
|
||||
tank,
|
||||
`${TANK_TYPE_EMOJIS[tank.type]} ${markdownEscape(
|
||||
resolveTankName(tank.tank_id),
|
||||
)} ${tank.is_premium ? '⭐' : ''}${
|
||||
COMP_TANKS.includes(tank.tank_id) ? '🏆' : ''
|
||||
}`,
|
||||
|
||||
+10
-33
@@ -15,8 +15,14 @@ import usernameAutocomplete from '../core/autocomplete/username.js';
|
||||
import getBlitzAccount from '../core/blitz/getBlitzAccount.js';
|
||||
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
|
||||
import sumStats from '../core/blitz/sumStats.js';
|
||||
import getPeriodNow from '../core/blitzstars/getPeriodNow.js';
|
||||
import getPeriodicStart from '../core/blitzstars/getPeriodStart.js';
|
||||
import getTankStatsOverTime from '../core/blitzstars/getTankStatsOverTime.js';
|
||||
import cmdName from '../core/interaction/cmdName.js';
|
||||
import addStatPeriodChoices, {
|
||||
StatPeriod,
|
||||
statPeriodNames,
|
||||
} from '../core/options/addStatPeriodChoices.js';
|
||||
import addUsernameOption from '../core/options/addUsernameOption.js';
|
||||
import { args } from '../core/process/args.js';
|
||||
import render from '../core/ui/render.js';
|
||||
@@ -24,36 +30,15 @@ import { CommandRegistry } from '../events/interactionCreate.js';
|
||||
import { AccountInfo, AllStats } from '../types/accountInfo.js';
|
||||
import { PlayerClanData } from '../types/playerClanData.js';
|
||||
|
||||
export type StatPeriod = 'today' | '30' | '60' | '90' | 'career';
|
||||
export const statPeriodNames: Record<StatPeriod, string> = {
|
||||
today: "Today's statistics",
|
||||
30: '30-day statistics',
|
||||
60: '60-day statistics',
|
||||
90: '90-day statistics',
|
||||
career: 'Career statistics',
|
||||
};
|
||||
|
||||
export default {
|
||||
inProduction: true,
|
||||
inDevelopment: true,
|
||||
inDevelopment: false,
|
||||
inPublic: true,
|
||||
|
||||
command: new SlashCommandBuilder()
|
||||
.setName(cmdName('stats'))
|
||||
.setDescription("Gets the user's in-game statistics")
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('period')
|
||||
.setDescription('The last number of days of stats')
|
||||
.setChoices(
|
||||
{ name: 'Today', value: 'today' satisfies StatPeriod },
|
||||
{ name: '30 Days', value: '30' satisfies StatPeriod },
|
||||
{ name: '60 Days', value: '60' satisfies StatPeriod },
|
||||
{ name: '90 Days', value: '90' satisfies StatPeriod },
|
||||
{ name: 'Career', value: 'career' satisfies StatPeriod },
|
||||
)
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption(addStatPeriodChoices)
|
||||
.addStringOption(addUsernameOption),
|
||||
|
||||
async execute(interaction) {
|
||||
@@ -72,19 +57,13 @@ export default {
|
||||
if (period === 'career') {
|
||||
stats = accountInfo[id].statistics.all;
|
||||
} else {
|
||||
const daysAgo = period === 'today' ? 0 : Number(period) - 1;
|
||||
const periodStart = new Date();
|
||||
|
||||
periodStart.setDate(periodStart.getDate() - daysAgo);
|
||||
periodStart.setHours(5, 0, 0, 0);
|
||||
|
||||
stats = sumStats(
|
||||
Object.entries(
|
||||
await getTankStatsOverTime(
|
||||
server,
|
||||
id,
|
||||
periodStart.getTime() / 1000,
|
||||
new Date().getTime() / 1000,
|
||||
getPeriodicStart(period),
|
||||
getPeriodNow(),
|
||||
),
|
||||
).map(([, stats]) => stats),
|
||||
);
|
||||
@@ -115,7 +94,6 @@ export default {
|
||||
'Winrate',
|
||||
`${(100 * (stats.wins / stats.battles)).toFixed(2)}%`,
|
||||
],
|
||||
// ['WN8', stats.wn8.toFixed(0)],
|
||||
[
|
||||
'Survival',
|
||||
`${(100 * (stats.survived_battles / stats.battles)).toFixed(
|
||||
@@ -135,7 +113,6 @@ export default {
|
||||
['Average hits', (stats.hits / stats.battles).toFixed(2)],
|
||||
['Average kills', (stats.frags / stats.battles).toFixed(2)],
|
||||
['Average spots', (stats.spotted / stats.battles).toFixed(2)],
|
||||
// ['Average tier', stats.avg_tier.toFixed(2)],
|
||||
[
|
||||
'Damage ratio',
|
||||
(stats.damage_dealt / stats.damage_received).toFixed(2),
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import markdownEscape from 'markdown-escape';
|
||||
import tanksAutocomplete from '../core/autocomplete/tanks.js';
|
||||
import usernameAutocomplete from '../core/autocomplete/username.js';
|
||||
import getBlitzAccount from '../core/blitz/getBlitzAccount.js';
|
||||
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
|
||||
import { tankopedia } from '../core/blitzstars/tankopedia.js';
|
||||
import getPeriodicStats, {
|
||||
Period,
|
||||
} from '../core/blitztankstats/getPeriodicStats.js';
|
||||
import cleanTable from '../core/interaction/cleanTable.js';
|
||||
import cmdName from '../core/interaction/cmdName.js';
|
||||
import errorEmbed from '../core/interaction/errorEmbed.js';
|
||||
import infoEmbed from '../core/interaction/infoEmbed.js';
|
||||
import addTankChoices from '../core/options/addTankChoices.js';
|
||||
import addUsernameOption from '../core/options/addUsernameOption.js';
|
||||
import { args } from '../core/process/args.js';
|
||||
import { CommandRegistry } from '../events/interactionCreate.js';
|
||||
import { AccountInfo } from '../types/accountInfo.js';
|
||||
import resolveTankName from '../utilities/resolveTankName.js';
|
||||
|
||||
type Periods = '30' | '60' | '90';
|
||||
|
||||
export default {
|
||||
inProduction: true,
|
||||
inDevelopment: false,
|
||||
inPublic: true,
|
||||
|
||||
command: new SlashCommandBuilder()
|
||||
.setName(cmdName('tankstats'))
|
||||
.setDescription('Stats for a tank over a period')
|
||||
.addStringOption(addTankChoices)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('period')
|
||||
.setDescription('The period to get stats for')
|
||||
.setChoices(
|
||||
{ name: '30 Days', value: '30' satisfies Periods },
|
||||
{ name: '60 Days', value: '60' satisfies Periods },
|
||||
{ name: '90 Days', value: '90' satisfies Periods },
|
||||
)
|
||||
.setRequired(true),
|
||||
)
|
||||
.addStringOption(addUsernameOption),
|
||||
|
||||
async execute(interaction) {
|
||||
const name = interaction.options.getString('username')!;
|
||||
const tankId = interaction.options.getString('tank')!;
|
||||
const period = interaction.options.getString('period') as Periods;
|
||||
const blitzAccount = await getBlitzAccount(interaction, name);
|
||||
const { server, id } = blitzAccount;
|
||||
const accountInfo = await getWargamingResponse<AccountInfo>(
|
||||
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${args['wargaming-application-id']}&account_id=${id}`,
|
||||
);
|
||||
const tank = tankopedia[tankId as unknown as number];
|
||||
|
||||
if (tank) {
|
||||
const periodicStats = (await getPeriodicStats(id, Number(period)))
|
||||
.filter((stats) => stats.tank_id === tank.tank_id)
|
||||
.filter((stats) => !stats.isRating);
|
||||
|
||||
function sum(summer: (period: Period) => number) {
|
||||
return periodicStats.reduce(
|
||||
(accumulative, period) => accumulative + summer(period),
|
||||
0,
|
||||
);
|
||||
}
|
||||
function weighted(summer: (period: Period) => number) {
|
||||
return (
|
||||
periodicStats.reduce(
|
||||
(accumulative, period) =>
|
||||
accumulative + summer(period) * period.battles,
|
||||
0,
|
||||
) / sum((period) => period.battles)
|
||||
);
|
||||
}
|
||||
|
||||
interaction.editReply({
|
||||
embeds: [
|
||||
infoEmbed(
|
||||
`${period} day stats for ${markdownEscape(
|
||||
accountInfo[id].nickname,
|
||||
)}'s ${resolveTankName(tank)}`,
|
||||
periodicStats.length > 0
|
||||
? cleanTable([
|
||||
[
|
||||
'Winrate',
|
||||
`${(
|
||||
100 *
|
||||
(sum((a) => a.wins) / sum((a) => a.battles))
|
||||
).toFixed(2)}%`,
|
||||
],
|
||||
[
|
||||
'Survival',
|
||||
`${(
|
||||
100 *
|
||||
(sum((a) => a.survived_battles) / sum((a) => a.battles))
|
||||
).toFixed(2)}%`,
|
||||
],
|
||||
[
|
||||
'Accuracy',
|
||||
`${(
|
||||
100 *
|
||||
(sum((a) => a.hits) / sum((a) => a.shots))
|
||||
).toFixed(2)}%`,
|
||||
],
|
||||
['WN8', weighted((a) => a.wn8).toFixed(0)],
|
||||
[],
|
||||
['Battles', sum((a) => a.battles)],
|
||||
['Wins', sum((a) => a.wins)],
|
||||
['Losses', sum((a) => a.losses)],
|
||||
[],
|
||||
[
|
||||
'Shots per battle',
|
||||
(sum((a) => a.shots) / sum((a) => a.battles)).toFixed(2),
|
||||
],
|
||||
[
|
||||
'Hits per battle',
|
||||
(sum((a) => a.hits) / sum((a) => a.battles)).toFixed(2),
|
||||
],
|
||||
[
|
||||
'Damage per battle',
|
||||
(
|
||||
sum((a) => a.damage_dealt) / sum((a) => a.battles)
|
||||
).toFixed(0),
|
||||
],
|
||||
[
|
||||
'Kills per battle',
|
||||
(sum((a) => a.frags) / sum((a) => a.battles)).toFixed(2),
|
||||
],
|
||||
[
|
||||
'Spots per battle',
|
||||
(sum((a) => a.spotted) / sum((a) => a.battles)).toFixed(2),
|
||||
],
|
||||
[
|
||||
'XP per battle',
|
||||
(sum((a) => a.xp) / sum((a) => a.battles)).toFixed(0),
|
||||
],
|
||||
[],
|
||||
[
|
||||
'Damage ratio',
|
||||
(
|
||||
sum((a) => a.damage_dealt) / sum((a) => a.damage_received)
|
||||
).toFixed(2),
|
||||
],
|
||||
[
|
||||
'Kills to death ratio',
|
||||
(
|
||||
sum((a) => a.frags) /
|
||||
sum((a) => a.battles - a.survived_battles)
|
||||
).toFixed(2),
|
||||
],
|
||||
])
|
||||
: 'No battles were played or recorded during this period.',
|
||||
),
|
||||
],
|
||||
});
|
||||
} else {
|
||||
interaction.editReply({
|
||||
embeds: [
|
||||
errorEmbed(
|
||||
'Tank not found',
|
||||
`"${markdownEscape(tankId)}" does not exist in the game.`,
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
autocomplete: (interaction) => {
|
||||
tanksAutocomplete(interaction);
|
||||
usernameAutocomplete(interaction);
|
||||
},
|
||||
} satisfies CommandRegistry;
|
||||
@@ -0,0 +1,156 @@
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
SlashCommandBuilder,
|
||||
} from 'discord.js';
|
||||
import GenericStats from '../components/GenericStats/index.js';
|
||||
import NoBattlesInPeriod from '../components/NoBattlesInPeriod.js';
|
||||
import PoweredByBlitzStars from '../components/PoweredByBlitzStars.js';
|
||||
import TitleBar from '../components/TitleBar.js';
|
||||
import Wrapper from '../components/Wrapper.js';
|
||||
import { BLITZ_SERVERS } from '../constants/servers.js';
|
||||
import fullBlitzStarsStats from '../core/actions/fullBlitzStarsStats.js';
|
||||
import { supportBlitzStars } from '../core/actions/supportBlitzStars.js';
|
||||
import tanksAutocomplete from '../core/autocomplete/tanks.js';
|
||||
import usernameAutocomplete from '../core/autocomplete/username.js';
|
||||
import getBlitzAccount from '../core/blitz/getBlitzAccount.js';
|
||||
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
|
||||
import resolveTankId from '../core/blitz/resolveTankId.js';
|
||||
import getPeriodNow from '../core/blitzstars/getPeriodNow.js';
|
||||
import getPeriodicStart from '../core/blitzstars/getPeriodStart.js';
|
||||
import getTankStatsOverTime from '../core/blitzstars/getTankStatsOverTime.js';
|
||||
import { tankopedia } from '../core/blitzstars/tankopedia.js';
|
||||
import cmdName from '../core/interaction/cmdName.js';
|
||||
import addStatPeriodChoices, {
|
||||
StatPeriod,
|
||||
statPeriodNames,
|
||||
} from '../core/options/addStatPeriodChoices.js';
|
||||
import addTankChoices from '../core/options/addTankChoices.js';
|
||||
import addUsernameOption from '../core/options/addUsernameOption.js';
|
||||
import { args } from '../core/process/args.js';
|
||||
import render from '../core/ui/render.js';
|
||||
import { CommandRegistry } from '../events/interactionCreate.js';
|
||||
import { AccountInfo, AllStats } from '../types/accountInfo.js';
|
||||
import { TanksStats } from '../types/tanksStats.js';
|
||||
import resolveTankName from '../utilities/resolveTankName.js';
|
||||
|
||||
export default {
|
||||
inProduction: true,
|
||||
inDevelopment: true,
|
||||
inPublic: true,
|
||||
|
||||
command: new SlashCommandBuilder()
|
||||
.setName(cmdName('tankstats'))
|
||||
.setDescription('Stats for a tank over a period')
|
||||
.addStringOption(addTankChoices)
|
||||
.addStringOption(addStatPeriodChoices)
|
||||
.addStringOption(addUsernameOption),
|
||||
|
||||
async execute(interaction) {
|
||||
const tank = await resolveTankId(
|
||||
interaction,
|
||||
interaction.options.getString('tank')!,
|
||||
);
|
||||
const period = interaction.options.getString('period') as StatPeriod;
|
||||
const username = interaction.options.getString('username')!;
|
||||
const blitzAccount = await getBlitzAccount(interaction, username);
|
||||
const { id, server } = blitzAccount;
|
||||
const accountInfo = await getWargamingResponse<AccountInfo>(
|
||||
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${args['wargaming-application-id']}&account_id=${id}`,
|
||||
);
|
||||
const tankStats = await getWargamingResponse<TanksStats>(
|
||||
`https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${args['wargaming-application-id']}&account_id=${id}`,
|
||||
);
|
||||
let stats: AllStats;
|
||||
|
||||
if (period === 'career') {
|
||||
stats = tankStats[id].find(
|
||||
(stats) => stats.tank_id === Number(tank),
|
||||
)!.all;
|
||||
} else {
|
||||
stats = (
|
||||
await getTankStatsOverTime(
|
||||
server,
|
||||
id,
|
||||
getPeriodicStart(period),
|
||||
getPeriodNow(),
|
||||
)
|
||||
)[Number(tank)];
|
||||
}
|
||||
|
||||
const image = await render(
|
||||
<Wrapper>
|
||||
<TitleBar
|
||||
name={accountInfo[id].nickname}
|
||||
nameDiscriminator={`(${resolveTankName(Number(tank))})`}
|
||||
image={tankopedia[Number(tank)].images.normal}
|
||||
description={`${
|
||||
statPeriodNames[period]
|
||||
} • ${new Date().toDateString()} • ${BLITZ_SERVERS[server]}`}
|
||||
/>
|
||||
|
||||
{stats.battles === 0 && <NoBattlesInPeriod />}
|
||||
{stats.battles > 0 && (
|
||||
// TODO: GenericStats generator for AllStats
|
||||
<GenericStats
|
||||
stats={[
|
||||
[
|
||||
'Winrate',
|
||||
`${(100 * (stats.wins / stats.battles)).toFixed(2)}%`,
|
||||
],
|
||||
[
|
||||
'Survival',
|
||||
`${(100 * (stats.survived_battles / stats.battles)).toFixed(
|
||||
2,
|
||||
)}%`,
|
||||
],
|
||||
['Accuracy', `${((stats.hits / stats.shots) * 100).toFixed(2)}%`],
|
||||
['Battles', stats.battles],
|
||||
['Wins', stats.wins],
|
||||
['Losses', stats.losses],
|
||||
[
|
||||
'Average damage',
|
||||
(stats.damage_dealt / stats.battles).toFixed(0),
|
||||
],
|
||||
['Average XP', (stats.xp / stats.battles).toFixed(0)],
|
||||
['Average shots', (stats.shots / stats.battles).toFixed(2)],
|
||||
['Average hits', (stats.hits / stats.battles).toFixed(2)],
|
||||
['Average kills', (stats.frags / stats.battles).toFixed(2)],
|
||||
['Average spots', (stats.spotted / stats.battles).toFixed(2)],
|
||||
[
|
||||
'Damage ratio',
|
||||
(stats.damage_dealt / stats.damage_received).toFixed(2),
|
||||
],
|
||||
[
|
||||
'Kills to death ratio',
|
||||
(
|
||||
stats.frags /
|
||||
(stats.battles - stats.survived_battles)
|
||||
).toFixed(2),
|
||||
],
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
||||
<PoweredByBlitzStars />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
fullBlitzStarsStats(server, accountInfo[id].nickname),
|
||||
supportBlitzStars,
|
||||
);
|
||||
|
||||
await interaction.editReply({
|
||||
files: [image],
|
||||
components: [actionRow],
|
||||
});
|
||||
|
||||
console.log(`Showing stats for ${accountInfo[id].nickname}`);
|
||||
},
|
||||
|
||||
autocomplete: (interaction) => {
|
||||
tanksAutocomplete(interaction);
|
||||
usernameAutocomplete(interaction);
|
||||
},
|
||||
} satisfies CommandRegistry;
|
||||
@@ -30,7 +30,7 @@ import resolveTankName from '../utilities/resolveTankName.js';
|
||||
|
||||
export default {
|
||||
inProduction: true,
|
||||
inDevelopment: true,
|
||||
inDevelopment: false,
|
||||
inPublic: true,
|
||||
|
||||
command: new SlashCommandBuilder()
|
||||
@@ -82,14 +82,7 @@ export default {
|
||||
return (
|
||||
<Breakdown.Row
|
||||
key={tankId}
|
||||
name={
|
||||
tankId === '0'
|
||||
? 'Today'
|
||||
: resolveTankName({
|
||||
tank_id: tankId as unknown as number,
|
||||
name: tankopedia[tankId as unknown as number].name,
|
||||
})
|
||||
}
|
||||
name={tankId === '0' ? 'Today' : resolveTankName(Number(tankId))}
|
||||
winrate={tankStats.wins / tankStats.battles}
|
||||
careerWinrate={career.wins / career.battles}
|
||||
WN8={-Infinity}
|
||||
|
||||
Reference in new issue
Block a user