From 909cabd51759815b50f76fced40ec3d1f1242dc9 Mon Sep 17 00:00:00 2001 From: tresabhi Date: Fri, 4 Aug 2023 19:49:40 +0000 Subject: [PATCH] feat: filter functionality --- src/commands/stats.tsx | 33 +++- src/components/Tanks/components/Item.tsx | 18 ++- src/core/blitz/getTreeType.ts | 8 +- .../discord/addStatTypeSubCommandGroups.ts | 20 ++- src/renderers/evolution.tsx | 2 +- src/renderers/fullStats.tsx | 3 +- src/renderers/stats.tsx | 150 ++++++++++-------- 7 files changed, 143 insertions(+), 91 deletions(-) diff --git a/src/commands/stats.tsx b/src/commands/stats.tsx index c4de71558..b66eb48cf 100644 --- a/src/commands/stats.tsx +++ b/src/commands/stats.tsx @@ -1,4 +1,5 @@ import { SlashCommandBuilder } from 'discord.js'; +import { TreeTypeString } from '../components/Tanks'; import { CYCLIC_API } from '../constants/cyclic'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; import resolveTankId from '../core/blitz/resolveTankId'; @@ -14,8 +15,7 @@ import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import { secrets } from '../core/node/secrets'; import { CommandRegistryRaw } from '../events/interactionCreate'; -import { StatType } from '../renderers/fullStats'; -import stats from '../renderers/stats'; +import stats, { MultiTankFilters, StatType } from '../renderers/stats'; import { AccountInfo } from '../types/accountInfo'; export const statsCommand = new Promise(async (resolve) => { @@ -61,7 +61,23 @@ export const statsCommand = new Promise(async (resolve) => { )[player.id]; return [ - await stats(commandGroup, resolvedPeriod, player, tankId), + await stats( + commandGroup, + resolvedPeriod, + player, + commandGroup === 'tank' + ? tankId + : commandGroup === 'multi-tank' + ? ({ + 'tank-type': + interaction.options.getString('tank-type') ?? undefined, + nation: interaction.options.getString('nation') ?? undefined, + tier: interaction.options.getInteger('tier') ?? undefined, + 'tree-type': (interaction.options.getString('tree-type') ?? + undefined) as TreeTypeString | undefined, + } satisfies Partial) + : null, + ), primaryButton(path, 'Refresh'), linkButton(`${CYCLIC_API}/${path}`, 'Embed'), linkButton( @@ -87,7 +103,16 @@ export const statsCommand = new Promise(async (resolve) => { commandGroup, period, player, - parseInt(url.searchParams.get('tankId')!), + commandGroup === 'tank' + ? parseInt(url.searchParams.get('tankId')!) + : commandGroup === 'multi-tank' + ? ({ + 'tank-type': url.searchParams.get('tank-type')!, + nation: url.searchParams.get('nation')!, + tier: parseInt(url.searchParams.get('tier')!), + 'tree-type': url.searchParams.get('tree-type') as TreeTypeString, + } satisfies MultiTankFilters) + : null, ); }, }); diff --git a/src/components/Tanks/components/Item.tsx b/src/components/Tanks/components/Item.tsx index d7d2cd546..9d9b52ef7 100644 --- a/src/components/Tanks/components/Item.tsx +++ b/src/components/Tanks/components/Item.tsx @@ -5,29 +5,31 @@ import { } from '../../../core/blitz/tankopedia'; import { theme } from '../../../stitches.config'; -export enum TreeType { +export enum TreeTypeEnum { TechTree, Premium, Collector, } +export type TreeTypeString = 'techtree' | 'premium' | 'collector'; + export interface ItemProps { image?: string; tankType?: string; name: string; - treeType: TreeType; + treeType: TreeTypeEnum; } const TREE_TYPE_COLOR = { - [TreeType.TechTree]: '', - [TreeType.Premium]: '_amber', - [TreeType.Collector]: '_blue', + [TreeTypeEnum.TechTree]: '', + [TreeTypeEnum.Premium]: '_amber', + [TreeTypeEnum.Collector]: '_blue', } as const; const TREE_TYPE_ICONS = { - [TreeType.TechTree]: TANK_ICONS, - [TreeType.Premium]: TANK_ICONS_PREMIUM, - [TreeType.Collector]: TANK_ICONS_COLLECTOR, + [TreeTypeEnum.TechTree]: TANK_ICONS, + [TreeTypeEnum.Premium]: TANK_ICONS_PREMIUM, + [TreeTypeEnum.Collector]: TANK_ICONS_COLLECTOR, }; export function Item({ image, tankType, name, treeType }: ItemProps) { diff --git a/src/core/blitz/getTreeType.ts b/src/core/blitz/getTreeType.ts index bdbf303be..5c5e4f1ec 100644 --- a/src/core/blitz/getTreeType.ts +++ b/src/core/blitz/getTreeType.ts @@ -1,11 +1,11 @@ -import { TreeType } from '../../components/Tanks'; +import { TreeTypeEnum } from '../../components/Tanks'; import { tankopedia } from './tankopedia'; export default async function getTreeType(id: number) { const entry = (await tankopedia)[id]; - if (entry?.is_collectible) return TreeType.Collector; - if (entry?.is_premium) return TreeType.Premium; + if (entry?.is_collectible) return TreeTypeEnum.Collector; + if (entry?.is_premium) return TreeTypeEnum.Premium; - return TreeType.TechTree; + return TreeTypeEnum.TechTree; } diff --git a/src/core/discord/addStatTypeSubCommandGroups.ts b/src/core/discord/addStatTypeSubCommandGroups.ts index e9e5dbb01..e182d0ad8 100644 --- a/src/core/discord/addStatTypeSubCommandGroups.ts +++ b/src/core/discord/addStatTypeSubCommandGroups.ts @@ -1,4 +1,6 @@ import { SlashCommandBuilder } from 'discord.js'; +import { TreeTypeString } from '../../components/Tanks'; +import { StatType } from '../../renderers/stats'; import { tankopediaInfo } from '../blitz/tankopedia'; import addPeriodSubCommands from './addPeriodSubCommands'; import addTankChoices from './addTankChoices'; @@ -14,7 +16,7 @@ export default async function addStatTypeSubCommandGroups( addPeriodSubCommands(option, (option) => option.addStringOption(addUsernameChoices), ) - .setName('player') + .setName('player' satisfies StatType) .setDescription('Player statistics'), ) .addSubcommandGroup((option) => @@ -23,7 +25,7 @@ export default async function addStatTypeSubCommandGroups( .addStringOption(addTankChoices) .addStringOption(addUsernameChoices), ) - .setName('tank') + .setName('tank' satisfies StatType) .setDescription('Tank statistics'), ) .addSubcommandGroup((option) => @@ -69,14 +71,20 @@ export default async function addStatTypeSubCommandGroups( .setName('tree-type') .setDescription('Tech tree type') .addChoices( - { name: 'Tech Tree', value: 'techtree' }, - { name: 'Premium', value: 'premium' }, - { name: 'Collector', value: 'collector' }, + { + name: 'Tech Tree', + value: 'techtree' satisfies TreeTypeString, + }, + { name: 'Premium', value: 'premium' satisfies TreeTypeString }, + { + name: 'Collector', + value: 'collector' satisfies TreeTypeString, + }, ) .setRequired(false), ), ) - .setName('multi-tank') + .setName('multi-tank' satisfies StatType) .setDescription('Multiple tanks'), ); } diff --git a/src/renderers/evolution.tsx b/src/renderers/evolution.tsx index 1267cf92c..70d4ab111 100644 --- a/src/renderers/evolution.tsx +++ b/src/renderers/evolution.tsx @@ -15,7 +15,7 @@ import { secrets } from '../core/node/secrets'; import { AccountInfo } from '../types/accountInfo'; import { Histories } from '../types/histories'; import { PlayerClanData } from '../types/playerClanData'; -import { StatType } from './fullStats'; +import { StatType } from './stats'; export default async function evolution( type: Type, diff --git a/src/renderers/fullStats.tsx b/src/renderers/fullStats.tsx index c6fc3a6d2..da8e26853 100644 --- a/src/renderers/fullStats.tsx +++ b/src/renderers/fullStats.tsx @@ -22,8 +22,7 @@ import { } from '../types/accountInfo'; import { PlayerClanData } from '../types/playerClanData'; import { PossiblyPromise } from '../types/possiblyPromise'; - -export type StatType = 'player' | 'tank'; +import { StatType } from './stats'; export default async function fullStats( type: Type, diff --git a/src/renderers/stats.tsx b/src/renderers/stats.tsx index 8c0287cc4..40e0dacda 100644 --- a/src/renderers/stats.tsx +++ b/src/renderers/stats.tsx @@ -1,6 +1,7 @@ import AllStatsOverview from '../components/AllStatsOverview'; import NoData, { NoDataType } from '../components/NoData'; import PoweredBy, { PoweredByType } from '../components/PoweredBy'; +import { TreeTypeString } from '../components/Tanks'; import { TierWeightsRecord } from '../components/TierWeights'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; @@ -22,15 +23,25 @@ import { SupplementaryStats, } from '../types/accountInfo'; import { PlayerClanData } from '../types/playerClanData'; -import { PossiblyPromise } from '../types/possiblyPromise'; -export type StatType = 'player' | 'tank'; +export type StatType = 'player' | 'tank' | 'multi-tank'; + +export interface MultiTankFilters { + nation: string; + 'tank-type': string; + tier: number; + 'tree-type': TreeTypeString; +} export default async function stats( type: Type, { start, end, statsName }: ResolvedPeriod, { region: server, id }: ResolvedPlayer, - tankId: Type extends 'tank' ? number : null, + filters: Type extends 'tank' + ? number + : Type extends 'multi-tank' + ? Partial + : null, naked = false, ) { let nameDiscriminator: string | undefined; @@ -48,91 +59,98 @@ export default async function stats( ? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png` : undefined; } else if (type === 'tank') { - nameDiscriminator = `(${await resolveTankName(tankId!)})`; - image = (await tankopedia)[tankId!]?.images.normal; + nameDiscriminator = `(${await resolveTankName(filters as number)})`; + image = (await tankopedia)[filters as number]?.images.normal; + } else { } - const { diffed } = await getDiffedTankStats(server, id, start, end); + const { 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') { - const entries = Object.entries(diffed); - stats = sumStats(entries.map(([, stats]) => stats)); - const battlesOfTanksWithAverages = await entries.reduce< - PossiblyPromise - >(async (accumulator, [tankIdString, stats]) => { - const tankId = parseInt(tankIdString); - const tankAverage = (await tankAverages)[tankId]; - - return tankAverage ? (await accumulator) + stats.battles : accumulator; - }, 0); - const battlesOfTanksWithTankopediaEntry = await entries.reduce< - PossiblyPromise - >(async (accumulator, [tankIdString, stats]) => { - const tankId = parseInt(tankIdString); - const tankopediaEntry = (await tankopedia)[tankId]; - - return tankopediaEntry - ? (await accumulator) + stats.battles - : accumulator; - }, 0); + if (type === 'player' || type === 'multi-tank') { + const typedFilters = filters as Partial | 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( + (accumulator, id) => + awaitedTankAverages[id] + ? accumulator + diffed[id].battles + : accumulator, + 0, + ); + const battlesOfTanksWithTankopediaEntry = filteredOrder.reduce( + (accumulator, id) => + awaitedTankopedia[id] ? accumulator + diffed[id].battles : accumulator, + 0, + ); supplementaryStats = { WN8: - (await entries.reduce>( - async (accumulator, [tankIdString, stats]) => { - const tankId = parseInt(tankIdString); - const tankAverage = (await tankAverages)[tankId]; - - return tankAverage - ? (await accumulator) + - calculateWN8(tankAverage.all, stats) * stats.battles - : accumulator; - }, + filteredOrder.reduce( + (accumulator, id) => + awaitedTankAverages[id] + ? accumulator + + calculateWN8(awaitedTankAverages[id].all, diffed[id]) * + diffed[id].battles + : accumulator, 0, - )) / battlesOfTanksWithAverages, + ) / battlesOfTanksWithAverages, tier: - (await entries.reduce>( - async (accumulator, [tankIdString, stats]) => { - const tankId = parseInt(tankIdString); - const tankopediaEntry = (await tankopedia)[tankId]; - - return tankopediaEntry - ? (await accumulator) + tankopediaEntry.tier * stats.battles - : accumulator; - }, + filteredOrder.reduce( + (accumulator, id) => + awaitedTankopedia[id] + ? accumulator + awaitedTankopedia[id]!.tier * diffed[id].battles + : accumulator, 0, - )) / battlesOfTanksWithTankopediaEntry, + ) / battlesOfTanksWithTankopediaEntry, }; - tierWeights = await entries.reduce>( - async (accumulator, [tankIdString, stats]) => { - const tankId = parseInt(tankIdString); - const tankopediaEntry = (await tankopedia)[tankId]; + tierWeights = filteredOrder.reduce((accumulator, id) => { + const tankopediaEntry = awaitedTankopedia[id]; - if (!tankopediaEntry) return accumulator; + if (!tankopediaEntry) return accumulator; - const tier = tankopediaEntry.tier as Tier; + const tier = tankopediaEntry.tier as Tier; - if ((await accumulator)[tier]) { - (await accumulator)[tier]! += stats.battles; - } else { - (await accumulator)[tier] = stats.battles; - } + if (accumulator[tier]) { + accumulator[tier]! += diffed[id].battles; + } else { + accumulator[tier] = diffed[id].battles; + } - return accumulator; - }, - {}, - ); + return accumulator; + }, {}); } else { - stats = diffed[tankId!]; + stats = diffed[(filters as number)!]; supplementaryStats = { WN8: stats - ? calculateWN8((await tankAverages)[tankId!].all, diffed[tankId!]) + ? calculateWN8( + (await tankAverages)[(filters as number)!].all, + diffed[(filters as number)!], + ) : undefined, - tier: (await tankopedia)[tankId!]?.tier, + tier: (await tankopedia)[(filters as number)!]?.tier, }; }