mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 12:09:51 +02:00
feat: wm8 and average tier calculation
This commit is contained in:
1 parent
c2fca42a86
commit
a12f969251
15 files changed
+315
-328
No files matched your search
+22
-18
@@ -1,8 +1,10 @@
|
|||||||
import { EmbedBuilder, SlashCommandBuilder } from 'discord.js';
|
import { SlashCommandBuilder } from 'discord.js';
|
||||||
import packageJSON from '../../package.json' assert { type: 'json' };
|
import packageJSON from '../../package.json' assert { type: 'json' };
|
||||||
import { ACCENT_COLOR } from '../constants/colors.js';
|
|
||||||
import { tankopedia } from '../core/blitz/tankopedia.js';
|
import { tankopedia } from '../core/blitz/tankopedia.js';
|
||||||
|
import { tankAverages } from '../core/blitzstars/tankaverages.js';
|
||||||
|
import cleanTable from '../core/interaction/cleanTable.js';
|
||||||
import cmdName from '../core/interaction/cmdName.js';
|
import cmdName from '../core/interaction/cmdName.js';
|
||||||
|
import infoEmbed from '../core/interaction/infoEmbed.js';
|
||||||
import getClientId from '../core/process/getClientId.js';
|
import getClientId from '../core/process/getClientId.js';
|
||||||
import { CommandRegistry } from '../events/interactionCreate.js';
|
import { CommandRegistry } from '../events/interactionCreate.js';
|
||||||
import { client } from '../index.js';
|
import { client } from '../index.js';
|
||||||
@@ -24,22 +26,24 @@ export default {
|
|||||||
|
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
embeds: [
|
embeds: [
|
||||||
new EmbedBuilder()
|
infoEmbed(
|
||||||
.setTitle(`${client.user?.username} debug information`)
|
`${client.user?.username} debug information`,
|
||||||
.setColor(ACCENT_COLOR)
|
cleanTable([
|
||||||
.setDescription(
|
['Version', packageJSON.version],
|
||||||
`**Version**: ${packageJSON.version}\n**Uptime**: ${Math.floor(
|
['Client ID', getClientId()],
|
||||||
uptime / 1000 / 60 / 60 / 24,
|
['Tag', interaction.client.user.tag],
|
||||||
)}d ${Math.floor((uptime / 1000 / 60 / 60) % 24)}h ${Math.floor(
|
[
|
||||||
(uptime / 1000 / 60) % 60,
|
'Uptime',
|
||||||
)}m ${Math.floor((uptime / 1000) % 60)}s ${Math.floor(
|
`${Math.floor(uptime / 1000 / 60 / 60 / 24)}d ${Math.floor(
|
||||||
uptime % 1000,
|
(uptime / 1000 / 60 / 60) % 24,
|
||||||
)}ms\n**Tankopedia**: ${
|
)}h ${Math.floor((uptime / 1000 / 60) % 60)}m ${Math.floor(
|
||||||
tankopedia ? 'cached' : 'not cached'
|
(uptime / 1000) % 60,
|
||||||
}\n**Client ID**: ${getClientId()}\n**Tag**: ${
|
)}s ${Math.floor(uptime % 1000)}ms`,
|
||||||
interaction.client.user.tag
|
],
|
||||||
}`,
|
['Tankopedia cached', `${tankopedia !== undefined}`],
|
||||||
),
|
['Tank averages cached', `${tankAverages !== undefined}`],
|
||||||
|
]),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+43
-49
@@ -3,7 +3,7 @@ import {
|
|||||||
ButtonBuilder,
|
ButtonBuilder,
|
||||||
SlashCommandBuilder,
|
SlashCommandBuilder,
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import GenericStats from '../components/GenericStats/index.js';
|
import GenericAllStats from '../components/GenericAllStats.js';
|
||||||
import NoBattlesInPeriod from '../components/NoBattlesInPeriod.js';
|
import NoBattlesInPeriod from '../components/NoBattlesInPeriod.js';
|
||||||
import PoweredByBlitzStars from '../components/PoweredByBlitzStars.js';
|
import PoweredByBlitzStars from '../components/PoweredByBlitzStars.js';
|
||||||
import TitleBar from '../components/TitleBar.js';
|
import TitleBar from '../components/TitleBar.js';
|
||||||
@@ -11,11 +11,14 @@ import Wrapper from '../components/Wrapper.js';
|
|||||||
import { BLITZ_SERVERS } from '../constants/servers.js';
|
import { BLITZ_SERVERS } from '../constants/servers.js';
|
||||||
import usernameAutocomplete from '../core/autocomplete/username.js';
|
import usernameAutocomplete from '../core/autocomplete/username.js';
|
||||||
import getBlitzAccount from '../core/blitz/getBlitzAccount.js';
|
import getBlitzAccount from '../core/blitz/getBlitzAccount.js';
|
||||||
|
import getWN8 from '../core/blitz/getWN8.js';
|
||||||
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
|
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
|
||||||
import sumStats from '../core/blitz/sumStats.js';
|
import sumStats from '../core/blitz/sumStats.js';
|
||||||
|
import { tankopedia } from '../core/blitz/tankopedia.js';
|
||||||
import getPeriodNow from '../core/blitzstars/getPeriodNow.js';
|
import getPeriodNow from '../core/blitzstars/getPeriodNow.js';
|
||||||
import getPeriodicStart from '../core/blitzstars/getPeriodStart.js';
|
import getPeriodicStart from '../core/blitzstars/getPeriodStart.js';
|
||||||
import getTankStatsOverTime from '../core/blitzstars/getTankStatsOverTime.js';
|
import getTankStatsOverTime from '../core/blitzstars/getTankStatsOverTime.js';
|
||||||
|
import { tankAverages } from '../core/blitzstars/tankaverages.js';
|
||||||
import cmdName from '../core/interaction/cmdName.js';
|
import cmdName from '../core/interaction/cmdName.js';
|
||||||
import fullBlitzStarsStats from '../core/interaction/fullBlitzStarsStats.js';
|
import fullBlitzStarsStats from '../core/interaction/fullBlitzStarsStats.js';
|
||||||
import { supportBlitzStars } from '../core/interaction/supportBlitzStars.js';
|
import { supportBlitzStars } from '../core/interaction/supportBlitzStars.js';
|
||||||
@@ -27,12 +30,16 @@ import addUsernameOption from '../core/options/addUsernameOption.js';
|
|||||||
import { args } from '../core/process/args.js';
|
import { args } from '../core/process/args.js';
|
||||||
import render from '../core/ui/render.js';
|
import render from '../core/ui/render.js';
|
||||||
import { CommandRegistry } from '../events/interactionCreate.js';
|
import { CommandRegistry } from '../events/interactionCreate.js';
|
||||||
import { AccountInfo, AllStats } from '../types/accountInfo.js';
|
import {
|
||||||
|
AccountInfo,
|
||||||
|
AllStats,
|
||||||
|
SupplementaryStats,
|
||||||
|
} from '../types/accountInfo.js';
|
||||||
import { PlayerClanData } from '../types/playerClanData.js';
|
import { PlayerClanData } from '../types/playerClanData.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -53,20 +60,41 @@ export default {
|
|||||||
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${args['wargaming-application-id']}&account_id=${id}&extra=clan`,
|
`https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${args['wargaming-application-id']}&account_id=${id}&extra=clan`,
|
||||||
);
|
);
|
||||||
let stats: AllStats;
|
let stats: AllStats;
|
||||||
|
let supplementaryStats: SupplementaryStats | undefined;
|
||||||
|
|
||||||
if (period === 'career') {
|
if (period === 'career') {
|
||||||
stats = accountInfo[id].statistics.all;
|
stats = accountInfo[id].statistics.all;
|
||||||
} else {
|
} else {
|
||||||
stats = sumStats(
|
const tankStatsOverTime = await getTankStatsOverTime(
|
||||||
Object.entries(
|
server,
|
||||||
await getTankStatsOverTime(
|
id,
|
||||||
server,
|
getPeriodicStart(period),
|
||||||
id,
|
getPeriodNow(),
|
||||||
getPeriodicStart(period),
|
|
||||||
getPeriodNow(),
|
|
||||||
),
|
|
||||||
).map(([, stats]) => stats),
|
|
||||||
);
|
);
|
||||||
|
const entries = Object.entries(tankStatsOverTime);
|
||||||
|
const totalBattles = entries.reduce(
|
||||||
|
(accumulator, [, stats]) => accumulator + stats.battles,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
supplementaryStats = {
|
||||||
|
tier:
|
||||||
|
entries.reduce((accumulator, [tankIdString, stats]) => {
|
||||||
|
const tankId = Number(tankIdString);
|
||||||
|
const tankTier = tankopedia[tankId].tier;
|
||||||
|
|
||||||
|
return accumulator + tankTier * stats.battles;
|
||||||
|
}, 0) / totalBattles,
|
||||||
|
wn8:
|
||||||
|
entries.reduce((accumulator, [tankIdString, stats]) => {
|
||||||
|
const tankId = Number(tankIdString);
|
||||||
|
const tankWn8 = getWN8(tankAverages[tankId].all, stats);
|
||||||
|
|
||||||
|
return accumulator + tankWn8 * stats.battles;
|
||||||
|
}, 0) / totalBattles,
|
||||||
|
};
|
||||||
|
|
||||||
|
stats = sumStats(entries.map(([, stats]) => stats));
|
||||||
}
|
}
|
||||||
|
|
||||||
const image = await render(
|
const image = await render(
|
||||||
@@ -88,43 +116,9 @@ export default {
|
|||||||
|
|
||||||
{stats.battles === 0 && <NoBattlesInPeriod />}
|
{stats.battles === 0 && <NoBattlesInPeriod />}
|
||||||
{stats.battles > 0 && (
|
{stats.battles > 0 && (
|
||||||
<GenericStats
|
<GenericAllStats
|
||||||
stats={[
|
stats={stats}
|
||||||
[
|
supplementaryStats={supplementaryStats}
|
||||||
'Winrate',
|
|
||||||
`${(100 * (stats.wins / stats.battles)).toFixed(2)}%`,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Survival',
|
|
||||||
`${(100 * (stats.survived_battles / stats.battles)).toFixed(
|
|
||||||
2,
|
|
||||||
)}%`,
|
|
||||||
],
|
|
||||||
['Accuracy', `${((stats.hits / stats.shots) * 100).toFixed(2)}%`],
|
|
||||||
['Battles', stats.battles],
|
|
||||||
['Wins', stats.wins],
|
|
||||||
['Losses', stats.losses],
|
|
||||||
[
|
|
||||||
'Average damage',
|
|
||||||
(stats.damage_dealt / stats.battles).toFixed(0),
|
|
||||||
],
|
|
||||||
['Average XP', (stats.xp / stats.battles).toFixed(0)],
|
|
||||||
['Average shots', (stats.shots / stats.battles).toFixed(2)],
|
|
||||||
['Average hits', (stats.hits / stats.battles).toFixed(2)],
|
|
||||||
['Average kills', (stats.frags / stats.battles).toFixed(2)],
|
|
||||||
['Average spots', (stats.spotted / stats.battles).toFixed(2)],
|
|
||||||
[
|
|
||||||
'Damage ratio',
|
|
||||||
(stats.damage_dealt / stats.damage_received).toFixed(2),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Kills to death ratio',
|
|
||||||
(
|
|
||||||
stats.frags /
|
|
||||||
(stats.battles - stats.survived_battles)
|
|
||||||
).toFixed(2),
|
|
||||||
],
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
+22
-51
@@ -3,7 +3,7 @@ import {
|
|||||||
ButtonBuilder,
|
ButtonBuilder,
|
||||||
SlashCommandBuilder,
|
SlashCommandBuilder,
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import GenericStats from '../components/GenericStats/index.js';
|
import GenericAllStats from '../components/GenericAllStats.js';
|
||||||
import NoBattlesInPeriod from '../components/NoBattlesInPeriod.js';
|
import NoBattlesInPeriod from '../components/NoBattlesInPeriod.js';
|
||||||
import PoweredByBlitzStars from '../components/PoweredByBlitzStars.js';
|
import PoweredByBlitzStars from '../components/PoweredByBlitzStars.js';
|
||||||
import TitleBar from '../components/TitleBar.js';
|
import TitleBar from '../components/TitleBar.js';
|
||||||
@@ -12,6 +12,7 @@ import { BLITZ_SERVERS } from '../constants/servers.js';
|
|||||||
import tanksAutocomplete from '../core/autocomplete/tanks.js';
|
import tanksAutocomplete from '../core/autocomplete/tanks.js';
|
||||||
import usernameAutocomplete from '../core/autocomplete/username.js';
|
import usernameAutocomplete from '../core/autocomplete/username.js';
|
||||||
import getBlitzAccount from '../core/blitz/getBlitzAccount.js';
|
import getBlitzAccount from '../core/blitz/getBlitzAccount.js';
|
||||||
|
import getWN8 from '../core/blitz/getWN8.js';
|
||||||
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
|
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
|
||||||
import resolveTankId from '../core/blitz/resolveTankId.js';
|
import resolveTankId from '../core/blitz/resolveTankId.js';
|
||||||
import resolveTankName from '../core/blitz/resolveTankName.js';
|
import resolveTankName from '../core/blitz/resolveTankName.js';
|
||||||
@@ -21,6 +22,7 @@ import getPeriodicStart from '../core/blitzstars/getPeriodStart.js';
|
|||||||
import getTankStatsOverTime, {
|
import getTankStatsOverTime, {
|
||||||
emptyAllStats,
|
emptyAllStats,
|
||||||
} from '../core/blitzstars/getTankStatsOverTime.js';
|
} from '../core/blitzstars/getTankStatsOverTime.js';
|
||||||
|
import { tankAverages } from '../core/blitzstars/tankaverages.js';
|
||||||
import cmdName from '../core/interaction/cmdName.js';
|
import cmdName from '../core/interaction/cmdName.js';
|
||||||
import fullBlitzStarsStats from '../core/interaction/fullBlitzStarsStats.js';
|
import fullBlitzStarsStats from '../core/interaction/fullBlitzStarsStats.js';
|
||||||
import { supportBlitzStars } from '../core/interaction/supportBlitzStars.js';
|
import { supportBlitzStars } from '../core/interaction/supportBlitzStars.js';
|
||||||
@@ -38,7 +40,7 @@ import { TanksStats } from '../types/tanksStats.js';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
@@ -64,28 +66,29 @@ export default {
|
|||||||
`https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${args['wargaming-application-id']}&account_id=${id}`,
|
`https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${args['wargaming-application-id']}&account_id=${id}`,
|
||||||
);
|
);
|
||||||
let stats: AllStats;
|
let stats: AllStats;
|
||||||
|
const tankId = Number(tank);
|
||||||
|
|
||||||
if (period === 'career') {
|
if (period === 'career') {
|
||||||
stats =
|
stats =
|
||||||
tankStats[id].find((stats) => stats.tank_id === Number(tank))?.all ??
|
tankStats[id].find((stats) => stats.tank_id === tankId)?.all ??
|
||||||
emptyAllStats;
|
emptyAllStats;
|
||||||
} else {
|
} else {
|
||||||
stats = (
|
const tankStatsOverTime = await getTankStatsOverTime(
|
||||||
await getTankStatsOverTime(
|
server,
|
||||||
server,
|
id,
|
||||||
id,
|
getPeriodicStart(period),
|
||||||
getPeriodicStart(period),
|
getPeriodNow(),
|
||||||
getPeriodNow(),
|
);
|
||||||
)
|
|
||||||
)[Number(tank)];
|
stats = tankStatsOverTime[tankId] ?? emptyAllStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
const image = await render(
|
const image = await render(
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<TitleBar
|
<TitleBar
|
||||||
name={accountInfo[id].nickname}
|
name={accountInfo[id].nickname}
|
||||||
nameDiscriminator={`(${resolveTankName(Number(tank))})`}
|
nameDiscriminator={`(${resolveTankName(tankId)})`}
|
||||||
image={tankopedia[Number(tank)].images.normal}
|
image={tankopedia[tankId].images.normal}
|
||||||
description={`${
|
description={`${
|
||||||
statPeriodNames[period]
|
statPeriodNames[period]
|
||||||
} • ${new Date().toDateString()} • ${BLITZ_SERVERS[server]}`}
|
} • ${new Date().toDateString()} • ${BLITZ_SERVERS[server]}`}
|
||||||
@@ -93,44 +96,12 @@ export default {
|
|||||||
|
|
||||||
{stats.battles === 0 && <NoBattlesInPeriod />}
|
{stats.battles === 0 && <NoBattlesInPeriod />}
|
||||||
{stats.battles > 0 && (
|
{stats.battles > 0 && (
|
||||||
// TODO: GenericStats generator for AllStats
|
<GenericAllStats
|
||||||
<GenericStats
|
stats={stats}
|
||||||
stats={[
|
supplementaryStats={{
|
||||||
[
|
tier: tankopedia[tankId].tier,
|
||||||
'Winrate',
|
wn8: getWN8(tankAverages[tankId].all, stats),
|
||||||
`${(100 * (stats.wins / stats.battles)).toFixed(2)}%`,
|
}}
|
||||||
],
|
|
||||||
[
|
|
||||||
'Survival',
|
|
||||||
`${(100 * (stats.survived_battles / stats.battles)).toFixed(
|
|
||||||
2,
|
|
||||||
)}%`,
|
|
||||||
],
|
|
||||||
['Accuracy', `${((stats.hits / stats.shots) * 100).toFixed(2)}%`],
|
|
||||||
['Battles', stats.battles],
|
|
||||||
['Wins', stats.wins],
|
|
||||||
['Losses', stats.losses],
|
|
||||||
[
|
|
||||||
'Average damage',
|
|
||||||
(stats.damage_dealt / stats.battles).toFixed(0),
|
|
||||||
],
|
|
||||||
['Average XP', (stats.xp / stats.battles).toFixed(0)],
|
|
||||||
['Average shots', (stats.shots / stats.battles).toFixed(2)],
|
|
||||||
['Average hits', (stats.hits / stats.battles).toFixed(2)],
|
|
||||||
['Average kills', (stats.frags / stats.battles).toFixed(2)],
|
|
||||||
['Average spots', (stats.spotted / stats.battles).toFixed(2)],
|
|
||||||
[
|
|
||||||
'Damage ratio',
|
|
||||||
(stats.damage_dealt / stats.damage_received).toFixed(2),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'Kills to death ratio',
|
|
||||||
(
|
|
||||||
stats.frags /
|
|
||||||
(stats.battles - stats.survived_battles)
|
|
||||||
).toFixed(2),
|
|
||||||
],
|
|
||||||
]}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import { TanksStats } from '../types/tanksStats.js';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inDevelopment: false,
|
inDevelopment: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: new SlashCommandBuilder()
|
command: new SlashCommandBuilder()
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { AllStats, SupplementaryStats } from '../types/accountInfo.js';
|
||||||
|
import GenericStats from './GenericStats/index.js';
|
||||||
|
|
||||||
|
export interface GenericAllStatsProps {
|
||||||
|
stats: AllStats;
|
||||||
|
supplementaryStats?: SupplementaryStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function GenericAllStats({
|
||||||
|
stats,
|
||||||
|
supplementaryStats,
|
||||||
|
}: GenericAllStatsProps) {
|
||||||
|
return (
|
||||||
|
<GenericStats
|
||||||
|
stats={[
|
||||||
|
['Winrate', `${(100 * (stats.wins / stats.battles)).toFixed(2)}%`],
|
||||||
|
['WN8', supplementaryStats?.wn8.toFixed(0) ?? -Infinity],
|
||||||
|
[
|
||||||
|
'Survival',
|
||||||
|
`${(100 * (stats.survived_battles / stats.battles)).toFixed(2)}%`,
|
||||||
|
],
|
||||||
|
['Accuracy', `${((stats.hits / stats.shots) * 100).toFixed(2)}%`],
|
||||||
|
['Battles', stats.battles],
|
||||||
|
['Wins', stats.wins],
|
||||||
|
['Losses', stats.losses],
|
||||||
|
['Average damage', (stats.damage_dealt / stats.battles).toFixed(0)],
|
||||||
|
['Average tier', supplementaryStats?.tier.toFixed(2) ?? -Infinity],
|
||||||
|
['Average XP', (stats.xp / stats.battles).toFixed(0)],
|
||||||
|
['Average shots', (stats.shots / stats.battles).toFixed(2)],
|
||||||
|
['Average hits', (stats.hits / stats.battles).toFixed(2)],
|
||||||
|
['Average kills', (stats.frags / stats.battles).toFixed(2)],
|
||||||
|
['Average spots', (stats.spotted / stats.battles).toFixed(2)],
|
||||||
|
[
|
||||||
|
'Damage ratio',
|
||||||
|
(stats.damage_dealt / stats.damage_received).toFixed(2),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Kills to death ratio',
|
||||||
|
(stats.frags / (stats.battles - stats.survived_battles)).toFixed(2),
|
||||||
|
],
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -11,7 +11,9 @@ export default function GenericStats({
|
|||||||
stats,
|
stats,
|
||||||
columnCount = 2,
|
columnCount = 2,
|
||||||
}: GenericStatsProps) {
|
}: GenericStatsProps) {
|
||||||
const filteredStats = stats.filter(([, value]) => value !== `${-Infinity}`);
|
const filteredStats = stats.filter(
|
||||||
|
([, value]) => `${value}` !== `${-Infinity}`,
|
||||||
|
);
|
||||||
const itemsPerRow = Math.ceil(filteredStats.length / columnCount);
|
const itemsPerRow = Math.ceil(filteredStats.length / columnCount);
|
||||||
const columns: Stat[][] = [];
|
const columns: Stat[][] = [];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { AllStats } from '../../types/accountInfo.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formula from https://web.archive.org/web/20190105064817/http://wiki.wnefficiency.net/pages/WN8
|
||||||
|
*/
|
||||||
|
export default function getWN8(expected: AllStats, average: AllStats) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
+31
-127
@@ -1,145 +1,49 @@
|
|||||||
import { args } from '../process/args.js';
|
import { WargamingResponse } from '../../types/wargamingResponse.js';
|
||||||
import isDev from '../process/isDev.js';
|
import isDev from '../process/isDev.js';
|
||||||
import URLToBase64 from '../ui/URLToBase64.js';
|
import URLToBase64 from '../ui/URLToBase64.js';
|
||||||
import getWargamingResponse from './getWargamingResponse.js';
|
|
||||||
|
/*
|
||||||
|
"1": {
|
||||||
|
"name": "T-34",
|
||||||
|
"nation": "ussr",
|
||||||
|
"is_premium": false,
|
||||||
|
"tier": 5,
|
||||||
|
"cost": {
|
||||||
|
"price_credit": 400000,
|
||||||
|
"price_gold": 0
|
||||||
|
},
|
||||||
|
"images": {
|
||||||
|
"preview": "/api/bs-cache/tank-icons/https%3A%2F%2Fglossary-eu-static.gcdn.co%2Ficons%2Fwotb%2Fcurrent%2Fuploaded%2Fvehicles%2Fhd_thumbnail%2FT-34.png",
|
||||||
|
"normal": "http://glossary-eu-static.gcdn.co/icons/wotb/current/uploaded/vehicles/hd/T-34.png"
|
||||||
|
},
|
||||||
|
"tank_id": 1,
|
||||||
|
"type": "mediumTank",
|
||||||
|
"description": "The legend of the Soviet armored forces and the most widely-produced Soviet tank of World War II, with a total of 33,805 vehicles manufactured. Three variants of this model were produced at several Soviet factories from 1940 through 1944."
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
|
||||||
export interface Tankopedia {
|
export interface Tankopedia {
|
||||||
[id: number]: {
|
[id: number]: {
|
||||||
description: string;
|
|
||||||
engines: number[];
|
|
||||||
guns: number[];
|
|
||||||
is_premium: boolean;
|
|
||||||
name: string;
|
name: string;
|
||||||
nation: string;
|
nation: string;
|
||||||
next_tanks: { [id: number]: number };
|
is_premium: boolean;
|
||||||
prices_xp: { [id: number]: number };
|
|
||||||
suspensions: number[];
|
|
||||||
tank_id: number;
|
|
||||||
tier: number;
|
tier: number;
|
||||||
turrets: number[] | null;
|
cost: { price_credit: number; price_gold: number };
|
||||||
|
images: { preview: string; normal: string };
|
||||||
|
tank_id: number;
|
||||||
type: string;
|
type: string;
|
||||||
cost: {
|
description: string;
|
||||||
price_credit: number;
|
|
||||||
price_gold: number;
|
|
||||||
};
|
|
||||||
default_profile: {
|
|
||||||
battle_level_range_max: number;
|
|
||||||
battle_level_range_min: number;
|
|
||||||
engine_id: number;
|
|
||||||
firepower: number;
|
|
||||||
gun_id: number;
|
|
||||||
hp: number;
|
|
||||||
hull_hp: number;
|
|
||||||
hull_weight: number;
|
|
||||||
is_default: boolean;
|
|
||||||
maneuverability: number;
|
|
||||||
max_ammo: number;
|
|
||||||
max_weight: number;
|
|
||||||
profile_id: string;
|
|
||||||
protection: number;
|
|
||||||
shot_efficiency: number;
|
|
||||||
signal_range: number;
|
|
||||||
speed_backward: number;
|
|
||||||
suspension_id: number;
|
|
||||||
turret_id: number;
|
|
||||||
weight: number;
|
|
||||||
armor: {
|
|
||||||
hull: {
|
|
||||||
front: number;
|
|
||||||
rear: number;
|
|
||||||
sides: number;
|
|
||||||
};
|
|
||||||
turret: {
|
|
||||||
front: number;
|
|
||||||
rear: number;
|
|
||||||
sides: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
engine: {
|
|
||||||
fire_chance: number;
|
|
||||||
name: string;
|
|
||||||
power: number;
|
|
||||||
tier: number;
|
|
||||||
weight: number;
|
|
||||||
};
|
|
||||||
gun: {
|
|
||||||
aim_time: number;
|
|
||||||
caliber: number;
|
|
||||||
clip_capacity: number;
|
|
||||||
clip_reload_time: number;
|
|
||||||
dispersion: number;
|
|
||||||
fire_rate: number;
|
|
||||||
move_down_arc: number;
|
|
||||||
move_up_arc: number;
|
|
||||||
name: string;
|
|
||||||
reload_time: number;
|
|
||||||
tier: number;
|
|
||||||
traverse_speed: number;
|
|
||||||
weight: number;
|
|
||||||
};
|
|
||||||
shells: {
|
|
||||||
damage: number;
|
|
||||||
penetration: number;
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
suspension: {
|
|
||||||
load_limit: number;
|
|
||||||
name: string;
|
|
||||||
tier: number;
|
|
||||||
traverse_speed: number;
|
|
||||||
weight: number;
|
|
||||||
};
|
|
||||||
turret: {
|
|
||||||
hp: number;
|
|
||||||
name: string;
|
|
||||||
tier: number;
|
|
||||||
traverse_left_arc: number;
|
|
||||||
traverse_right_arc: number;
|
|
||||||
traverse_speed: number;
|
|
||||||
view_range: number;
|
|
||||||
weight: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
images: {
|
|
||||||
normal: string;
|
|
||||||
preview: string;
|
|
||||||
};
|
|
||||||
modules_tree: {
|
|
||||||
[id: number]: {
|
|
||||||
is_default: boolean;
|
|
||||||
module_id: number;
|
|
||||||
name: string;
|
|
||||||
next_modules: number[] | null;
|
|
||||||
next_tanks: number[] | null;
|
|
||||||
price_credit: number;
|
|
||||||
price_xp: number;
|
|
||||||
type: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TankopediaInfo {
|
|
||||||
achievement_sections: { [name: string]: { name: string; order: number } };
|
|
||||||
tanks_updated_at: number;
|
|
||||||
languages: { [name: string]: string };
|
|
||||||
vehicle_types: { [name: string]: string };
|
|
||||||
vehicle_nations: { [name: string]: string };
|
|
||||||
game_version: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Caching tankopedia...');
|
console.log('Caching tankopedia...');
|
||||||
export const tankopedia = await getWargamingResponse<Tankopedia>(
|
export const tankopedia = (
|
||||||
`https://api.wotblitz.com/wotb/encyclopedia/vehicles/?application_id=${args['wargaming-application-id']}`,
|
(await (
|
||||||
);
|
await fetch('https://www.blitzstars.com/bs-tankopedia.json')
|
||||||
|
).json()) as WargamingResponse<Tankopedia> & { status: 'ok' }
|
||||||
|
).data;
|
||||||
console.log('Cached tankopedia');
|
console.log('Cached tankopedia');
|
||||||
|
|
||||||
console.log('Caching tankopedia info...');
|
|
||||||
export const tankopediaInfo = await getWargamingResponse<TankopediaInfo>(
|
|
||||||
`https://api.wotblitz.com/wotb/encyclopedia/info/?application_id=${args['wargaming-application-id']}`,
|
|
||||||
);
|
|
||||||
console.log('Cached tankopedia info');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,8 +3,16 @@ import { StatPeriod } from '../options/addStatPeriodChoices.js';
|
|||||||
export default function getPeriodicStart(period: StatPeriod) {
|
export default function getPeriodicStart(period: StatPeriod) {
|
||||||
if (period === 'career') {
|
if (period === 'career') {
|
||||||
return -Infinity;
|
return -Infinity;
|
||||||
|
} else if (period === 'today') {
|
||||||
|
const now = new Date();
|
||||||
|
now.setHours(5, 0, 0, 0);
|
||||||
|
|
||||||
|
// If it's after 5:00 AM today, subtract one day
|
||||||
|
if (now.getTime() > Date.now()) now.setDate(now.getDate() - 1);
|
||||||
|
|
||||||
|
return now.getTime() / 1000;
|
||||||
} else {
|
} else {
|
||||||
const daysAgo = period === 'today' ? 0 : Number(period) - 1;
|
const daysAgo = Number(period) - 1;
|
||||||
const periodStart = new Date();
|
const periodStart = new Date();
|
||||||
|
|
||||||
periodStart.setDate(periodStart.getDate() - daysAgo);
|
periodStart.setDate(periodStart.getDate() - daysAgo);
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { CacheType, ChatInputCommandInteraction } from 'discord.js';
|
|
||||||
import { PlayerStats } from '../../types/playerStats.js';
|
|
||||||
|
|
||||||
export default async function getPlayerStats(
|
|
||||||
interaction: ChatInputCommandInteraction<CacheType>,
|
|
||||||
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);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -47,8 +47,8 @@ export type TankHistory = TankHistoryNode[];
|
|||||||
export default async function getTankStatsOverTime(
|
export default async function getTankStatsOverTime(
|
||||||
server: BlitzServer,
|
server: BlitzServer,
|
||||||
id: number,
|
id: number,
|
||||||
start = -Infinity,
|
start: number,
|
||||||
end = Infinity,
|
end: number,
|
||||||
) {
|
) {
|
||||||
const latestTankStatsRaw = await getWargamingResponse<TanksStats>(
|
const latestTankStatsRaw = await getWargamingResponse<TanksStats>(
|
||||||
`https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${args['wargaming-application-id']}&account_id=${id}`,
|
`https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${args['wargaming-application-id']}&account_id=${id}`,
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { AllStats } from '../../types/accountInfo.js';
|
||||||
|
|
||||||
|
export interface SpecialStats {
|
||||||
|
winrate: number;
|
||||||
|
damageRatio: number;
|
||||||
|
kdr: number;
|
||||||
|
damagePerBattle: number;
|
||||||
|
killsPerBattle: number;
|
||||||
|
hitsPerBattle: number;
|
||||||
|
spotsPerBattle: number;
|
||||||
|
wpm: number;
|
||||||
|
dpm: number;
|
||||||
|
kpm: number;
|
||||||
|
hitRate: number;
|
||||||
|
survivalRate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Percentiles = [
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface SpecialPercentiles {
|
||||||
|
winRate: Percentiles;
|
||||||
|
damageRatio: Percentiles;
|
||||||
|
kdr: Percentiles;
|
||||||
|
damagePerBattle: Percentiles;
|
||||||
|
killsPerBattle: Percentiles;
|
||||||
|
hitsPerBattle: Percentiles;
|
||||||
|
spotsPerBattle: Percentiles;
|
||||||
|
wpm: Percentiles;
|
||||||
|
dpm: Percentiles;
|
||||||
|
kpm: Percentiles;
|
||||||
|
hitRate: Percentiles;
|
||||||
|
survivalRate: Percentiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MeanSd {
|
||||||
|
winrateMean: number;
|
||||||
|
winrateSd: number;
|
||||||
|
hitRateMean: number;
|
||||||
|
hitRateSd: number;
|
||||||
|
survivalRateMean: number;
|
||||||
|
survivalRateSd: number;
|
||||||
|
damageRatioMean: number;
|
||||||
|
damageRatioSd: number;
|
||||||
|
kdrMean: number;
|
||||||
|
kdrSd: number;
|
||||||
|
dpbMean: number;
|
||||||
|
dpbSd: number;
|
||||||
|
kpbMean: number;
|
||||||
|
kpbSd: number;
|
||||||
|
hpbMean: number;
|
||||||
|
hpbSd: number;
|
||||||
|
spbMean: number;
|
||||||
|
spbSd: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IndividualTankAverages {
|
||||||
|
_id: number;
|
||||||
|
battle_life_time: number;
|
||||||
|
total_battle_life_time: number;
|
||||||
|
number_of_players: number;
|
||||||
|
tank_id: number;
|
||||||
|
all: AllStats;
|
||||||
|
special: SpecialStats;
|
||||||
|
percentiles: SpecialPercentiles;
|
||||||
|
percentiles_player_count: number;
|
||||||
|
meanSd: MeanSd;
|
||||||
|
shotEff: number;
|
||||||
|
last_update: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Caching tank averages...');
|
||||||
|
const tankAveragesRaw = (await (
|
||||||
|
await fetch('https://www.blitzstars.com/api/tankaverages.json')
|
||||||
|
).json()) as IndividualTankAverages[];
|
||||||
|
export const tankAverages: Record<number, IndividualTankAverages> = {};
|
||||||
|
|
||||||
|
Object.entries(tankAveragesRaw).forEach(([, tank]) => {
|
||||||
|
tankAverages[tank.tank_id] = tank;
|
||||||
|
});
|
||||||
|
console.log('Tank averages cached');
|
||||||
@@ -18,16 +18,9 @@ export interface AllStats {
|
|||||||
dropped_capture_points: number;
|
dropped_capture_points: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpecialStats {
|
export interface SupplementaryStats {
|
||||||
winrate: number;
|
wn8: number;
|
||||||
damageRatio: number;
|
tier: number;
|
||||||
kdr: number;
|
|
||||||
dpb: number;
|
|
||||||
kpb: number;
|
|
||||||
hpb: number;
|
|
||||||
spb: number;
|
|
||||||
hitRate: number;
|
|
||||||
survivalRate: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClanStats {
|
export interface ClanStats {
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import { BlitzStartsComputedPeriodicStatistics } from './statistics.js';
|
|
||||||
|
|
||||||
export type PlayerStats = {
|
|
||||||
clan: {
|
|
||||||
clan_id: number;
|
|
||||||
name: string;
|
|
||||||
tag: string;
|
|
||||||
};
|
|
||||||
statistics: BlitzStartsComputedPeriodicStatistics;
|
|
||||||
_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;
|
|
||||||
}[];
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { AllStats, SpecialStats } from './accountInfo.js';
|
|
||||||
|
|
||||||
export interface BlitzStartsComputedPeriodicStatistics {
|
|
||||||
all: AllStats;
|
|
||||||
|
|
||||||
wn7: number;
|
|
||||||
wn8: number;
|
|
||||||
avg_tier: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PeriodStatistics
|
|
||||||
extends BlitzStartsComputedPeriodicStatistics {
|
|
||||||
special: SpecialStats;
|
|
||||||
pwp: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlayerPeriodicStatsCollection {
|
|
||||||
statistics: PeriodStatistics;
|
|
||||||
period30d: PeriodStatistics;
|
|
||||||
period90d: PeriodStatistics;
|
|
||||||
|
|
||||||
_id: string;
|
|
||||||
nickname: string;
|
|
||||||
nickname_lowercase: string;
|
|
||||||
region: string;
|
|
||||||
last_battle_time: number;
|
|
||||||
__v: number;
|
|
||||||
updatedAt: string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user