feat: updated full stats

This commit is contained in:
TrèsAbhi committed 2023-10-11 18:57:51 -05:00
1 parent d2668974f7
commit d062a0da1b
7 files changed
+99 -246

No files matched your search

+69 -67
View File
@@ -1,23 +1,68 @@
import { SlashCommandBuilder } from 'discord.js';
import { TreeTypeString } from '../components/Tanks';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import resolveTankId from '../core/blitz/resolveTankId';
import GenericAllStats from '../components/GenericAllStats';
import NoData, { NoDataType } from '../components/NoData';
import TierWeights from '../components/TierWeights';
import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper';
import { StatFilters, filterStats } from '../core/blitz/filterStats';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanLogo } from '../core/blitz/getClanLogo';
import { playerClanInfo } from '../core/blitz/playerClanInfo';
import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription';
import { getTierWeights } from '../core/blitzkrieg/getTierWeights';
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import getDiffedTankStats from '../core/blitzstars/getDiffedTankStats';
import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteTanks from '../core/discord/autocompleteTanks';
import autocompleteUsername from '../core/discord/autocompleteUsername';
import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams';
import { getFiltersFromButton } from '../core/discord/getFiltersFromButton';
import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand';
import interactionToURL from '../core/discord/interactionToURL';
import linkButton from '../core/discord/linkButton';
import primaryButton from '../core/discord/primaryButton';
import { refreshButton } from '../core/discord/refreshButton';
import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
import resolvePeriodFromCommand, {
ResolvedPeriod,
} from '../core/discord/resolvePeriodFromCommand';
import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import resolvePlayerFromCommand, {
ResolvedPlayer,
} from '../core/discord/resolvePlayerFromCommand';
import { CommandRegistryRaw } from '../events/interactionCreate';
import fullStats from '../renderers/fullStats';
import { MultiTankFilters, StatType } from '../renderers/stats';
import { AccountInfo } from '../types/accountInfo';
async function render(
{ region, id }: ResolvedPlayer,
{ start, end, name }: ResolvedPeriod,
filters: StatFilters,
) {
const nickname = (await getAccountInfo(region, id))[id].nickname;
const clan = (await playerClanInfo(region, id))[id]?.clan;
const clanImage = clan ? getClanLogo(clan.emblem_set_id) : undefined;
const diffedTankStats = await getDiffedTankStats(region, id, start, end);
const { stats, supplementary, filteredOrder } = await filterStats(
diffedTankStats,
filters,
);
const filterDescriptions = await filtersToDescription(filters);
const tierWeights = await getTierWeights(diffedTankStats.diff, filteredOrder);
return (
<Wrapper>
<TitleBar
name={nickname}
image={clanImage}
description={`${name} • ${filterDescriptions}`}
/>
{stats.battles === 0 && <NoData type={NoDataType.BattlesInPeriod} />}
{stats.battles > 0 && <TierWeights weights={tierWeights!} />}
{stats.battles > 0 && (
<GenericAllStats stats={stats} supplementaryStats={supplementary} />
)}
</Wrapper>
);
}
export const fullStatsCommand = new Promise<CommandRegistryRaw>(
async (resolve) => {
@@ -35,59 +80,23 @@ export const fullStatsCommand = new Promise<CommandRegistryRaw>(
command,
async handler(interaction) {
const commandGroup = interaction.options.getSubcommandGroup(
true,
) as StatType;
const player = await resolvePlayerFromCommand(interaction);
const resolvedPlayer = await resolvePlayerFromCommand(interaction);
const resolvedPeriod = resolvePeriodFromCommand(
player.region,
resolvedPlayer.region,
interaction,
);
const period = interaction.options.getString('period');
const tankIdRaw = interaction.options.getString('tank')!;
const tankId =
commandGroup === 'tank' ? await resolveTankId(tankIdRaw) : null;
const start = interaction.options.getInteger('start');
const end = interaction.options.getInteger('end');
const filters = getFiltersFromCommand(interaction);
const path = interactionToURL(interaction, {
...player,
tankId,
start,
end,
period,
...resolvedPlayer,
...getCustomPeriodParams(interaction),
...filters,
});
const { nickname } = (
await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${player.region}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${player.id}`,
)
)[player.id];
const tierOption = interaction.options.getString('tier');
return [
await fullStats(
commandGroup,
resolvedPeriod,
player,
commandGroup === 'tank'
? tankId
: commandGroup === 'multi-tank'
? ({
'tank-type':
interaction.options.getString('tank-type') ?? undefined,
nation: interaction.options.getString('nation') ?? undefined,
tier: tierOption ? parseInt(tierOption) : undefined,
'tree-type': (interaction.options.getString('tree-type') ??
undefined) as TreeTypeString | undefined,
} satisfies Partial<MultiTankFilters>)
: null,
),
primaryButton(path, 'Refresh'),
linkButton(
`https://www.blitzstars.com/player/${player.region}/${nickname}`,
'BlitzStars',
),
];
return Promise.all([
render(resolvedPlayer, resolvedPeriod, filters),
refreshButton(interaction, path),
getBlitzStarsLinkButton(resolvedPlayer.region, resolvedPlayer.id),
]);
},
autocomplete: (interaction) => {
@@ -96,18 +105,11 @@ export const fullStatsCommand = new Promise<CommandRegistryRaw>(
},
async button(interaction) {
const url = new URL(`https://example.com/${interaction.customId}`);
const path = url.pathname.split('/').filter(Boolean);
const commandGroup = path[1] as StatType;
const player = await resolvePlayerFromButton(interaction);
const period = resolvePeriodFromButton(player.region, interaction);
const filters = getFiltersFromButton(interaction);
return await fullStats(
commandGroup,
period,
player,
parseInt(url.searchParams.get('tankId')!),
);
return await render(player, period, filters);
},
});
},
+5 -4
View File
@@ -29,7 +29,7 @@ import resolvePlayerFromCommand, {
} from '../core/discord/resolvePlayerFromCommand';
import { CommandRegistryRaw } from '../events/interactionCreate';
export default async function render(
async function render(
{ region, id }: ResolvedPlayer,
{ start, end, name }: ResolvedPeriod,
filters: StatFilters,
@@ -39,17 +39,18 @@ export default async function render(
const clanImage = clan ? getClanLogo(clan.emblem_set_id) : undefined;
const diffedTankStats = await getDiffedTankStats(region, id, start, end);
const { stats, supplementary } = await filterStats(diffedTankStats, filters);
const filterDescriptions = await filtersToDescription(filters);
return (
<Wrapper>
<TitleBar
name={nickname}
image={clanImage}
description={`${name} • ${await filtersToDescription(filters)}`}
description={`${name} • ${filterDescriptions}`}
/>
{!stats?.battles && <NoData type={NoDataType.BattlesInPeriod} />}
{stats?.battles > 0 && (
{!stats.battles && <NoData type={NoDataType.BattlesInPeriod} />}
{stats.battles > 0 && (
<AllStatsOverview stats={stats} supplementaryStats={supplementary} />
)}
</Wrapper>
@@ -44,6 +44,7 @@ export function Card({ items, title }: CardProps) {
>
{items.map((item) => (
<div
key={item.label}
style={{
display: 'flex',
gap: 8,
+1 -1
View File
@@ -75,5 +75,5 @@ export async function filterStats(
) / battlesOfTanksWithTankopediaEntry,
};
return { stats, supplementary };
return { stats, supplementary, filteredOrder };
}
+1 -1
View File
@@ -9,7 +9,7 @@ export interface TankopediaEntry {
nation: string;
is_premium: boolean;
is_collectible: boolean;
tier: number;
tier: Tier;
cost: { price_credit: number; price_gold: number };
images: { preview: string; normal: string };
tank_id: number;
+22
View File
@@ -0,0 +1,22 @@
import { TierWeightsRecord } from '../../components/TierWeights';
import { AllStats } from '../../types/accountInfo';
import { tankopedia } from '../blitz/tankopedia';
export async function getTierWeights(
stats: Record<number, AllStats>,
order: number[] = Object.keys(stats).map(parseInt),
) {
const awaitedTankopedia = await tankopedia;
const tierWeights: TierWeightsRecord = {};
order.map((id) => {
const entry = awaitedTankopedia[id];
if (!entry) return;
if (!tierWeights[entry.tier]) tierWeights[entry.tier] = 0;
tierWeights[entry.tier]! += stats[id].battles;
});
return tierWeights;
}
-173
View File
@@ -1,173 +0,0 @@
import GenericAllStats from '../components/GenericAllStats';
import NoData, { NoDataType } from '../components/NoData';
import TierWeights, { TierWeightsRecord } from '../components/TierWeights';
import TitleBar from '../components/TitleBar';
import Wrapper from '../components/Wrapper';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
import calculateWN8 from '../core/blitz/calculateWN8';
import getWargamingResponse from '../core/blitz/getWargamingResponse';
import resolveTankName from '../core/blitz/resolveTankName';
import sumStats from '../core/blitz/sumStats';
import { Tier, tankopedia } from '../core/blitz/tankopedia';
import getDiffedTankStats from '../core/blitzstars/getDiffedTankStats';
import { tankAverages } from '../core/blitzstars/tankAverages';
import { ResolvedPeriod } from '../core/discord/resolvePeriodFromCommand';
import { ResolvedPlayer } from '../core/discord/resolvePlayerFromCommand';
import {
AccountInfo,
AllStats,
SupplementaryStats,
} from '../types/accountInfo';
import { PlayerClanInfo } from '../types/playerClanData';
import { MultiTankFilters, StatType } from './stats';
export default async function fullStats<Type extends StatType>(
type: Type,
{ start, end, name: statsName }: ResolvedPeriod,
{ region: server, id }: ResolvedPlayer,
filters: Type extends 'tank'
? number
: Type extends 'multi-tank'
? Partial<MultiTankFilters>
: null,
) {
let nameDiscriminator: string | undefined;
let image: string | undefined;
if (type === 'player' || type === 'multi-tank') {
const clan = (
await getWargamingResponse<PlayerClanInfo>(
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`,
)
)[id]?.clan;
if (clan) nameDiscriminator = `[${clan.tag}]`;
image = clan
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
: undefined;
} else if (type === 'tank') {
nameDiscriminator = `(${await resolveTankName(filters as number)})`;
image = (await tankopedia)[filters as number]?.images.normal;
}
const { diff: diffed, order } = await getDiffedTankStats(
server,
id,
start,
end,
);
const awaitedTankopedia = await tankopedia;
let stats: AllStats | undefined;
let supplementaryStats: SupplementaryStats;
let tierWeights: TierWeightsRecord;
if (type === 'player' || type === 'multi-tank') {
const typedFilters = filters as Partial<MultiTankFilters> | null;
const filteredOrder = order.filter(
(id) =>
typedFilters === null ||
((typedFilters.nation === undefined ||
awaitedTankopedia[id]?.nation === typedFilters.nation) &&
(typedFilters['tank-type'] === undefined ||
awaitedTankopedia[id]?.type === typedFilters['tank-type']) &&
(typedFilters.tier === undefined ||
awaitedTankopedia[id]?.tier === typedFilters.tier) &&
(typedFilters['tree-type'] === undefined ||
(typedFilters['tree-type'] === 'collector' &&
awaitedTankopedia[id]?.is_collectible) ||
(typedFilters['tree-type'] === 'premium' &&
awaitedTankopedia[id]?.is_premium) ||
(typedFilters['tree-type'] === 'techtree' &&
!awaitedTankopedia[id]?.is_collectible &&
!awaitedTankopedia[id]?.is_premium))),
);
const awaitedTankAverages = await tankAverages;
stats = sumStats(filteredOrder.map((id) => diffed[id]));
const battlesOfTanksWithAverages = filteredOrder.reduce<number>(
(accumulator, id) =>
awaitedTankAverages[id]
? accumulator + diffed[id].battles
: accumulator,
0,
);
const battlesOfTanksWithTankopediaEntry = filteredOrder.reduce<number>(
(accumulator, id) =>
awaitedTankopedia[id] ? accumulator + diffed[id].battles : accumulator,
0,
);
supplementaryStats = {
WN8:
filteredOrder.reduce<number>(
(accumulator, id) =>
awaitedTankAverages[id]
? accumulator +
calculateWN8(awaitedTankAverages[id].all, diffed[id]) *
diffed[id].battles
: accumulator,
0,
) / battlesOfTanksWithAverages,
tier:
filteredOrder.reduce<number>(
(accumulator, id) =>
awaitedTankopedia[id]
? accumulator + awaitedTankopedia[id]!.tier * diffed[id].battles
: accumulator,
0,
) / battlesOfTanksWithTankopediaEntry,
};
tierWeights = filteredOrder.reduce<TierWeightsRecord>((accumulator, id) => {
const tankopediaEntry = awaitedTankopedia[id];
if (!tankopediaEntry) return accumulator;
const tier = tankopediaEntry.tier as Tier;
if (accumulator[tier]) {
accumulator[tier]! += diffed[id].battles;
} else {
accumulator[tier] = diffed[id].battles;
}
return accumulator;
}, {});
} else {
stats = diffed[(filters as number)!];
supplementaryStats = {
WN8: stats
? calculateWN8(
(await tankAverages)[(filters as number)!].all,
diffed[(filters as number)!],
)
: undefined,
tier: (await tankopedia)[(filters as number)!]?.tier,
};
}
const accountInfo = await getWargamingResponse<AccountInfo>(
`https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
return (
<Wrapper>
<TitleBar
name={accountInfo[id].nickname}
nameDiscriminator={nameDiscriminator}
image={image}
description={`${statsName} • ${new Date().toDateString()}`}
/>
{stats.battles === 0 && <NoData type={NoDataType.BattlesInPeriod} />}
{stats.battles > 0 && type === 'player' && (
<TierWeights weights={tierWeights!} />
)}
{stats.battles > 0 && (
<GenericAllStats
stats={stats}
supplementaryStats={supplementaryStats}
/>
)}
</Wrapper>
);
}