diff --git a/src/app/tools/embed/configurations/breakdown.tsx b/src/app/tools/embed/configurations/breakdown.tsx index 2c45f5df1..dffd316f6 100644 --- a/src/app/tools/embed/configurations/breakdown.tsx +++ b/src/app/tools/embed/configurations/breakdown.tsx @@ -5,10 +5,20 @@ import { BreakdownEmbedWrapper, } from '../../../../components/TanksEmbed'; import { tankDefinitionsArray } from '../../../../core/blitzkit/tankDefinitions'; +import { + CompositeStatsKey, + compositeStatsKeys, +} from '../../../../core/statistics/compositeStats/constants'; +import strings from '../../../../lang/en-US.json'; import { EmbedConfig } from '../../../../stores/embedState'; import { EmbedItemType } from '../../../../stores/embedState/constants'; import { useEmbedStateCurry } from '../../../../stores/embedState/utilities'; +const compositeStatsKeysOptions = compositeStatsKeys.map((value) => ({ + label: strings.common.composite_stats[value], + value, +})); + export const breakdownConfig = { showTotal: { type: EmbedItemType.Boolean, default: true, pad: true }, @@ -60,14 +70,30 @@ export const breakdownConfig = { pad: true, }, - column1Unit: { type: EmbedItemType.String, default: '' }, - column2Unit: { type: EmbedItemType.String, default: '%' }, - column3Unit: { type: EmbedItemType.String, default: '' }, - column4Unit: { type: EmbedItemType.String, default: '' }, - column1Label: { type: EmbedItemType.String, default: 'Battles' }, - column2Label: { type: EmbedItemType.String, default: 'Winrate' }, - column3Label: { type: EmbedItemType.String, default: 'WN8' }, - column4Label: { type: EmbedItemType.String, default: 'Damage' }, + column1: { + type: EmbedItemType.Enum, + default: 'cumulative_battles' satisfies CompositeStatsKey, + options: compositeStatsKeysOptions, + }, + column2: { + type: EmbedItemType.Enum, + default: 'normalized_wins' satisfies CompositeStatsKey, + options: compositeStatsKeysOptions, + }, + column3: { + type: EmbedItemType.Enum, + default: 'cumulative_wn8' satisfies CompositeStatsKey, + options: compositeStatsKeysOptions, + }, + column4: { + type: EmbedItemType.Enum, + default: 'normalized_damage_dealt' satisfies CompositeStatsKey, + options: compositeStatsKeysOptions, + }, + column1CustomLabel: { type: EmbedItemType.String, default: '' }, + column2CustomLabel: { type: EmbedItemType.String, default: '' }, + column3CustomLabel: { type: EmbedItemType.String, default: '' }, + column4CustomLabel: { type: EmbedItemType.String, default: '' }, } satisfies EmbedConfig; export function BreakdownPreview() { diff --git a/src/app/tools/embed/customize/[embed]/components/Enum.tsx b/src/app/tools/embed/customize/[embed]/components/Enum.tsx new file mode 100644 index 000000000..b87528d06 --- /dev/null +++ b/src/app/tools/embed/customize/[embed]/components/Enum.tsx @@ -0,0 +1,36 @@ +import { Select } from '@radix-ui/themes'; +import * as EmbedState from '../../../../../../stores/embedState'; +import { + EmbedConfigItemType, + EmbedItemType, +} from '../../../../../../stores/embedState/constants'; +import { EmbedPreviewControllerProps } from '../page'; + +export function Enum({ + configKey, + config, +}: EmbedPreviewControllerProps & { + config: EmbedConfigItemType; +}) { + const mutateEmbedState = EmbedState.useMutation(); + + return ( + state[configKey] as string)} + onValueChange={(value) => { + mutateEmbedState((draft) => { + draft[configKey] = value; + }); + }} + > + + + {config.options.map((option) => ( + + {option.label} + + ))} + + + ); +} diff --git a/src/app/tools/embed/customize/[embed]/components/RichText.tsx b/src/app/tools/embed/customize/[embed]/components/RichText.tsx index 480e3a82c..06e47ddff 100644 --- a/src/app/tools/embed/customize/[embed]/components/RichText.tsx +++ b/src/app/tools/embed/customize/[embed]/components/RichText.tsx @@ -15,8 +15,6 @@ export function RichText({ configKey }: EmbedPreviewControllerProps) { ] as EmbedConfigItemType['default'], ); - console.log(state); - return ( ; + break; + } + + case EmbedItemType.Enum: { + control = ; + break; } } diff --git a/src/app/tools/performance/components/Total.tsx b/src/app/tools/performance/components/Total.tsx index 3a002c620..10c648d17 100644 --- a/src/app/tools/performance/components/Total.tsx +++ b/src/app/tools/performance/components/Total.tsx @@ -3,10 +3,10 @@ import { memo, use, useCallback } from 'react'; import { StickyRowHeaderCell } from '../../../../components/StickyRowHeaderCell'; import { averageDefinitions, - AverageDefinitionsAllStats, AverageDefinitionsEntryWithId, } from '../../../../core/blitzkit/averageDefinitions'; import { formatCompact } from '../../../../core/math/formatCompact'; +import { BlitzkitStats } from '../../../../core/statistics/compositeStats/constants'; import { useAveragesExclusionRatio } from '../../../../hooks/useAveragesExclusionRatio'; import * as TankPerformancePersistent from '../../../../stores/tankPerformancePersistent'; @@ -22,7 +22,7 @@ export const Total = memo( ); const ratio = useAveragesExclusionRatio(); const sum = useCallback( - (slice: (tank: AverageDefinitionsAllStats) => number) => { + (slice: (tank: BlitzkitStats) => number) => { return tanks.reduce( (acc, tank) => acc + tank.samples.total * slice(tank.mu), 0, diff --git a/src/commands/breakdown/render.tsx b/src/commands/breakdown/render.tsx index 132179d22..727ba5a06 100644 --- a/src/commands/breakdown/render.tsx +++ b/src/commands/breakdown/render.tsx @@ -4,7 +4,7 @@ import * as Breakdown from '../../components/Breakdown'; import CommandWrapper from '../../components/CommandWrapper'; import NoData from '../../components/NoData'; import TitleBar from '../../components/TitleBar'; -import { AllStats, getAccountInfo } from '../../core/blitz/getAccountInfo'; +import { getAccountInfo } from '../../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../../core/blitz/getClanAccountInfo'; import getTankStats from '../../core/blitz/getTankStats'; import { filtersToDescription } from '../../core/blitzkit/filtersToDescription'; @@ -18,6 +18,7 @@ import { ResolvedPeriod } from '../../core/discord/resolvePeriodFromCommand'; import { ResolvedPlayer } from '../../core/discord/resolvePlayerFromCommand'; import { translator } from '../../core/localization/translator'; import calculateWN8 from '../../core/statistics/calculateWN8'; +import { BlitzStats } from '../../core/statistics/compositeStats/constants'; import { StatFilters, filterStats } from '../../core/statistics/filterStats'; import getWN8Percentile from '../../core/statistics/getWN8Percentile'; import { UserError } from '../../hooks/userError'; @@ -40,8 +41,8 @@ export async function renderBreakdown( const clanData = await getClanAccountInfo(region, id, ['clan']); const tankStats = await getTankStats(region, id); const filterDescriptions = await filtersToDescription(filters, locale); - const orderedCurrentStats: AllStats[] = []; - const orderedCareerStats: AllStats[] = []; + const orderedCurrentStats: BlitzStats[] = []; + const orderedCareerStats: BlitzStats[] = []; const orderedCurrentWN8: (number | undefined)[] = []; const orderedCareerWN8: (number | undefined)[] = []; const orderedCareerWN8Full: (number | undefined)[] = []; diff --git a/src/components/GenericAllStats/index.tsx b/src/components/GenericAllStats/index.tsx index 2e2ff3bb5..fe495bb63 100644 --- a/src/components/GenericAllStats/index.tsx +++ b/src/components/GenericAllStats/index.tsx @@ -1,14 +1,15 @@ 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 isNumber from '../../core/math/isNumber'; +import { BlitzStats } from '../../core/statistics/compositeStats/constants'; import getWN8Percentile from '../../core/statistics/getWN8Percentile'; import { Card } from './components/Card'; import { Root } from './components/Root'; import { Row } from './components/Row'; interface GenericAllStatsProps { - stats: AllStats; + stats: BlitzStats; supplementaryStats?: SupplementaryStats; locale: Locale; } diff --git a/src/components/TanksEmbed.tsx b/src/components/TanksEmbed.tsx index cddaec73f..c9d0bcad9 100644 --- a/src/components/TanksEmbed.tsx +++ b/src/components/TanksEmbed.tsx @@ -5,6 +5,9 @@ import { ReactNode } from 'react'; import { breakdownConfig } from '../app/tools/embed/configurations/breakdown'; import { toRadiusVar } from '../app/tools/embed/utilities'; import { TankDefinition } from '../core/blitzkit/tankDefinitions'; +import { previewCompositeStat } from '../core/statistics/compositeStats'; +import { CompositeStatsKey } from '../core/statistics/compositeStats/constants'; +import strings from '../lang/en-US.json'; import { useEmbedStateCurry } from '../stores/embedState/utilities'; import { ClassHeavy } from './ClassIcon/components/ClassHeavy'; import { ClassLight } from './ClassIcon/components/ClassLight'; @@ -97,6 +100,8 @@ export function BreakdownEmbedCard({ tank }: BreakdownEmbedCardProps) { {times(4, (index) => { const column = (index + 1) as 1 | 2 | 3 | 4; + const columnState = useState(`column${column}`) as CompositeStatsKey; + const customLabel = useState(`column${column}CustomLabel`); return ( - 70{useState(`column${column}Unit`)} + {previewCompositeStat(columnState)} - {useState(`column${column}Label`)} + {customLabel.length === 0 + ? strings.common.composite_stats[columnState] + : customLabel} ); diff --git a/src/core/blitz/generateStats.ts b/src/core/blitz/generateStats.ts index b3406ca0f..7dfb6115a 100644 --- a/src/core/blitz/generateStats.ts +++ b/src/core/blitz/generateStats.ts @@ -1,5 +1,5 @@ import calculateWN8 from '../statistics/calculateWN8'; -import { AllStats } from './getAccountInfo'; +import { BlitzStats } from '../statistics/compositeStats/constants'; export type Stat = keyof ReturnType; @@ -14,7 +14,7 @@ export const STAT_NAMES: Record = { export const STAT_KEYS = Object.keys(STAT_NAMES) as Stat[]; -export function generateStats(s: AllStats, e?: AllStats) { +export function generateStats(s: BlitzStats, e?: BlitzStats) { return { battles: s.battles, wins: s.wins, diff --git a/src/core/blitz/getAccountInfo.ts b/src/core/blitz/getAccountInfo.ts index 80ab17977..50a21ee0a 100644 --- a/src/core/blitz/getAccountInfo.ts +++ b/src/core/blitz/getAccountInfo.ts @@ -1,27 +1,8 @@ import { Region } from '../../constants/regions'; +import { BlitzStats } from '../statistics/compositeStats/constants'; import fetchBlitz, { FetchBlitzParams } from './fetchBlitz'; import { normalizeIds } from './normalizeIds'; -export interface AllStats { - spotted: number; - hits: number; - frags: number; - max_xp: number; - wins: number; - losses: number; - capture_points: number; - battles: number; - damage_dealt: number; - damage_received: number; - max_frags: number; - shots: number; - frags8p: number; - xp: number; - win_and_survived: number; - survived_battles: number; - dropped_capture_points: number; -} - export interface SupplementaryStats { WN8?: number; tier?: number; @@ -79,7 +60,7 @@ export interface IndividualAccountInfo { last_battle_time: number; nickname: string; statistics: { - all: AllStats; + all: BlitzStats; clan: ClanStats; rating?: RatingStats; frags: null | number; diff --git a/src/core/blitzkit/averageDefinitions/constants.ts b/src/core/blitzkit/averageDefinitions/constants.ts index 035ae11be..b25afdf7c 100644 --- a/src/core/blitzkit/averageDefinitions/constants.ts +++ b/src/core/blitzkit/averageDefinitions/constants.ts @@ -1,14 +1,15 @@ -import { AverageDefinitionsAllStats, Samples } from '.'; +import { Samples } from '.'; import { emptyAllStats } from '../../blitzstars/getStatsInPeriod/constants'; +import { BlitzkitStats } from '../../statistics/compositeStats/constants'; -export const emptyAverageDefinitionsAllStats: AverageDefinitionsAllStats = { +export const emptyAverageDefinitionsAllStats: BlitzkitStats = { ...emptyAllStats, battle_life_time: 0, }; export const averageDefinitionsAllStatsKeys = Object.keys( emptyAverageDefinitionsAllStats, -) as (keyof AverageDefinitionsAllStats)[]; +) as (keyof BlitzkitStats)[]; export const emptySamples: Samples = { d_1: 0, diff --git a/src/core/blitzkit/averageDefinitions/index.ts b/src/core/blitzkit/averageDefinitions/index.ts index 535e54d65..460620b7c 100644 --- a/src/core/blitzkit/averageDefinitions/index.ts +++ b/src/core/blitzkit/averageDefinitions/index.ts @@ -1,22 +1,18 @@ -import { AllStats } from '../../blitz/getAccountInfo'; import { decode } from '../../protobuf/decode'; +import { BlitzkitStats } from '../../statistics/compositeStats/constants'; import { asset } from '../asset'; -export interface AverageDefinitionsAllStats extends AllStats { - battle_life_time: number; -} - export interface AverageDefinitionsEntry { - mu: AverageDefinitionsAllStats; - sigma: AverageDefinitionsAllStats; - r: AverageDefinitionsAllStats; + mu: BlitzkitStats; + sigma: BlitzkitStats; + r: BlitzkitStats; samples: Samples; } export interface AverageDefinitionsEntrySubPartial { - mu: Partial; - sigma: Partial; - r: Partial; + mu: Partial; + sigma: Partial; + r: Partial; samples: Samples; } diff --git a/src/core/blitzstars/getPlayerStats.ts b/src/core/blitzstars/getPlayerStats.ts index a9c8980fe..1ba54703a 100644 --- a/src/core/blitzstars/getPlayerStats.ts +++ b/src/core/blitzstars/getPlayerStats.ts @@ -1,10 +1,10 @@ import { Region } from '../../constants/regions'; -import { AllStats } from '../blitz/getAccountInfo'; +import { BlitzStats } from '../statistics/compositeStats/constants'; interface IndividualPlayerStats { clan: { clan_id: number; name: string; tag: string }; statistics: { - all: AllStats; + all: BlitzStats; wn8: number; wn7: number; average_tier: number; diff --git a/src/core/blitzstars/getStatsInPeriod/constants.ts b/src/core/blitzstars/getStatsInPeriod/constants.ts index 9e41e3dda..bee13b582 100644 --- a/src/core/blitzstars/getStatsInPeriod/constants.ts +++ b/src/core/blitzstars/getStatsInPeriod/constants.ts @@ -1,6 +1,6 @@ -import { AllStats } from '../../blitz/getAccountInfo'; +import { BlitzStats } from '../../statistics/compositeStats/constants'; -export const emptyAllStats: AllStats = { +export const emptyAllStats: BlitzStats = { battles: 0, capture_points: 0, damage_dealt: 0, diff --git a/src/core/blitzstars/getStatsInPeriod/index.ts b/src/core/blitzstars/getStatsInPeriod/index.ts index f984085ee..66ee8e8e6 100644 --- a/src/core/blitzstars/getStatsInPeriod/index.ts +++ b/src/core/blitzstars/getStatsInPeriod/index.ts @@ -1,10 +1,10 @@ import { Locale } from 'discord.js'; import { Region } from '../../../constants/regions'; -import { AllStats } from '../../blitz/getAccountInfo'; import getTankHistories, { TankHistories, TankHistoryRaw, } from '../../blitzkit/getTankHistories'; +import { BlitzStats } from '../../statistics/compositeStats/constants'; import { emptyAllStats } from './constants'; const emptyTankHistoryNode: TankHistoryRaw = { @@ -17,7 +17,7 @@ const emptyTankHistoryNode: TankHistoryRaw = { }; export interface DiffedTankStats { - diff: Record; + diff: Record; order: number[]; } @@ -54,7 +54,7 @@ export default async function getStatsInPeriod( const playedTanks: number[] = []; // fetch history node right before "start" and right before "end" - const diff: Record = {}; + const diff: Record = {}; inRangeTanks.forEach((tankId) => { const sortedHistory = tankSortedHistory[tankId]; const previousIndex = @@ -65,7 +65,7 @@ export default async function getStatsInPeriod( const latest = sortedHistory[latestIndex] ?? sortedHistory[sortedHistory.length - 1]; - function d(get: (allStats: AllStats) => number) { + function d(get: (allStats: BlitzStats) => number) { return get(latest.all) - get(previous.all); } diff --git a/src/core/blitzstars/tankAverages.ts b/src/core/blitzstars/tankAverages.ts index 74afff267..a6b0b5bd4 100644 --- a/src/core/blitzstars/tankAverages.ts +++ b/src/core/blitzstars/tankAverages.ts @@ -1,6 +1,6 @@ -import { AllStats } from '../blitz/getAccountInfo'; import { context } from '../blitzkit/context'; import { tankDefinitions } from '../blitzkit/tankDefinitions'; +import { BlitzStats } from '../statistics/compositeStats/constants'; interface SpecialStats { winrate: number; @@ -71,7 +71,7 @@ export interface IndividualTankAverage { total_battle_life_time: number; number_of_players: number; tank_id: number; - all: AllStats; + all: BlitzStats; special: SpecialStats; percentiles: SpecialPercentiles; percentiles_player_count: number; diff --git a/src/core/statistics/calculateWN8.ts b/src/core/statistics/calculateWN8.ts index 885ad2430..235ef7b04 100644 --- a/src/core/statistics/calculateWN8.ts +++ b/src/core/statistics/calculateWN8.ts @@ -1,4 +1,4 @@ -import { AllStats } from '../blitz/getAccountInfo'; +import { BlitzStats } from './compositeStats/constants'; /** * Formula from https://web.archive.org/web/20190105064817/http://wiki.wnefficiency.net/pages/WN8 @@ -7,7 +7,7 @@ import { AllStats } from '../blitz/getAccountInfo'; * @param average what the player in question performs like */ export default function calculateWN8( - expected: AllStats, + expected: BlitzStats, average: { damage_dealt: number; battles: number; diff --git a/src/core/statistics/compositeStats/constants.ts b/src/core/statistics/compositeStats/constants.ts new file mode 100644 index 000000000..116a133bb --- /dev/null +++ b/src/core/statistics/compositeStats/constants.ts @@ -0,0 +1,175 @@ +export const blitzStatsKeys = [ + 'battles', + 'capture_points', + 'damage_dealt', + 'damage_received', + 'dropped_capture_points', + 'frags', + 'frags8p', + 'hits', + 'losses', + 'max_frags', + 'max_xp', + 'shots', + 'spotted', + 'survived_battles', + 'win_and_survived', + 'wins', + 'xp', +] as const; + +export const blitzPlayerStatsKeys = [ + ...blitzStatsKeys, + 'max_frags_tank_id', + 'max_xp_tank_id', +] as const; + +export const compositeStatsKeysBase = [ + ...blitzStatsKeys, + 'damage_ratio', + 'kills_to_death_ratio', + 'accuracy', + 'inaccuracy', + 'misses', + 'deaths', + 'wn8', +] as const; + +const normalizedBlitzStatsKeys = [ + // 'battles', will result in 1 + // 'capture_points', not all battles are encounter mode + 'damage_dealt', + 'damage_received', + // 'dropped_capture_points', not all battles are encounter mode + 'frags', + // 'frags8p', null on most tanks and not all battles have tier 8 and above tanks + 'hits', + 'losses', + // 'max_frags', counts the max; not cumulative + // 'max_xp', counts the max; not cumulative + 'shots', + 'spotted', + 'survived_battles', + 'win_and_survived', + 'wins', + 'xp', +] satisfies (typeof blitzStatsKeys)[number][]; + +const cumulativeBlitzStatsKeys = [ + 'battles', + 'capture_points', + 'damage_dealt', + 'damage_received', + 'dropped_capture_points', + 'frags', + // 'frags8p', once again, just a painful metric + 'hits', + 'losses', + // 'max_frags', static, not cumulative + // 'max_xp', static, not cumulative + 'shots', + 'spotted', + 'survived_battles', + 'win_and_survived', + 'wins', + 'xp', +] satisfies (typeof blitzStatsKeys)[number][]; + +const normalizedCompositeStatsKeys = [ + ...normalizedBlitzStatsKeys, + // 'damage_ratio', is a dimensionless ratio + // 'kills_to_death_ratio', also a dimensionless ratio + // 'accuracy', dimensionless ratio + // 'inaccuracy', dimensionless ratio + 'misses', + 'deaths', + // 'wn8', is based on a sample of all battles so doesn't make sense to normalize +] satisfies (typeof compositeStatsKeysBase)[number][]; + +const cumulativeCompositeStatsKeys = [ + ...cumulativeBlitzStatsKeys, + 'damage_ratio', + 'kills_to_death_ratio', + 'accuracy', + 'inaccuracy', + 'misses', + 'deaths', + 'wn8', +] satisfies (typeof compositeStatsKeysBase)[number][]; + +export const compositeStatsKeys = [ + ...normalizedCompositeStatsKeys.map((key) => `normalized_${key}`), + ...cumulativeCompositeStatsKeys.map((key) => `cumulative_${key}`), +] as CompositeStatsKey[]; + +type BlitzStatsKey = (typeof blitzStatsKeys)[number]; +export type BlitzStats = Record; + +type BlitzPlayerStatsKey = (typeof blitzPlayerStatsKeys)[number]; +export type BlitzPlayerStats = Record; + +export type CompositeStatsKey = + | `normalized_${(typeof normalizedCompositeStatsKeys)[number]}` + | `cumulative_${(typeof cumulativeCompositeStatsKeys)[number]}`; +export type CompositeStats = Record; + +export const compositeStatsFormatting: { + [Key in CompositeStatsKey]: { + unit?: string; + fixed: number; + coefficient?: number; + preview: number; + localeFormat?: boolean; + }; +} = { + normalized_damage_dealt: { fixed: 0, preview: 3246, localeFormat: true }, + normalized_damage_received: { fixed: 0, preview: 966, localeFormat: true }, + normalized_deaths: { fixed: 0, unit: '%', coefficient: 100, preview: 0.09 }, + normalized_frags: { fixed: 2, preview: 2.1 }, + normalized_hits: { fixed: 2, preview: 12.33 }, + normalized_losses: { fixed: 0, unit: '%', coefficient: 100, preview: 0.3 }, + normalized_misses: { fixed: 2, preview: 1.02 }, + normalized_shots: { fixed: 2, preview: 14.54 }, + normalized_spotted: { fixed: 2, preview: 2.02 }, + normalized_survived_battles: { + fixed: 0, + unit: '%', + coefficient: 100, + preview: 0.65, + }, + normalized_win_and_survived: { + fixed: 0, + unit: '%', + coefficient: 100, + preview: 0.59, + }, + normalized_wins: { fixed: 0, unit: '%', coefficient: 100, preview: 0.72 }, + normalized_xp: { fixed: 0, preview: 982, localeFormat: true }, + + cumulative_accuracy: { fixed: 0, unit: '%', coefficient: 100, preview: 0.91 }, + cumulative_battles: { fixed: 0, preview: 12 }, + cumulative_capture_points: { fixed: 2, preview: 0.12 }, + cumulative_damage_dealt: { fixed: 0, preview: 45023, localeFormat: true }, + cumulative_damage_ratio: { fixed: 2, preview: 2.83 }, + cumulative_damage_received: { fixed: 0, preview: 10093, localeFormat: true }, + cumulative_deaths: { fixed: 0, preview: 2 }, + cumulative_dropped_capture_points: { fixed: 2, preview: 15 }, + cumulative_frags: { fixed: 0, preview: 26 }, + cumulative_hits: { fixed: 0, preview: 480 }, + cumulative_inaccuracy: { + fixed: 0, + unit: '%', + coefficient: 100, + preview: 0.08, + }, + cumulative_kills_to_death_ratio: { fixed: 2, preview: 2.3 }, + cumulative_losses: { fixed: 0, preview: 3 }, + cumulative_misses: { fixed: 0, preview: 13 }, + cumulative_shots: { fixed: 0, preview: 309 }, + cumulative_spotted: { fixed: 0, preview: 99 }, + cumulative_survived_battles: { fixed: 0, preview: 10 }, + cumulative_win_and_survived: { fixed: 0, preview: 9 }, + cumulative_wins: { fixed: 0, preview: 8 }, + cumulative_xp: { fixed: 0, preview: 19403, localeFormat: true }, + cumulative_wn8: { fixed: 0, preview: 3103, localeFormat: true }, +}; diff --git a/src/core/statistics/compositeStats/index.ts b/src/core/statistics/compositeStats/index.ts new file mode 100644 index 000000000..3887cd5aa --- /dev/null +++ b/src/core/statistics/compositeStats/index.ts @@ -0,0 +1,59 @@ +import calculateWN8 from '../calculateWN8'; +import { + BlitzStats, + CompositeStats, + compositeStatsFormatting, + CompositeStatsKey, +} from './constants'; + +export function compositeStats(stats: BlitzStats, average: BlitzStats) { + return { + normalized_damage_dealt: stats.damage_dealt / stats.battles, + normalized_damage_received: stats.damage_received / stats.battles, + normalized_deaths: (stats.battles - stats.survived_battles) / stats.battles, + normalized_frags: stats.frags / stats.battles, + normalized_hits: stats.hits / stats.battles, + normalized_losses: stats.losses / stats.battles, + normalized_misses: (stats.shots - stats.hits) / stats.battles, + normalized_shots: stats.shots / stats.battles, + normalized_spotted: stats.spotted / stats.battles, + normalized_survived_battles: stats.survived_battles / stats.battles, + normalized_win_and_survived: stats.win_and_survived / stats.battles, + normalized_wins: stats.wins / stats.battles, + normalized_xp: stats.xp / stats.battles, + + cumulative_accuracy: stats.hits / stats.shots, + cumulative_battles: stats.battles, + cumulative_capture_points: stats.capture_points, + cumulative_damage_dealt: stats.damage_dealt, + cumulative_damage_ratio: stats.damage_dealt / stats.damage_received, + cumulative_damage_received: stats.damage_received, + cumulative_deaths: stats.battles - stats.survived_battles, + cumulative_dropped_capture_points: stats.dropped_capture_points, + cumulative_frags: stats.frags, + cumulative_hits: stats.hits, + cumulative_inaccuracy: (stats.shots - stats.hits) / stats.shots, + cumulative_kills_to_death_ratio: + stats.frags / (stats.battles - stats.survived_battles), + cumulative_losses: stats.losses, + cumulative_misses: stats.shots - stats.hits, + cumulative_shots: stats.shots, + cumulative_spotted: stats.spotted, + cumulative_survived_battles: stats.survived_battles, + cumulative_win_and_survived: stats.win_and_survived, + cumulative_wins: stats.wins, + cumulative_xp: stats.xp, + cumulative_wn8: calculateWN8(average, stats), + } satisfies CompositeStats; +} + +export function formatCompositeStat(value: number, key: CompositeStatsKey) { + const formatting = compositeStatsFormatting[key]; + const resolvedValue = (formatting.coefficient ?? 1) * value; + return `${formatting.localeFormat ? resolvedValue.toLocaleString() : resolvedValue.toFixed(formatting.fixed)}${formatting.unit ?? ''}`; +} + +export function previewCompositeStat(key: CompositeStatsKey) { + const { preview } = compositeStatsFormatting[key]; + return formatCompositeStat(preview, key); +} diff --git a/src/core/statistics/getTierWeights.ts b/src/core/statistics/getTierWeights.ts index 5be4c4aba..e24b984df 100644 --- a/src/core/statistics/getTierWeights.ts +++ b/src/core/statistics/getTierWeights.ts @@ -1,9 +1,9 @@ import { TierWeightsRecord } from '../../components/TierWeights'; -import { AllStats } from '../blitz/getAccountInfo'; import { tankDefinitions } from '../blitzkit/tankDefinitions'; +import { BlitzStats } from './compositeStats/constants'; export async function getTierWeights( - stats: Record, + stats: Record, order: number[] = Object.keys(stats).map(parseInt), ) { const awaitedTankDefinitions = await tankDefinitions; diff --git a/src/core/statistics/sumStats.ts b/src/core/statistics/sumStats.ts index adcdceeaa..baab5aff9 100644 --- a/src/core/statistics/sumStats.ts +++ b/src/core/statistics/sumStats.ts @@ -1,12 +1,12 @@ -import { AllStats } from '../blitz/getAccountInfo'; import { emptyAllStats } from '../blitzstars/getStatsInPeriod/constants'; +import { BlitzStats } from './compositeStats/constants'; -export default function sumStats(allStatsCollection: AllStats[]) { +export default function sumStats(allStatsCollection: BlitzStats[]) { return allStatsCollection.reduce((accumulated, current) => { - function sum(value: (allStats: AllStats) => number) { + function sum(value: (allStats: BlitzStats) => number) { return value(accumulated) + value(current); } - function max(value: (allStats: AllStats) => number) { + function max(value: (allStats: BlitzStats) => number) { return Math.max(value(accumulated), value(current)); } @@ -28,6 +28,6 @@ export default function sumStats(allStatsCollection: AllStats[]) { survived_battles: sum((a) => a.survived_battles), win_and_survived: sum((a) => a.win_and_survived), xp: sum((a) => a.xp), - } satisfies AllStats; + } satisfies BlitzStats; }, emptyAllStats); } diff --git a/src/core/streams/rts.ts b/src/core/streams/rts.ts index 8f6a54180..e490bdc27 100644 --- a/src/core/streams/rts.ts +++ b/src/core/streams/rts.ts @@ -1,9 +1,9 @@ import { times } from 'lodash'; import { IndividualTankStats } from '../../types/tanksStats'; -import { AllStats } from '../blitz/getAccountInfo'; +import { BlitzStats } from '../statistics/compositeStats/constants'; import { ReadStream, WriteStream } from './buffer'; -export interface BlitzkitTankStats extends AllStats { +export interface BlitzkitTankStats extends BlitzStats { id: number; battle_life_time: number; last_battle_time: number; diff --git a/src/lang/en-US.json b/src/lang/en-US.json index a170b1a2f..69f241fd1 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -83,6 +83,44 @@ "gold": "Gold", "silver": "Silver", "bronze": "Bronze" + }, + + "composite_stats": { + "normalized_damage_dealt": "Damage", + "normalized_damage_received": "Damage received", + "normalized_deaths": "Deathrate", + "normalized_frags": "Kills", + "normalized_hits": "Hits", + "normalized_losses": "Lossrate", + "normalized_misses": "Misses", + "normalized_shots": "Shots", + "normalized_spotted": "Spots", + "normalized_survived_battles": "Survival rate", + "normalized_win_and_survived": "Winning and surviving rate", + "normalized_wins": "Winrate", + "normalized_xp": "XP", + + "cumulative_accuracy": "Accuracy", + "cumulative_battles": "Battles", + "cumulative_capture_points": "Total encounter points", + "cumulative_damage_dealt": "Total damage", + "cumulative_damage_ratio": "Damage ratio", + "cumulative_damage_received": "Total damage received", + "cumulative_deaths": "Total deaths", + "cumulative_dropped_capture_points": "Total dropped encounter points", + "cumulative_frags": "Total kills", + "cumulative_hits": "Total hits", + "cumulative_inaccuracy": "Inaccuracy", + "cumulative_kills_to_death_ratio": "Kills to death ratio", + "cumulative_losses": "Losses", + "cumulative_misses": "Total misses", + "cumulative_shots": "Total shots", + "cumulative_spotted": "Total spots", + "cumulative_survived_battles": "Survived battles", + "cumulative_win_and_survived": "Won and survived battles", + "cumulative_wins": "Wins", + "cumulative_xp": "Total XP", + "cumulative_wn8": "WN8" } }, @@ -573,7 +611,7 @@ }, "rating": { - "$": "rating-session", + "$": "rating", "description": "Rating player's statistics", "subcommands": { diff --git a/src/stores/embedState/constants.ts b/src/stores/embedState/constants.ts index 0926716b6..771659ed7 100644 --- a/src/stores/embedState/constants.ts +++ b/src/stores/embedState/constants.ts @@ -9,6 +9,7 @@ export enum EmbedItemType { RichText, Boolean, Slider, + Enum, } export const radixGrays = [ diff --git a/src/stores/embedState/index.ts b/src/stores/embedState/index.ts index 81967bdab..30150e5aa 100644 --- a/src/stores/embedState/index.ts +++ b/src/stores/embedState/index.ts @@ -73,6 +73,14 @@ export type EmbedConfigItem = ( weight: RadixTextWeight; }; } + | { + type: EmbedItemType.Enum; + default: string; + options: { + label: string; + value: string; + }[]; + } ) & { pad?: boolean; }; diff --git a/src/types/histories.ts b/src/types/histories.ts index 59c2f8ab4..47309fae3 100644 --- a/src/types/histories.ts +++ b/src/types/histories.ts @@ -1,9 +1,9 @@ // a cleaner simplification of Blitz Stars' histories because it's a mess there -import { AllStats } from '../core/blitz/getAccountInfo'; +import { BlitzStats } from '../core/statistics/compositeStats/constants'; export interface History { - all: AllStats; + all: BlitzStats; last_battle_time: number; } diff --git a/src/types/tanksStats.ts b/src/types/tanksStats.ts index dcff4a22e..8e6be2059 100644 --- a/src/types/tanksStats.ts +++ b/src/types/tanksStats.ts @@ -1,8 +1,8 @@ -import { AllStats } from '../core/blitz/getAccountInfo'; import { emptyAllStats } from '../core/blitzstars/getStatsInPeriod/constants'; +import { BlitzStats } from '../core/statistics/compositeStats/constants'; export interface IndividualTankStats { - all: AllStats; + all: BlitzStats; last_battle_time: number; account_id: number; max_xp: number;