mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-22 11:17:05 +02:00
feat: composite stats
This commit is contained in:
1 parent
84893842e9
commit
64139e301b
27 files changed
+417
-82
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import calculateWN8 from '../statistics/calculateWN8';
|
||||
import { AllStats } from './getAccountInfo';
|
||||
import { BlitzStats } from '../statistics/compositeStats/constants';
|
||||
|
||||
export type Stat = keyof ReturnType<typeof generateStats>;
|
||||
|
||||
@@ -14,7 +14,7 @@ export const STAT_NAMES: Record<Stat, string> = {
|
||||
|
||||
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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<AverageDefinitionsAllStats>;
|
||||
sigma: Partial<AverageDefinitionsAllStats>;
|
||||
r: Partial<AverageDefinitionsAllStats>;
|
||||
mu: Partial<BlitzkitStats>;
|
||||
sigma: Partial<BlitzkitStats>;
|
||||
r: Partial<BlitzkitStats>;
|
||||
samples: Samples;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<number, AllStats>;
|
||||
diff: Record<number, BlitzStats>;
|
||||
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<number, AllStats> = {};
|
||||
const diff: Record<number, BlitzStats> = {};
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<BlitzStatsKey, number>;
|
||||
|
||||
type BlitzPlayerStatsKey = (typeof blitzPlayerStatsKeys)[number];
|
||||
export type BlitzPlayerStats = Record<BlitzPlayerStatsKey, number>;
|
||||
|
||||
export type CompositeStatsKey =
|
||||
| `normalized_${(typeof normalizedCompositeStatsKeys)[number]}`
|
||||
| `cumulative_${(typeof cumulativeCompositeStatsKeys)[number]}`;
|
||||
export type CompositeStats = Record<CompositeStatsKey, number>;
|
||||
|
||||
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 },
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<number, AllStats>,
|
||||
stats: Record<number, BlitzStats>,
|
||||
order: number[] = Object.keys(stats).map(parseInt),
|
||||
) {
|
||||
const awaitedTankDefinitions = await tankDefinitions;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user