mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: migration 2
This commit is contained in:
1 parent
e1c8194beb
commit
6285f33989
305 files changed
+414
-414
No files matched your search
@@ -0,0 +1,58 @@
|
||||
import { BlitzStats } from './compositeStats/constants';
|
||||
|
||||
/**
|
||||
* Formula from https://web.archive.org/web/20190105064817/http://wiki.wnefficiency.net/pages/WN8
|
||||
*
|
||||
* @param expected what all players on average perform like
|
||||
* @param average what the player in question performs like
|
||||
*/
|
||||
export default function calculateWN8(
|
||||
expected: BlitzStats,
|
||||
average: {
|
||||
damage_dealt: number;
|
||||
battles: number;
|
||||
spotted: number;
|
||||
frags: number;
|
||||
dropped_capture_points: number;
|
||||
wins: number;
|
||||
},
|
||||
) {
|
||||
const avgDmg = average.damage_dealt / average.battles;
|
||||
const avgSpot = average.spotted / average.battles;
|
||||
const avgFrag = average.frags / average.battles;
|
||||
const avgDef = average.dropped_capture_points / average.battles;
|
||||
const avgWinRate = average.wins / average.battles;
|
||||
|
||||
const expDmg = expected.damage_dealt / expected.battles;
|
||||
const expSpot = expected.spotted / expected.battles;
|
||||
const expFrag = expected.frags / expected.battles;
|
||||
const expDef = expected.dropped_capture_points / expected.battles;
|
||||
const expWinRate = expected.wins / expected.battles;
|
||||
|
||||
const rDAMAGE = avgDmg / expDmg;
|
||||
const rSPOT = avgSpot / expSpot;
|
||||
const rFRAG = avgFrag / expFrag;
|
||||
const rDEF = avgDef / expDef;
|
||||
const rWIN = avgWinRate / expWinRate;
|
||||
|
||||
const rWINc = Math.max(0, (rWIN - 0.71) / (1 - 0.71));
|
||||
const rDAMAGEc = Math.max(0, (rDAMAGE - 0.22) / (1 - 0.22));
|
||||
const rFRAGc = Math.max(
|
||||
0,
|
||||
Math.min(rDAMAGEc + 0.2, (rFRAG - 0.12) / (1 - 0.12)),
|
||||
);
|
||||
const rSPOTc = Math.max(
|
||||
0,
|
||||
Math.min(rDAMAGEc + 0.1, (rSPOT - 0.38) / (1 - 0.38)),
|
||||
);
|
||||
const rDEFc = Math.max(0, Math.min(rDAMAGEc + 0.1, (rDEF - 0.1) / (1 - 0.1)));
|
||||
|
||||
const WN8 =
|
||||
980 * rDAMAGEc +
|
||||
210 * rDAMAGEc * rFRAGc +
|
||||
155 * rFRAGc * rSPOTc +
|
||||
75 * rDEFc * rFRAGc +
|
||||
145 * Math.min(1.8, rWINc);
|
||||
|
||||
return WN8;
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
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 blitzkitStatsKeys = [
|
||||
...blitzStatsKeys,
|
||||
'battle_life_time',
|
||||
] as const;
|
||||
|
||||
export const blitzPlayerStatsKeys = [
|
||||
...blitzStatsKeys,
|
||||
'max_frags_tank_id',
|
||||
'max_xp_tank_id',
|
||||
] as const;
|
||||
|
||||
export const compositeStatsKeysBase = [
|
||||
...blitzkitStatsKeys,
|
||||
'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 normalizedBlitzkitStatsKeys = [
|
||||
...normalizedBlitzStatsKeys,
|
||||
'battle_life_time',
|
||||
] satisfies (typeof blitzkitStatsKeys)[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 cumulativeBlitzkitStatsKeys = [
|
||||
...cumulativeBlitzStatsKeys,
|
||||
'battle_life_time',
|
||||
] satisfies (typeof blitzkitStatsKeys)[number][];
|
||||
|
||||
const normalizedCompositeStatsKeys = [
|
||||
...normalizedBlitzkitStatsKeys,
|
||||
// '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 = [
|
||||
...cumulativeBlitzkitStatsKeys,
|
||||
'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 BlitzkitStatsKey = (typeof blitzkitStatsKeys)[number];
|
||||
export type BlitzkitStats = Record<BlitzkitStatsKey, 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 },
|
||||
normalized_battle_life_time: { fixed: 1, preview: 4.6, coefficient: 1 / 60 },
|
||||
|
||||
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 },
|
||||
cumulative_battle_life_time: {
|
||||
fixed: 0,
|
||||
preview: 109,
|
||||
coefficient: 1 / 60,
|
||||
localeFormat: true,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
import calculateWN8 from '../calculateWN8';
|
||||
import {
|
||||
BlitzkitStats,
|
||||
CompositeStats,
|
||||
compositeStatsFormatting,
|
||||
CompositeStatsKey,
|
||||
} from './constants';
|
||||
|
||||
export function compositeStats(stats: BlitzkitStats, average: BlitzkitStats) {
|
||||
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,
|
||||
normalized_battle_life_time: stats.battle_life_time / 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),
|
||||
cumulative_battle_life_time: stats.battle_life_time,
|
||||
} 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);
|
||||
}
|
||||
|
||||
export * from './constants';
|
||||
@@ -0,0 +1,18 @@
|
||||
import { RatingPlayer } from '../../commands/ratingLeaderboard';
|
||||
import { RatingStats } from '../blitz/getAccountInfo';
|
||||
import { BkrlSuperset1Entry } from '../streams/bkrl';
|
||||
|
||||
export function deltaBkrlBlitzStats(
|
||||
a: BkrlSuperset1Entry,
|
||||
b1: RatingStats,
|
||||
b2: RatingPlayer,
|
||||
) {
|
||||
return {
|
||||
battles: b1.battles - a.battles,
|
||||
damage: b1.damage_dealt - a.damage,
|
||||
kills: b1.frags - a.kills,
|
||||
survived: b1.survived_battles - a.survived,
|
||||
wins: b1.wins - a.wins,
|
||||
score: b2.score - a.score,
|
||||
} satisfies Omit<BkrlSuperset1Entry, 'id'>;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
export function deltaTankStats(
|
||||
tanksA: IndividualTankStats[],
|
||||
tanksB: IndividualTankStats[],
|
||||
) {
|
||||
return tanksB
|
||||
.map((b) => {
|
||||
const a =
|
||||
tanksA.find((a) => a.tank_id === b.tank_id) ?? emptyIndividualTankStats;
|
||||
|
||||
return {
|
||||
account_id: b.account_id,
|
||||
battle_life_time: b.battle_life_time - a.battle_life_time,
|
||||
frags: b.frags === null || a.frags === null ? null : b.frags - a.frags,
|
||||
in_garage: b.in_garage,
|
||||
in_garage_updated: b.in_garage_updated,
|
||||
last_battle_time: b.last_battle_time,
|
||||
mark_of_mastery: b.mark_of_mastery,
|
||||
max_frags: b.max_frags,
|
||||
max_xp: b.max_xp,
|
||||
tank_id: b.tank_id,
|
||||
|
||||
all: {
|
||||
battles: b.all.battles - a.all.battles,
|
||||
capture_points: b.all.capture_points - a.all.capture_points,
|
||||
damage_dealt: b.all.damage_dealt - a.all.damage_dealt,
|
||||
damage_received: b.all.damage_received - a.all.damage_received,
|
||||
dropped_capture_points:
|
||||
b.all.dropped_capture_points - a.all.dropped_capture_points,
|
||||
frags: b.all.frags - a.all.frags,
|
||||
frags8p: b.all.frags8p - a.all.frags8p,
|
||||
hits: b.all.hits - a.all.hits,
|
||||
losses: b.all.losses - a.all.losses,
|
||||
max_frags: b.all.max_frags,
|
||||
max_xp: b.all.max_xp,
|
||||
shots: b.all.shots - a.all.shots,
|
||||
spotted: b.all.spotted - a.all.spotted,
|
||||
survived_battles: b.all.survived_battles - a.all.survived_battles,
|
||||
xp: b.all.xp - a.all.xp,
|
||||
win_and_survived: b.all.win_and_survived - a.all.win_and_survived,
|
||||
wins: b.all.wins - a.all.wins,
|
||||
},
|
||||
} satisfies IndividualTankStats;
|
||||
})
|
||||
.filter((a) => a !== null && a.all.battles > 0) as IndividualTankStats[];
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { tankDefinitions } from '../blitzkit/tankDefinitions';
|
||||
import calculateWN8 from './calculateWN8';
|
||||
import sumStats from './sumStats';
|
||||
|
||||
export interface StatFilters {
|
||||
nation?: string;
|
||||
tier?: number;
|
||||
tankType?: string;
|
||||
treeType?: TreeType;
|
||||
tank?: number;
|
||||
}
|
||||
|
||||
export async function filterStats(
|
||||
{ diff, order }: DiffedTankStats,
|
||||
filters: StatFilters,
|
||||
) {
|
||||
// tank, if provided, takes priority over all other filters
|
||||
if (filters.tank) filters = { tank: filters.tank };
|
||||
|
||||
const awaitedTankDefinitions = await tankDefinitions;
|
||||
const awaitedTankAverages = await tankAverages;
|
||||
const filteredOrder = order.filter((id) => {
|
||||
const entry = awaitedTankDefinitions[id];
|
||||
|
||||
return (
|
||||
entry &&
|
||||
(filters.nation === undefined || entry.nation === filters.nation) &&
|
||||
(filters.tier === undefined || entry.tier === filters.tier) &&
|
||||
(filters.tankType === undefined || entry.class === filters.tankType) &&
|
||||
(filters.treeType === undefined ||
|
||||
(filters.treeType === 'collector' && entry.treeType === 'collector') ||
|
||||
(filters.treeType === 'premium' && entry.treeType === 'premium') ||
|
||||
(filters.treeType === 'researchable' &&
|
||||
entry.treeType === 'researchable')) &&
|
||||
(filters.tank === undefined || entry.id === filters.tank)
|
||||
);
|
||||
});
|
||||
const stats = sumStats(filteredOrder.map((id) => diff[id]));
|
||||
const battlesOfTanksWithAverages = filteredOrder.reduce<number>(
|
||||
(accumulator, id) =>
|
||||
awaitedTankAverages[id] ? accumulator + diff[id].battles : accumulator,
|
||||
0,
|
||||
);
|
||||
const battlesOfTanksWithTankDefinition = filteredOrder.reduce<number>(
|
||||
(accumulator, id) =>
|
||||
awaitedTankDefinitions[id] ? accumulator + diff[id].battles : accumulator,
|
||||
0,
|
||||
);
|
||||
const supplementary = {
|
||||
WN8:
|
||||
filteredOrder.reduce<number>(
|
||||
(accumulator, id) =>
|
||||
awaitedTankAverages[id]
|
||||
? accumulator +
|
||||
calculateWN8(awaitedTankAverages[id].all, diff[id]) *
|
||||
diff[id].battles
|
||||
: accumulator,
|
||||
0,
|
||||
) / battlesOfTanksWithAverages,
|
||||
tier:
|
||||
filteredOrder.reduce<number>(
|
||||
(accumulator, id) =>
|
||||
awaitedTankDefinitions[id]
|
||||
? accumulator + awaitedTankDefinitions[id]!.tier * diff[id].battles
|
||||
: accumulator,
|
||||
0,
|
||||
) / battlesOfTanksWithTankDefinition,
|
||||
} satisfies SupplementaryStats;
|
||||
|
||||
return { stats, supplementary, filteredOrder };
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
export enum BkniPercentile {
|
||||
SuperMalum,
|
||||
Malum,
|
||||
VeryBad,
|
||||
Bad,
|
||||
BelowAverage,
|
||||
Average,
|
||||
AboveAverage,
|
||||
Good,
|
||||
VeryGood,
|
||||
Unicum,
|
||||
SuperUnicum,
|
||||
}
|
||||
|
||||
export const BKNI_PERCENTILES = [
|
||||
{ min: 0, percentile: BkniPercentile.SuperMalum },
|
||||
{ min: 1, percentile: BkniPercentile.Malum },
|
||||
{ min: 10, percentile: BkniPercentile.VeryBad },
|
||||
{ min: 20, percentile: BkniPercentile.Bad },
|
||||
{ min: 30, percentile: BkniPercentile.BelowAverage },
|
||||
{ min: 40, percentile: BkniPercentile.Average },
|
||||
{ min: 60, percentile: BkniPercentile.AboveAverage },
|
||||
{ min: 70, percentile: BkniPercentile.Good },
|
||||
{ min: 80, percentile: BkniPercentile.VeryGood },
|
||||
{ min: 90, percentile: BkniPercentile.Unicum },
|
||||
{ min: 99, percentile: BkniPercentile.SuperUnicum },
|
||||
];
|
||||
|
||||
export const BKNI_COLORS = {
|
||||
[BkniPercentile.SuperMalum]: 'tomato',
|
||||
[BkniPercentile.Malum]: 'orange',
|
||||
[BkniPercentile.VeryBad]: 'amber',
|
||||
[BkniPercentile.Bad]: 'yellow',
|
||||
[BkniPercentile.BelowAverage]: 'lime',
|
||||
[BkniPercentile.Average]: 'green',
|
||||
[BkniPercentile.AboveAverage]: 'teal',
|
||||
[BkniPercentile.Good]: 'cyan',
|
||||
[BkniPercentile.VeryGood]: 'pink',
|
||||
[BkniPercentile.Unicum]: 'plum',
|
||||
[BkniPercentile.SuperUnicum]: 'purple',
|
||||
} as const;
|
||||
|
||||
export default function getBkniPercentile(wn8: number) {
|
||||
const lastIndex = BKNI_PERCENTILES.findLastIndex(({ min }) => min <= wn8);
|
||||
return BKNI_PERCENTILES[lastIndex === -1 ? 0 : lastIndex].percentile;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { TierWeightsRecord } from '../../components/TierWeights';
|
||||
import { tankDefinitions } from '../blitzkit/tankDefinitions';
|
||||
import { BlitzStats } from './compositeStats/constants';
|
||||
|
||||
export async function getTierWeights(
|
||||
stats: Record<number, BlitzStats>,
|
||||
order: number[] = Object.keys(stats).map(parseInt),
|
||||
) {
|
||||
const awaitedTankDefinitions = await tankDefinitions;
|
||||
const tierWeights: TierWeightsRecord = {};
|
||||
|
||||
order.forEach((id) => {
|
||||
const entry = awaitedTankDefinitions[id];
|
||||
|
||||
if (!entry) return;
|
||||
if (!tierWeights[entry.tier]) tierWeights[entry.tier] = 0;
|
||||
|
||||
tierWeights[entry.tier]! += stats[id].battles;
|
||||
});
|
||||
|
||||
return tierWeights;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Percentile } from './percentiles';
|
||||
|
||||
const WN8_PERCENTILES: [number, Percentile][] = [
|
||||
[0, Percentile.VeryBad],
|
||||
[300, Percentile.Bad],
|
||||
[450, Percentile.BelowAverage],
|
||||
[650, Percentile.Average],
|
||||
[900, Percentile.AboveAverage],
|
||||
[1200, Percentile.Good],
|
||||
[1600, Percentile.VeryGood],
|
||||
[2000, Percentile.Great],
|
||||
[2450, Percentile.Unicum],
|
||||
[2900, Percentile.SuperUnicum],
|
||||
];
|
||||
|
||||
export default function getWN8Percentile(WN8: number) {
|
||||
const lastIndex = WN8_PERCENTILES.findLastIndex(
|
||||
([WN8Listing]) => WN8Listing <= WN8,
|
||||
);
|
||||
return WN8_PERCENTILES[lastIndex === -1 ? 0 : lastIndex][1];
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export * from './calculateWN8';
|
||||
export * from './compositeStats';
|
||||
export * from './deltaBkrlBlitzStats';
|
||||
export * from './deltaTankStats';
|
||||
export * from './filterStats';
|
||||
export * from './getBkniPercentile';
|
||||
export * from './getTierWeights';
|
||||
export * from './getWN8Percentile';
|
||||
export * from './percentiles';
|
||||
export * from './sumStats';
|
||||
@@ -0,0 +1,13 @@
|
||||
// http://www.wotinfo.net/en/wn8scale
|
||||
export enum Percentile {
|
||||
VeryBad,
|
||||
Bad,
|
||||
BelowAverage,
|
||||
Average,
|
||||
AboveAverage,
|
||||
Good,
|
||||
VeryGood,
|
||||
Great,
|
||||
Unicum,
|
||||
SuperUnicum,
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { BlitzStats } from './compositeStats/constants';
|
||||
|
||||
export default function sumStats(allStatsCollection: BlitzStats[]) {
|
||||
return allStatsCollection.reduce((accumulated, current) => {
|
||||
function sum(value: (allStats: BlitzStats) => number) {
|
||||
return value(accumulated) + value(current);
|
||||
}
|
||||
function max(value: (allStats: BlitzStats) => number) {
|
||||
return Math.max(value(accumulated), value(current));
|
||||
}
|
||||
|
||||
return {
|
||||
battles: sum((a) => a.battles),
|
||||
wins: sum((a) => a.wins),
|
||||
capture_points: sum((a) => a.capture_points),
|
||||
damage_dealt: sum((a) => a.damage_dealt),
|
||||
damage_received: sum((a) => a.damage_received),
|
||||
dropped_capture_points: sum((a) => a.dropped_capture_points),
|
||||
frags: sum((a) => a.frags),
|
||||
frags8p: sum((a) => a.frags8p),
|
||||
hits: sum((a) => a.hits),
|
||||
losses: sum((a) => a.losses),
|
||||
max_frags: max((a) => a.max_frags),
|
||||
max_xp: max((a) => a.max_xp),
|
||||
shots: sum((a) => a.shots),
|
||||
spotted: sum((a) => a.spotted),
|
||||
survived_battles: sum((a) => a.survived_battles),
|
||||
win_and_survived: sum((a) => a.win_and_survived),
|
||||
xp: sum((a) => a.xp),
|
||||
} satisfies BlitzStats;
|
||||
}, emptyAllStats);
|
||||
}
|
||||
Reference in new issue
Block a user