diff --git a/src/commands/debug.ts b/src/commands/debug.ts index a8f5e87de..dede137d5 100644 --- a/src/commands/debug.ts +++ b/src/commands/debug.ts @@ -4,6 +4,7 @@ import { CommandRegistry } from '../behaviors/interactionCreate.js'; import { SKILLED_COLOR } from '../constants/colors.js'; import { client } from '../index.js'; import getClientId from '../utilities/getClientId.js'; +import { tankAverages } from '../utilities/tankAverages.js'; import { tankopedia } from '../utilities/tankopedia.js'; const executionStart = new Date().getTime(); @@ -35,6 +36,8 @@ export default { uptime % 1000, )}ms\n**Tankopedia**: ${ tankopedia ? 'cached' : 'not cached' + }\n**Tank averages**: ${ + tankAverages ? 'cached' : 'not cached' }\n**Client ID**: ${getClientId()}\n**Tag**: ${ interaction.client.user.tag }`, diff --git a/src/commands/inactive.ts b/src/commands/inactive.ts index 97e3b574a..ad5425a1b 100644 --- a/src/commands/inactive.ts +++ b/src/commands/inactive.ts @@ -4,7 +4,7 @@ import { CommandRegistry } from '../behaviors/interactionCreate.js'; import { NEGATIVE_COLOR, POSITIVE_COLOR } from '../constants/colors.js'; import { BlitzServer } from '../constants/servers.js'; import { ClanInfo } from '../types/clanInfo.js'; -import { PlayerPersonalData } from '../types/playerPersonalData.js'; +import { AccountInfo } from '../types/accountInfo.js'; import addServerChoices from '../utilities/addServerChoices.js'; import { args } from '../utilities/args.js'; import getClan from '../utilities/getClan.js'; @@ -53,7 +53,7 @@ export default { async (clanInfo) => { const memberIds = clanInfo[clan.clan_id].members_ids; - getWargamingResponse( + getWargamingResponse( `https://api.wotblitz.${server}/wotb/account/info/?application_id=${ args['wargaming-application-id'] }&account_id=${memberIds.join(',')}`, diff --git a/src/commands/stats.ts b/src/commands/stats.ts index debd25c8a..5c429576b 100644 --- a/src/commands/stats.ts +++ b/src/commands/stats.ts @@ -3,13 +3,19 @@ import markdownEscape from 'markdown-escape'; import { CommandRegistry } from '../behaviors/interactionCreate.js'; import { SKILLED_COLOR } from '../constants/colors.js'; import { BlitzServer } from '../constants/servers.js'; +import { AccountInfo, AllStats } from '../types/accountInfo.js'; +import { PeriodStatistics } from '../types/statistics.js'; import addIGNOption from '../utilities/addIGNOption.js'; import addServerChoices from '../utilities/addServerChoices.js'; +import { args } from '../utilities/args.js'; import blitzLinks from '../utilities/blitzLinks.js'; import getBlitzAccount from '../utilities/getBlitzAccount.js'; import getBlitzStarsAccount from '../utilities/getBlitzStarsAccount.js'; +import getWargamingResponse from '../utilities/getWargamingResponse.js'; import poweredByBlitzStars from '../utilities/poweredByBlitzStars.js'; +type Period = 'today' | '30' | '90' | 'career'; + export default { inProduction: true, inDevelopment: false, @@ -23,9 +29,10 @@ export default { .setName('period') .setDescription('The last number of days of stats') .setChoices( - { name: '30 Days', value: '30' }, - { name: '90 Days', value: '90' }, - { name: 'Career', value: 'career' }, + { name: 'Today', value: 'today' satisfies Period }, + { name: '30 Days', value: '30' satisfies Period }, + { name: '90 Days', value: '90' satisfies Period }, + { name: 'Career', value: 'career' satisfies Period }, ) .setRequired(true), ) @@ -35,89 +42,188 @@ export default { execute(interaction) { const name = interaction.options.getString('name')!; const server = interaction.options.getString('server') as BlitzServer; - const period = interaction.options.getString('period')! as - | '30' - | '90' - | 'career'; + const period = interaction.options.getString('period') as Period; const command = `stats ${server} ${name} ${period}`; - getBlitzAccount(interaction, command, name, server, async (account) => { - getBlitzStarsAccount( - interaction, - command, - account.account_id, - name, - async (data) => { - { - const stats = - period === 'career' ? data.statistics : data[`period${period}d`]; + getBlitzAccount( + interaction, + command, + name, + server, + async (blitzAccount) => { + getBlitzStarsAccount( + interaction, + command, + blitzAccount.account_id, + name, + async (blitzStarsAccount) => { + async function reply(stats: PeriodStatistics) { + await interaction.editReply({ + embeds: [ + poweredByBlitzStars( + new EmbedBuilder() + .setColor(SKILLED_COLOR) + .setTitle( + `${markdownEscape(blitzStarsAccount.nickname)}'s ${ + period === 'career' ? period : `${period} day` + } stats`, + ) + .setDescription( + `${ + period === 'today' + ? '**⚠ Some stats are not available**\n\n' + : '' + }${[ + ['Winrate', `${stats.special.winrate.toFixed(2)}%`], + [ + 'Survival', + `${stats.special.survivalRate.toFixed(2)}%`, + ], + [ + 'Accuracy', + `${( + (stats.all.hits / stats.all.shots) * + 100 + ).toFixed(2)}%`, + ], + ['WN8', stats.wn8.toFixed(0)], + ['WN7', stats.wn7.toFixed(0)], + [], + ['Battles', stats.all.battles], + ['Wins', stats.all.wins], + ['Losses', stats.all.losses], + ['Average tier', stats.avg_tier.toFixed(2)], + [], + [ + 'Shots per battle', + (stats.all.shots / stats.all.battles).toFixed(2), + ], + ['Hits per battle', stats.special.hpb.toFixed(2)], + ['Damage per battle', stats.special.dpb.toFixed(0)], + ['Kills per battle', stats.special.kpb.toFixed(2)], + ['Spots per battle', stats.special.spb.toFixed(2)], + [ + 'XP per battle', + (stats.all.xp / stats.all.battles).toFixed(0), + ], + [], + [ + 'Damage ratio', + stats.special.damageRatio.toFixed(2), + ], + [ + 'Kills to death ratio', + stats.special.kdr.toFixed(2), + ], + ] + .filter((array) => array[1] !== `${-Infinity}`) + .map((array) => + array.length === 0 + ? '' + : `**${array[0]}**: ${array[1]}`, + ) + .join('\n')}\n\n${blitzLinks(blitzStarsAccount)}`, + ), + ), + ], + }); - await interaction.editReply({ - embeds: [ - poweredByBlitzStars( - new EmbedBuilder() - .setColor(SKILLED_COLOR) - .setTitle( - `${markdownEscape(data.nickname)}'s ${ - period === 'career' ? period : `${period} day` - } stats`, - ) - .setDescription( - `${[ - ['Winrate', `${stats.special.winrate.toFixed(2)}%`], - [ - 'Survival', - `${stats.special.survivalRate.toFixed(2)}%`, - ], - [ - 'Accuracy', - `${((stats.all.hits / stats.all.shots) * 100).toFixed( - 2, - )}%`, - ], - ['WN8', stats.wn8.toFixed(0)], - ['WN7', stats.wn7.toFixed(0)], - [], - ['Battles', stats.all.battles], - ['Wins', stats.all.wins], - ['Losses', stats.all.losses], - ['Average tier', stats.avg_tier.toFixed(2)], - [], - ['Hits per battle', stats.special.hpb.toFixed(2)], - ['Damage per battle', stats.special.dpb.toFixed(0)], - ['Kills per battle', stats.special.kpb.toFixed(2)], - ['Spots per battle', stats.special.spb.toFixed(2)], - [ - 'Shots per battle', - (stats.all.shots / stats.all.battles).toFixed(2), - ], - [ - 'Hits per battle', - (stats.all.hits / stats.all.battles).toFixed(2), - ], - [ - 'XP per battle', - (stats.all.xp / stats.all.battles).toFixed(0), - ], - [], - ['Damage ratio', stats.special.damageRatio.toFixed(2)], - ['Kills to death ratio', stats.special.kdr.toFixed(2)], - ] - .map((array) => - array.length === 0 - ? '' - : `**${array[0]}**: ${array[1]}`, - ) - .join('\n')}\n\n${blitzLinks(data)}`, - ), - ), - ], - }); + console.log( + `Showing ${blitzStarsAccount.nickname}'s ${period} stats`, + ); + } - console.log(`Showing ${data.nickname}'s ${period} stats`); - } - }, - ); - }); + let stats: PeriodStatistics | null = null; + + if (period === 'career') { + stats = blitzStarsAccount.statistics; + } else if (period === 'today') { + getWargamingResponse( + `https://api.wotblitz.${server}/wotb/account/info/?application_id=${args['wargaming-application-id']}&account_id=${blitzAccount.account_id}`, + interaction, + command, + async (accountInfo) => { + const a1 = blitzStarsAccount.statistics; + const a2 = accountInfo[blitzAccount.account_id].statistics; + + const battles = a2.all.battles - a1.all.battles; + + function pb(value: (allStats: AllStats) => number) { + return (value(a2.all) - value(a1.all)) / battles; + } + function diff(value: (allStats: AllStats) => number) { + return value(a2.all) - value(a1.all); + } + function span( + value1: (allStats: AllStats) => number, + value2: (allStats: AllStats) => number, + ) { + return ( + (value1(a2.all) - value1(a1.all)) / + (value2(a2.all) - value2(a1.all)) + ); + } + + stats = { + all: { + battles, + capture_points: pb((a) => a.capture_points), + damage_dealt: pb((a) => a.damage_dealt), + damage_received: pb((a) => a.damage_received), + dropped_capture_points: diff( + (a) => a.dropped_capture_points, + ), + frags: pb((a) => a.frags), + frags8p: pb((a) => a.frags8p), + hits: diff((a) => a.hits), + losses: diff((a) => a.losses), + max_frags: pb((a) => a.max_frags), + max_frags_tank_id: pb((a) => a.max_frags_tank_id), + max_xp: pb((a) => a.max_xp), + max_xp_tank_id: pb((a) => a.max_xp_tank_id), + shots: diff((a) => a.shots), + spotted: pb((a) => a.spotted), + survived_battles: pb((a) => a.survived_battles), + win_and_survived: pb((a) => a.win_and_survived), + wins: diff((a) => a.wins), + xp: diff((a) => a.xp), + }, + special: { + winrate: pb((a) => a.wins) * 100, + dpb: pb((a) => a.damage_dealt), + survivalRate: pb((a) => a.survived_battles) * 100, + damageRatio: span( + (a) => a.damage_dealt, + (a) => a.damage_received, + ), + hitRate: pb((a) => a.hits / a.shots), + hpb: pb((a) => a.hits), + kdr: + diff((a) => a.frags) / + diff((a) => a.battles - a.survived_battles), + kpb: pb((a) => a.frags), + spb: pb((a) => a.spotted), + }, + + avg_tier: -Infinity, + pwp: -Infinity, + wn7: -Infinity, + wn8: -Infinity, + }; + + reply(stats); + }, + ); + } else { + stats = blitzStarsAccount[`period${period}d`]; + } + + if (stats) { + reply(stats); + } + }, + ); + }, + ); }, } satisfies CommandRegistry; diff --git a/src/commands/verify.ts b/src/commands/verify.ts index 0c7f3bb2d..509eccf1b 100644 --- a/src/commands/verify.ts +++ b/src/commands/verify.ts @@ -13,7 +13,7 @@ import getWargamingResponse from '../utilities/getWargamingResponse.js'; export default { inProduction: true, - inDevelopment: true, + inDevelopment: false, inPublic: true, command: new SlashCommandBuilder() diff --git a/src/types/accountInfo.ts b/src/types/accountInfo.ts new file mode 100644 index 000000000..3d109f079 --- /dev/null +++ b/src/types/accountInfo.ts @@ -0,0 +1,71 @@ +export interface AllStats { + spotted: number; + max_frags_tank_id: number; + hits: number; + frags: number; + max_xp: number; + max_xp_tank_id: 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 SpecialStats { + winrate: number; + damageRatio: number; + kdr: number; + dpb: number; + kpb: number; + hpb: number; + spb: number; + hitRate: number; + survivalRate: number; +} + +export interface ClanStats { + spotted: number; + max_frags_tank_id: number; + hits: number; + frags: number; + max_xp: number; + max_xp_tank_id: 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 type AccountInfo = { + [accountId: number]: { + statistics: { + clan: ClanStats; + all: AllStats; + frags: null; + }; + account_id: number; + created_at: number; + updated_at: number; + private: null; + last_battle_time: number; + nickname: string; + }; +}; diff --git a/src/types/playerPersonalData.ts b/src/types/playerPersonalData.ts deleted file mode 100644 index d7e3d1277..000000000 --- a/src/types/playerPersonalData.ts +++ /dev/null @@ -1,55 +0,0 @@ -export type PlayerPersonalData = { - [accountId: number]: { - statistics: { - clan: { - spotted: number; - max_frags_tank_id: number; - hits: number; - frags: number; - max_xp: number; - max_xp_tank_id: 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; - }; - all: { - spotted: number; - max_frags_tank_id: number; - hits: number; - frags: number; - max_xp: number; - max_xp_tank_id: 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; - }; - frags: null; - }; - account_id: number; - created_at: number; - updated_at: number; - private: null; - last_battle_time: number; - nickname: string; - }; -}; diff --git a/src/types/playerStats.ts b/src/types/playerStats.ts new file mode 100644 index 000000000..cd78fcdc6 --- /dev/null +++ b/src/types/playerStats.ts @@ -0,0 +1,28 @@ +import { AllStats } from './accountInfo.js'; + +export type PlayerStats = { + clan: { + clan_id: number; + name: string; + tag: string; + }; + statistics: { + all: AllStats; + wn8: number; + wn7: number; + average_tier: number; + }; + _id: string; + last_battle_time: number; + nickname: string; + account_id: number; + achievements: { + account_id: string; + medalLafayettePool: number; + medalRadleyWalters: number; + warrior: number; + jointVictoryCount: number; + }; + region: string; + __v: number; +}[]; diff --git a/src/types/statistics.ts b/src/types/statistics.ts index f26a77388..102fdcc9e 100644 --- a/src/types/statistics.ts +++ b/src/types/statistics.ts @@ -1,32 +1,8 @@ +import { AllStats, SpecialStats } from './accountInfo.js'; + export interface PeriodStatistics { - all: { - spotted: number; - hits: number; - frags: number; - wins: number; - losses: number; - capture_points: number; - battles: number; - damage_dealt: number; - damage_received: number; - shots: number; - frags8p: number; - xp: number; - win_and_survived: number; - survived_battles: number; - dropped_capture_points: number; - }; - special: { - winrate: number; - damageRatio: number; - kdr: number; - dpb: number; - kpb: number; - hpb: number; - spb: number; - hitRate: number; - survivalRate: number; - }; + all: AllStats; + special: SpecialStats; wn7: number; wn8: number; diff --git a/src/utilities/compoundingAverage.ts b/src/utilities/compoundingAverage.ts new file mode 100644 index 000000000..7c75765f1 --- /dev/null +++ b/src/utilities/compoundingAverage.ts @@ -0,0 +1,8 @@ +export default function compoundingAverage( + average1: number, + battles1: number, + average2: number, + battles2: number, +) { + return (average2 * battles2 - average1 * battles1) / (battles2 - battles1); +} diff --git a/src/utilities/getPlayerStats.ts b/src/utilities/getPlayerStats.ts new file mode 100644 index 000000000..814e8f2d3 --- /dev/null +++ b/src/utilities/getPlayerStats.ts @@ -0,0 +1,16 @@ +import { CacheType, ChatInputCommandInteraction } from 'discord.js'; +import fetch from 'node-fetch'; +import { PlayerStats } from '../types/playerStats.js'; + +export default async function getPlayerStats( + interaction: ChatInputCommandInteraction, + command: string, + id: number, + callback: (playerStats: PlayerStats) => void, +) { + fetch(`https://www.blitzstars.com/api/playerstats/${id}`).then( + async (response) => { + callback((await response.json()) as PlayerStats); + }, + ); +} diff --git a/src/utilities/tankAverages.ts b/src/utilities/tankAverages.ts new file mode 100644 index 000000000..1acac2024 --- /dev/null +++ b/src/utilities/tankAverages.ts @@ -0,0 +1,641 @@ +import fetch from 'node-fetch'; +import { AllStats } from '../types/accountInfo.js'; + +/* +[ + { + "_id": 1, + "battle_life_time": 87518, + "total_battle_life_time": 875184684, + "number_of_players": 10000, + "tank_id": 1, + "all": { + "spotted": 616.4213, + "hits": 3994.2422, + "wins": 290.8189, + "losses": 265.5425, + "capture_points": 239.7655, + "battles": 567.3393, + "damage_dealt": 325559.6622, + "damage_received": 316424.7297, + "shots": 5125.5252, + "max_xp": 1199.4879, + "xp": 210141.9274, + "frags": 403.2506, + "survived_battles": 170.2097, + "dropped_capture_points": 619.8172 + }, + "special": { + "winrate": 51.2601, + "damageRatio": 1.0289, + "kdr": 1.0154, + "damagePerBattle": 573.8359, + "killsPerBattle": 0.7108, + "hitsPerBattle": 7.0403, + "spotsPerBattle": 1.0865, + "wpm": 0.1994, + "dpm": 223.1938, + "kpm": 0.2765, + "hitRate": 77.9284, + "survivalRate": 30.0014 + }, + "percentiles": { + "winRate": [ + 43.714, + 46.429, + 48.3395, + 50.101, + 51.835, + 53.755, + 55.8765, + 58.58, + 62.749 + ], + "damageRatio": [ + 0.622, + 0.7585, + 0.861, + 0.955, + 1.051, + 1.1535, + 1.269, + 1.428, + 1.686 + ], + "kdr": [ + 0.4935, + 0.655, + 0.79, + 0.9245, + 1.0665, + 1.226, + 1.424, + 1.702, + 2.2165 + ], + "damagePerBattle": [ + 361.925, + 437.475, + 493.325, + 544.06, + 593.75, + 645.125, + 702.875, + 776.97, + 896.835 + ], + "killsPerBattle": [ + 0.381, + 0.489, + 0.574, + 0.655, + 0.735, + 0.82, + 0.917, + 1.0425, + 1.2385 + ], + "hitsPerBattle": [ + 3.9295, + 4.6525, + 5.335, + 6.04, + 7.0145, + 8.14, + 9.343, + 10.594, + 12.313 + ], + "spotsPerBattle": [ + 0.648, + 0.789, + 0.903, + 1.008, + 1.11, + 1.214, + 1.337, + 1.474, + 1.671 + ], + "wpm": [ + 0.1582, + 0.1717, + 0.182, + 0.1908, + 0.1992, + 0.208, + 0.2179, + 0.2295, + 0.2459 + ], + "dpm": [ + 139.612, + 167.501, + 188.5304, + 207.8887, + 226.5041, + 246.3742, + 268.8646, + 297.8587, + 339.6516 + ], + "kpm": [ + 0.1511, + 0.1916, + 0.222, + 0.2502, + 0.2791, + 0.31, + 0.3448, + 0.39, + 0.463 + ], + "hitRate": [ + 69.3035, + 73.4495, + 75.77, + 77.587, + 79.0885, + 80.4375, + 81.807, + 83.251, + 84.895 + ], + "survivalRate": [ + 20.495, + 24.071, + 26.696, + 29.07, + 31.2725, + 33.632, + 36.387, + 39.651, + 44.9655 + ] + }, + "percentiles_player_count": 10000, + "meanSd": { + "winrateMean": 52.6691, + "winrateSd": 7.501, + "hitRateMean": 76.8419, + "hitRateSd": 11.8812, + "survivalRateMean": 31.8702, + "survivalRateSd": 10.2378, + "damageRatioMean": 1.1025, + "damageRatioSd": 0.4427, + "kdrMean": 1.2394, + "kdrSd": 0.7795, + "dpbMean": 608.4002, + "dpbSd": 217.8841, + "kpbMean": 0.774, + "kpbSd": 0.3449, + "hpbMean": 7.5795, + "hpbSd": 3.3543, + "spbMean": 1.1317, + "spbSd": 0.4135 + }, + "shotEff": 81.3044, + "last_update": 1678771055585 + }, + { + "_id": 17, + "battle_life_time": 106463, + "total_battle_life_time": 1064627816, + "number_of_players": 10000, + "tank_id": 17, + "all": { + "spotted": 707.8467, + "hits": 3128.9507, + "wins": 337.453, + "losses": 326.723, + "capture_points": 273.1936, + "battles": 673.9891, + "damage_dealt": 371207.8193, + "damage_received": 386415.786, + "shots": 4144.258, + "max_xp": 1191.7719, + "xp": 242667.5486, + "frags": 462.8709, + "survived_battles": 200.5843, + "dropped_capture_points": 595.6305 + }, + "special": { + "winrate": 50.068, + "damageRatio": 0.9606, + "kdr": 0.9777, + "damagePerBattle": 550.7623, + "killsPerBattle": 0.6868, + "hitsPerBattle": 4.6424, + "spotsPerBattle": 1.0502, + "wpm": 0.1902, + "dpm": 209.2043, + "kpm": 0.2609, + "hitRate": 75.5009, + "survivalRate": 29.7608 + }, + "percentiles": { + "winRate": [ + 43.103, + 45.24, + 46.8795, + 48.332, + 49.8065, + 51.196, + 52.918, + 55.1185, + 59.0235 + ], + "damageRatio": [ + 0.584, + 0.704, + 0.789, + 0.861, + 0.935, + 1.018, + 1.112, + 1.244, + 1.465 + ], + "kdr": [ + 0.485, + 0.617, + 0.726, + 0.827, + 0.9345, + 1.056, + 1.2155, + 1.428, + 1.845 + ], + "damagePerBattle": [ + 345.69, + 412.49, + 459.44, + 499.55, + 540.035, + 584.985, + 633.355, + 699.025, + 811.67 + ], + "killsPerBattle": [ + 0.375, + 0.465, + 0.533, + 0.596, + 0.659, + 0.73, + 0.811, + 0.918, + 1.102 + ], + "hitsPerBattle": [ + 3.195, + 3.69, + 4.068, + 4.362, + 4.647, + 4.9485, + 5.271, + 5.656, + 6.2055 + ], + "spotsPerBattle": [ + 0.629, + 0.752, + 0.8575, + 0.956, + 1.049, + 1.1465, + 1.262, + 1.4, + 1.603 + ], + "wpm": [ + 0.1528, + 0.1655, + 0.1743, + 0.1825, + 0.19, + 0.1979, + 0.2065, + 0.2171, + 0.2339 + ], + "dpm": [ + 131.2505, + 155.7725, + 174.1863, + 189.9051, + 205.3328, + 221.5702, + 240.8529, + 264.8677, + 307.253 + ], + "kpm": [ + 0.1463, + 0.1799, + 0.2042, + 0.2268, + 0.2493, + 0.2736, + 0.3036, + 0.3432, + 0.4089 + ], + "hitRate": [ + 65.617, + 70.051, + 72.553, + 74.549, + 76.2425, + 77.7385, + 79.2765, + 80.858, + 83.018 + ], + "survivalRate": [ + 20.444, + 23.447, + 25.6725, + 27.725, + 29.6715, + 31.6955, + 33.923, + 36.803, + 41.437 + ] + }, + "percentiles_player_count": 10000, + "meanSd": { + "winrateMean": 50.5356, + "winrateSd": 6.6334, + "hitRateMean": 74.5835, + "hitRateSd": 9.6505, + "survivalRateMean": 30.3145, + "survivalRateSd": 8.9155, + "damageRatioMean": 0.9912, + "damageRatioSd": 0.3822, + "kdrMean": 1.0923, + "kdrSd": 0.6887, + "dpbMean": 562.3008, + "dpbSd": 195.5684, + "kpbMean": 0.7088, + "kpbSd": 0.312, + "hpbMean": 4.6704, + "hpbSd": 1.2382, + "spbMean": 1.0833, + "spbSd": 0.3853 + }, + "shotEff": 154.9048, + "last_update": 1677561392794 + }, + { + "_id": 33, + "battle_life_time": 47339, + "total_battle_life_time": 473389371, + "number_of_players": 10000, + "tank_id": 33, + "all": { + "spotted": 318.6163, + "hits": 1918.026, + "wins": 161.9938, + "losses": 135.1226, + "capture_points": 163.6557, + "battles": 300.0921, + "damage_dealt": 199194.9486, + "damage_received": 183696.4883, + "shots": 2420.9167, + "max_xp": 1377.8881, + "xp": 148216.5586, + "frags": 257.9868, + "survived_battles": 103.0862, + "dropped_capture_points": 260.2245 + }, + "special": { + "winrate": 53.9814, + "damageRatio": 1.0844, + "kdr": 1.3095, + "damagePerBattle": 663.7794, + "killsPerBattle": 0.8597, + "hitsPerBattle": 6.3915, + "spotsPerBattle": 1.0617, + "wpm": 0.2053, + "dpm": 252.4707, + "kpm": 0.327, + "hitRate": 79.2273, + "survivalRate": 34.3515 + }, + "percentiles": { + "winRate": [ + 45.669, + 48.923, + 51.2915, + 53.4155, + 55.4195, + 57.519, + 59.783, + 62.553, + 66.882 + ], + "damageRatio": [ + 0.695, + 0.839, + 0.949, + 1.05, + 1.156, + 1.261, + 1.378, + 1.5385, + 1.7965 + ], + "kdr": [ + 0.705, + 0.915, + 1.086, + 1.254, + 1.424, + 1.617, + 1.86, + 2.1795, + 2.7895 + ], + "damagePerBattle": [ + 450.59, + 533.78, + 595.96, + 652.085, + 702.07, + 755.18, + 810.885, + 880.155, + 980.285 + ], + "killsPerBattle": [ + 0.521, + 0.653, + 0.743, + 0.831, + 0.912, + 0.994, + 1.09, + 1.208, + 1.382 + ], + "hitsPerBattle": [ + 4.8345, + 5.4535, + 5.933, + 6.3205, + 6.716, + 7.0945, + 7.508, + 8, + 8.7625 + ], + "spotsPerBattle": [ + 0.67, + 0.812, + 0.919, + 1.0105, + 1.096, + 1.188, + 1.284, + 1.401, + 1.568 + ], + "wpm": [ + 0.1667, + 0.1817, + 0.1923, + 0.2014, + 0.2098, + 0.2185, + 0.2272, + 0.2377, + 0.2524 + ], + "dpm": [ + 170.9212, + 203.7661, + 227.6935, + 247.3242, + 266.3126, + 284.356, + 303.8913, + 325.3216, + 359.4345 + ], + "kpm": [ + 0.2015, + 0.2506, + 0.2854, + 0.3168, + 0.3443, + 0.3722, + 0.4033, + 0.4413, + 0.4974 + ], + "hitRate": [ + 72.5495, + 75.8405, + 77.7595, + 79.216, + 80.451, + 81.597, + 82.783, + 83.9565, + 85.5015 + ], + "survivalRate": [ + 24.224, + 28.201, + 31.128, + 33.803, + 36.3355, + 38.8445, + 41.722, + 45.3425, + 51.186 + ] + }, + "percentiles_player_count": 10000, + "meanSd": { + "winrateMean": 55.9148, + "winrateSd": 8.2526, + "hitRateMean": 79.1161, + "hitRateSd": 8.4723, + "survivalRateMean": 36.9185, + "survivalRateSd": 10.7729, + "damageRatioMean": 1.2076, + "damageRatioSd": 0.454, + "kdrMean": 1.6293, + "kdrSd": 0.9557, + "dpbMean": 709.1817, + "dpbSd": 212.8413, + "kpbMean": 0.9358, + "kpbSd": 0.3445, + "hpbMean": 6.7378, + "hpbSd": 1.634, + "spbMean": 1.1089, + "spbSd": 0.3544 + }, + "shotEff": 117.2326, + "last_update": 1681791408996 + }, + { + "_id": 49, + "battle_life_time": 107010, + "total_battle_life_time": 1070098258, + "number_of_players": 10000, + "tank_id": 49, + "all": { + "spotted": 998.0054, + "hits": 3884.2635, + "wins": 334.7506, + "losses": 306.5336, + "capture_points": 69.2516, + "battles": 646.2226, + "damage_dealt": 789251.7596, + "damage_received": 715410.0777, + "shots": 4686.3514, + "max_xp": 1912.2171, + "xp": 465914.6865, + "frags": 489.4052, + "survived_battles": 205.6315, + "dropped_capture_points": 272.3055 + }, + "special": { + "winrate": 51.8011, + "damageRatio": 1.1032, + "kdr": 1.1108, + "damagePerBattle": 1221.3311, + "killsPerBattle": 0.7573, + "hitsPerBattle": 6.01 + } + } +] +*/ + +export const tankAverages = (await ( + await fetch('https://www.blitzstars.com/api/tankaverages.json') +).json()) as TankAverages; + +export type TankAverages = { + _id: number; + battle_life_time: number; + total_battle_life_time: number; + number_of_players: number; + tank_id: number; + all: AllStats; + special: { + winrate: number; + damageRatio: number; + kdr: number; + damagePerBattle: number; + killsPerBattle: number; + hitsPerBattle: number; + }; +}[]; diff --git a/src/utilities/wn8.ts b/src/utilities/wn8.ts new file mode 100644 index 000000000..0c35ecc02 --- /dev/null +++ b/src/utilities/wn8.ts @@ -0,0 +1,59 @@ +import { PeriodStatistics } from '../types/statistics.js'; +import { tankAverages } from './tankAverages.js'; + +// const expDmg = 2300; +// const expSpot = 1; +// const expFrag = 0.5; +// const expDef = 5; +// const expWinRate = 48; + +const expDmg = + tankAverages.reduce((a, b) => a + b.special.damagePerBattle, 0) / tankAverages.length; +const expSpot = + tankAverages.reduce((a, b) => a + (b.all.spotted / b.all.battles), 0) / tankAverages.length; +const expFrag = + tankAverages.reduce((a, b) => a + b.special.killsPerBattle, 0) / tankAverages.length; +const expDef = + tankAverages.reduce((a, b) => a + b.all.capture_points / b.all.battles, 0) / + tankAverages.length; +const expWinRate = + tankAverages.reduce((a, b) => a + b.special.winrate, 0) / tankAverages.length; + +export default function wn8(stats: PeriodStatistics) { + const rDAMAGE = stats.special.dpb / expDmg; + + const rDAMAGEc = Math.max(0, (rDAMAGE - 0.22) / (1 - 0.22)); + + const rSPOT = stats.special.spb / expSpot; + const rFRAG = stats.special.kpb / expFrag; + const rDEF = stats.all.capture_points / stats.all.battles / expDef; + const rWIN = stats.special.winrate / expWinRate; + + 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))); + + let rWINc = + 0.09 + + 0.613 * rDAMAGEc + + 0.131 * rFRAGc * rDAMAGEc + + 0.097 * rFRAGc * rSPOTc + + 0.047 * rFRAGc * rDEFc; + + rWINc = Math.max(0, (rWIN - 0.71) / (1 - 0.71)); + + const WN8 = + 980 * rDAMAGEc + + 210 * rDAMAGEc * rFRAGc + + 155 * rFRAGc * rSPOTc + + 75 * rDEFc * rFRAGc + + 145 * Math.min(1.8, rWINc); + + return WN8; +}