mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +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
|
: tanks
|
||||||
.map(
|
.map(
|
||||||
(tank) =>
|
(tank) =>
|
||||||
`${TANK_TYPE_EMOJIS[tank.type]} ${resolveTankName(
|
`${TANK_TYPE_EMOJIS[tank.type]} ${markdownEscape(
|
||||||
tank,
|
resolveTankName(tank.tank_id),
|
||||||
)} ${tank.is_premium ? '⭐' : ''}${
|
)} ${tank.is_premium ? '⭐' : ''}${
|
||||||
COMP_TANKS.includes(tank.tank_id) ? '🏆' : ''
|
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 getBlitzAccount from '../core/blitz/getBlitzAccount.js';
|
||||||
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
|
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
|
||||||
import sumStats from '../core/blitz/sumStats.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 getTankStatsOverTime from '../core/blitzstars/getTankStatsOverTime.js';
|
||||||
import cmdName from '../core/interaction/cmdName.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 addUsernameOption from '../core/options/addUsernameOption.js';
|
||||||
import { args } from '../core/process/args.js';
|
import { args } from '../core/process/args.js';
|
||||||
import render from '../core/ui/render.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 { AccountInfo, AllStats } from '../types/accountInfo.js';
|
||||||
import { PlayerClanData } from '../types/playerClanData.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 {
|
export default {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: true,
|
inDevelopment: false,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
.setName(cmdName('stats'))
|
.setName(cmdName('stats'))
|
||||||
.setDescription("Gets the user's in-game statistics")
|
.setDescription("Gets the user's in-game statistics")
|
||||||
.addStringOption((option) =>
|
.addStringOption(addStatPeriodChoices)
|
||||||
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(addUsernameOption),
|
.addStringOption(addUsernameOption),
|
||||||
|
|
||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
@@ -72,19 +57,13 @@ export default {
|
|||||||
if (period === 'career') {
|
if (period === 'career') {
|
||||||
stats = accountInfo[id].statistics.all;
|
stats = accountInfo[id].statistics.all;
|
||||||
} else {
|
} 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(
|
stats = sumStats(
|
||||||
Object.entries(
|
Object.entries(
|
||||||
await getTankStatsOverTime(
|
await getTankStatsOverTime(
|
||||||
server,
|
server,
|
||||||
id,
|
id,
|
||||||
periodStart.getTime() / 1000,
|
getPeriodicStart(period),
|
||||||
new Date().getTime() / 1000,
|
getPeriodNow(),
|
||||||
),
|
),
|
||||||
).map(([, stats]) => stats),
|
).map(([, stats]) => stats),
|
||||||
);
|
);
|
||||||
@@ -115,7 +94,6 @@ export default {
|
|||||||
'Winrate',
|
'Winrate',
|
||||||
`${(100 * (stats.wins / stats.battles)).toFixed(2)}%`,
|
`${(100 * (stats.wins / stats.battles)).toFixed(2)}%`,
|
||||||
],
|
],
|
||||||
// ['WN8', stats.wn8.toFixed(0)],
|
|
||||||
[
|
[
|
||||||
'Survival',
|
'Survival',
|
||||||
`${(100 * (stats.survived_battles / stats.battles)).toFixed(
|
`${(100 * (stats.survived_battles / stats.battles)).toFixed(
|
||||||
@@ -135,7 +113,6 @@ export default {
|
|||||||
['Average hits', (stats.hits / stats.battles).toFixed(2)],
|
['Average hits', (stats.hits / stats.battles).toFixed(2)],
|
||||||
['Average kills', (stats.frags / stats.battles).toFixed(2)],
|
['Average kills', (stats.frags / stats.battles).toFixed(2)],
|
||||||
['Average spots', (stats.spotted / stats.battles).toFixed(2)],
|
['Average spots', (stats.spotted / stats.battles).toFixed(2)],
|
||||||
// ['Average tier', stats.avg_tier.toFixed(2)],
|
|
||||||
[
|
[
|
||||||
'Damage ratio',
|
'Damage ratio',
|
||||||
(stats.damage_dealt / stats.damage_received).toFixed(2),
|
(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 {
|
export default {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: true,
|
inDevelopment: false,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -82,14 +82,7 @@ export default {
|
|||||||
return (
|
return (
|
||||||
<Breakdown.Row
|
<Breakdown.Row
|
||||||
key={tankId}
|
key={tankId}
|
||||||
name={
|
name={tankId === '0' ? 'Today' : resolveTankName(Number(tankId))}
|
||||||
tankId === '0'
|
|
||||||
? 'Today'
|
|
||||||
: resolveTankName({
|
|
||||||
tank_id: tankId as unknown as number,
|
|
||||||
name: tankopedia[tankId as unknown as number].name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
winrate={tankStats.wins / tankStats.battles}
|
winrate={tankStats.wins / tankStats.battles}
|
||||||
careerWinrate={career.wins / career.battles}
|
careerWinrate={career.wins / career.battles}
|
||||||
WN8={-Infinity}
|
WN8={-Infinity}
|
||||||
|
|||||||
+37
-11
@@ -17,29 +17,55 @@ export default function TitleBar({
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', gap: 8 }}>
|
<div style={{ display: 'flex', gap: 8, flex: 1 }}>
|
||||||
{image && <img style={{ width: 64, height: 64 }} src={image} />}
|
{image && (
|
||||||
|
<img
|
||||||
|
style={{ width: 64, height: 64, objectFit: 'contain' }}
|
||||||
|
src={image}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
<div
|
||||||
<span
|
style={{ display: 'flex', flexDirection: 'column', gap: 8, flex: 1 }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
style={{
|
style={{
|
||||||
fontSize: 32,
|
display: 'flex',
|
||||||
color: theme.colors.textHighContrast,
|
|
||||||
fontWeight: 900,
|
|
||||||
gap: 4,
|
gap: 4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{name}
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 32,
|
||||||
|
color: theme.colors.textHighContrast,
|
||||||
|
fontWeight: 900,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
{nameDiscriminator && (
|
{nameDiscriminator && (
|
||||||
<span style={{ color: theme.colors.textLowContrast }}>
|
<span
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 32,
|
||||||
|
color: theme.colors.textLowContrast,
|
||||||
|
fontWeight: 900,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
{nameDiscriminator}
|
{nameDiscriminator}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</div>
|
||||||
|
|
||||||
<span style={{ color: theme.colors.textLowContrast, fontSize: 16 }}>
|
<span style={{ color: theme.colors.textLowContrast, fontSize: 16 }}>
|
||||||
{description}
|
{description}
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ export default async function tanksAutocomplete(
|
|||||||
? go(focusedOption.value, tanks, { keys: ['name'], limit: 10 }).map(
|
? go(focusedOption.value, tanks, { keys: ['name'], limit: 10 }).map(
|
||||||
(item) =>
|
(item) =>
|
||||||
({
|
({
|
||||||
name: item.obj.name ? item.obj.name : resolveTankName(item.obj),
|
name: item.obj.name
|
||||||
|
? item.obj.name
|
||||||
|
: resolveTankName(item.obj.tank_id),
|
||||||
value: `${item.obj.tank_id}`,
|
value: `${item.obj.tank_id}`,
|
||||||
} satisfies ApplicationCommandOptionChoiceData<string>),
|
} satisfies ApplicationCommandOptionChoiceData<string>),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
|
||||||
|
import { go } from 'fuzzysort';
|
||||||
|
import { tankopedia, tanks } from '../blitzstars/tankopedia.js';
|
||||||
|
import errorEmbed from '../interaction/errorEmbed.js';
|
||||||
|
|
||||||
|
export default async function resolveTankId(
|
||||||
|
interaction: ChatInputCommandInteraction<CacheType>,
|
||||||
|
tank: string,
|
||||||
|
) {
|
||||||
|
const number = Number(tank);
|
||||||
|
|
||||||
|
if (Number.isNaN(number)) {
|
||||||
|
const searchResult = go(tank, tanks, { keys: ['name'], limit: 1 });
|
||||||
|
|
||||||
|
if (searchResult.length === 0) {
|
||||||
|
await interaction.editReply({
|
||||||
|
embeds: [
|
||||||
|
errorEmbed(
|
||||||
|
'Tank not found',
|
||||||
|
`Could not find tank by the name "${tank}".`,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error(`Tank not found by name "${tank}"`);
|
||||||
|
} else {
|
||||||
|
return searchResult[0].obj.tank_id;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (tankopedia[number]) {
|
||||||
|
return number;
|
||||||
|
} else {
|
||||||
|
await interaction.editReply({
|
||||||
|
embeds: [
|
||||||
|
errorEmbed(
|
||||||
|
'Tank not found',
|
||||||
|
`Could not find tank by the ID "${number}".`,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error(`Tank not found by ID "${number}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export default function getPeriodNow() {
|
||||||
|
return new Date().getTime() / 1000;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { StatPeriod } from '../options/addStatPeriodChoices.js';
|
||||||
|
|
||||||
|
export default function getPeriodicStart(period: StatPeriod) {
|
||||||
|
if (period === 'career') {
|
||||||
|
return -Infinity;
|
||||||
|
} else {
|
||||||
|
const daysAgo = period === 'today' ? 0 : Number(period) - 1;
|
||||||
|
const periodStart = new Date();
|
||||||
|
|
||||||
|
periodStart.setDate(periodStart.getDate() - daysAgo);
|
||||||
|
periodStart.setHours(5, 0, 0, 0);
|
||||||
|
|
||||||
|
return periodStart.getTime() / 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
export interface Period {
|
|
||||||
statsId: number;
|
|
||||||
account_id: number;
|
|
||||||
tank_id: number;
|
|
||||||
recordDate: string;
|
|
||||||
last_battle_time: number;
|
|
||||||
fromDate: string;
|
|
||||||
prior_battle_time: number;
|
|
||||||
battles: number;
|
|
||||||
wins: number;
|
|
||||||
losses: number;
|
|
||||||
spotted: number;
|
|
||||||
shots: number;
|
|
||||||
hits: number;
|
|
||||||
xp: number;
|
|
||||||
battle_life_time: number;
|
|
||||||
frags: number;
|
|
||||||
max_frags: number;
|
|
||||||
damage_dealt: number;
|
|
||||||
damage_received: number;
|
|
||||||
survived_battles: number;
|
|
||||||
win_and_survived: number;
|
|
||||||
capture_points: number;
|
|
||||||
dropped_capture_points: number;
|
|
||||||
killStealerValue: number;
|
|
||||||
isRating: boolean;
|
|
||||||
wn8: number;
|
|
||||||
region: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function getPeriodicStats(id: number, period: number) {
|
|
||||||
const response = await fetch(
|
|
||||||
`https://blitztankstats.com/api/Stats/period/${id}/${period}`,
|
|
||||||
);
|
|
||||||
return (await response.json()) as Period[];
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { SlashCommandStringOption } from 'discord.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 function addStatPeriodChoices(option: SlashCommandStringOption) {
|
||||||
|
return option
|
||||||
|
.setName('period')
|
||||||
|
.setDescription('The period to get stats for')
|
||||||
|
.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);
|
||||||
|
}
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import markdownEscape from 'markdown-escape';
|
import { tankopedia } from '../core/blitzstars/tankopedia.js';
|
||||||
|
|
||||||
export default function resolveTankName(tank: {
|
export default function resolveTankName(tankId: number) {
|
||||||
tank_id: number;
|
const tankopediaName = tankopedia[tankId].name;
|
||||||
name?: string;
|
return tankopediaName ? tankopediaName : `Unknown Tank ${tankId}`;
|
||||||
}) {
|
|
||||||
return tank.name ? markdownEscape(tank.name) : `Unknown Tank ${tank.tank_id}`;
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user