mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02:00
feat: added WN8 to /today
This commit is contained in:
1 parent
c5d14758d5
commit
f801783358
14 files changed
+163
-51
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { SlashCommandBuilder } from 'discord.js';
|
||||
import packageJSON from '../../package.json' assert { type: 'json' };
|
||||
import { tankopedia } from '../core/blitz/tankopedia.js';
|
||||
import { tankAverages } from '../core/blitzstars/tankaverages.js';
|
||||
import { tankAverages } from '../core/blitzstars/tankAverages.js';
|
||||
import cleanTable from '../core/interaction/cleanTable.js';
|
||||
import cmdName from '../core/interaction/cmdName.js';
|
||||
import infoEmbed from '../core/interaction/infoEmbed.js';
|
||||
|
||||
@@ -18,7 +18,7 @@ import { tankopedia } from '../core/blitz/tankopedia.js';
|
||||
import getPeriodNow from '../core/blitzstars/getPeriodNow.js';
|
||||
import getPeriodicStart from '../core/blitzstars/getPeriodStart.js';
|
||||
import getTankStatsOverTime from '../core/blitzstars/getTankStatsOverTime.js';
|
||||
import { tankAverages } from '../core/blitzstars/tankaverages.js';
|
||||
import { tankAverages } from '../core/blitzstars/tankAverages.js';
|
||||
import cmdName from '../core/interaction/cmdName.js';
|
||||
import fullBlitzStarsStats from '../core/interaction/fullBlitzStarsStats.js';
|
||||
import { supportBlitzStars } from '../core/interaction/supportBlitzStars.js';
|
||||
|
||||
@@ -22,7 +22,7 @@ import getPeriodicStart from '../core/blitzstars/getPeriodStart.js';
|
||||
import getTankStatsOverTime, {
|
||||
emptyAllStats,
|
||||
} from '../core/blitzstars/getTankStatsOverTime.js';
|
||||
import { tankAverages } from '../core/blitzstars/tankaverages.js';
|
||||
import { tankAverages } from '../core/blitzstars/tankAverages.js';
|
||||
import cmdName from '../core/interaction/cmdName.js';
|
||||
import fullBlitzStarsStats from '../core/interaction/fullBlitzStarsStats.js';
|
||||
import { supportBlitzStars } from '../core/interaction/supportBlitzStars.js';
|
||||
|
||||
+91
-26
@@ -7,18 +7,21 @@ import * as Breakdown from '../components/Breakdown/index.js';
|
||||
import NoBattlesInPeriod from '../components/NoBattlesInPeriod.js';
|
||||
import PoweredByBlitzStars from '../components/PoweredByBlitzStars.js';
|
||||
import TitleBar from '../components/TitleBar.js';
|
||||
import Wrapper from '../components/Wrapper.js';
|
||||
import Wrapper, { WrapperSize } from '../components/Wrapper.js';
|
||||
import { BLITZ_SERVERS } from '../constants/servers.js';
|
||||
import usernameAutocomplete from '../core/autocomplete/username.js';
|
||||
import getBlitzAccount from '../core/blitz/getBlitzAccount.js';
|
||||
import getWN8 from '../core/blitz/getWN8.js';
|
||||
import getWargamingResponse from '../core/blitz/getWargamingResponse.js';
|
||||
import resolveTankName from '../core/blitz/resolveTankName.js';
|
||||
import sumStats from '../core/blitz/sumStats.js';
|
||||
import { tankopedia } from '../core/blitz/tankopedia.js';
|
||||
import getTankStatsOverTime from '../core/blitzstars/getTankStatsOverTime.js';
|
||||
import last5AM from '../core/blitzstars/last5AM.js';
|
||||
import { tankAverages } from '../core/blitzstars/tankAverages.js';
|
||||
import cmdName from '../core/interaction/cmdName.js';
|
||||
import fullBlitzStarsStats from '../core/interaction/fullBlitzStarsStats.js';
|
||||
import infoEmbed from '../core/interaction/infoEmbed.js';
|
||||
import { supportBlitzStars } from '../core/interaction/supportBlitzStars.js';
|
||||
import addUsernameOption from '../core/options/addUsernameOption.js';
|
||||
import { args } from '../core/process/args.js';
|
||||
@@ -75,36 +78,88 @@ export default {
|
||||
tankStatsOverTime[0] = accumulatedStats;
|
||||
}
|
||||
|
||||
const rows = Object.entries(tankStatsOverTime).map(
|
||||
([tankId, tankStats]) => {
|
||||
const career = careerStats[tankId as unknown as number];
|
||||
const tankStatsOverTimeEntries = Object.entries(tankStatsOverTime);
|
||||
const todayWN8s = tankStatsOverTimeEntries.reduce<Record<number, number>>(
|
||||
(accumulator, [tankIdString, tankStats]) => {
|
||||
const tankId = Number(tankIdString);
|
||||
|
||||
return (
|
||||
<Breakdown.Row
|
||||
key={tankId}
|
||||
name={tankId === '0' ? 'Today' : resolveTankName(Number(tankId))}
|
||||
winrate={tankStats.wins / tankStats.battles}
|
||||
careerWinrate={career.wins / career.battles}
|
||||
WN8={-Infinity}
|
||||
careerWN8={-Infinity}
|
||||
damage={tankStats.damage_dealt / tankStats.battles}
|
||||
careerDamage={career.damage_dealt / career.battles}
|
||||
survival={tankStats.survived_battles / tankStats.battles}
|
||||
careerSurvival={career.survived_battles / career.battles}
|
||||
battles={tankStats.battles}
|
||||
careerBattles={career.battles}
|
||||
icon={
|
||||
tankId === '0'
|
||||
? undefined
|
||||
: tankopedia[tankId as unknown as number].images.normal
|
||||
}
|
||||
/>
|
||||
);
|
||||
return tankId === 0
|
||||
? accumulator
|
||||
: {
|
||||
...accumulator,
|
||||
[tankId]: getWN8(tankAverages[tankId].all, tankStats),
|
||||
};
|
||||
},
|
||||
{},
|
||||
);
|
||||
const careerWN8s = tankStatsOverTimeEntries.reduce<Record<number, number>>(
|
||||
(accumulator, [tankIdString, tankStats]) => {
|
||||
const tankId = Number(tankIdString);
|
||||
|
||||
return tankId === 0
|
||||
? accumulator
|
||||
: {
|
||||
...accumulator,
|
||||
[tankId]: getWN8(tankAverages[tankId].all, careerStats[tankId]),
|
||||
};
|
||||
},
|
||||
{},
|
||||
);
|
||||
const todayWN8sEntries = Object.entries(todayWN8s);
|
||||
const careerWN8sEntries = Object.entries(careerWN8s);
|
||||
|
||||
todayWN8s[0] =
|
||||
todayWN8sEntries.reduce(
|
||||
(accumulator, [tankIdString, wn8]) =>
|
||||
accumulator + wn8 * tankStatsOverTime[Number(tankIdString)].battles,
|
||||
0,
|
||||
) /
|
||||
todayWN8sEntries.reduce(
|
||||
(accumulator, [tankIdString]) =>
|
||||
accumulator + tankStatsOverTime[Number(tankIdString)].battles,
|
||||
0,
|
||||
);
|
||||
careerWN8s[0] =
|
||||
careerWN8sEntries.reduce(
|
||||
(accumulator, [tankIdString, wn8]) =>
|
||||
accumulator + wn8 * careerStats[Number(tankIdString)].battles,
|
||||
0,
|
||||
) /
|
||||
careerWN8sEntries.reduce(
|
||||
(accumulator, [tankIdString]) =>
|
||||
accumulator + careerStats[Number(tankIdString)].battles,
|
||||
0,
|
||||
);
|
||||
|
||||
const rows = tankStatsOverTimeEntries.map(([tankIdString, tankStats]) => {
|
||||
const tankId = Number(tankIdString);
|
||||
const career = careerStats[tankId];
|
||||
|
||||
return (
|
||||
<Breakdown.Row
|
||||
key={tankId}
|
||||
name={tankId === 0 ? 'Total' : resolveTankName(Number(tankIdString))}
|
||||
winrate={tankStats.wins / tankStats.battles}
|
||||
careerWinrate={career.wins / career.battles}
|
||||
WN8={todayWN8s[tankId]}
|
||||
careerWN8={careerWN8s[tankId]}
|
||||
damage={tankStats.damage_dealt / tankStats.battles}
|
||||
careerDamage={career.damage_dealt / career.battles}
|
||||
survival={tankStats.survived_battles / tankStats.battles}
|
||||
careerSurvival={career.survived_battles / career.battles}
|
||||
battles={tankStats.battles}
|
||||
careerBattles={career.battles}
|
||||
icon={
|
||||
tankId === 0
|
||||
? undefined
|
||||
: tankopedia[tankIdString as unknown as number].images.normal
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const image = await render(
|
||||
<Wrapper>
|
||||
<Wrapper size={WrapperSize.Roomy}>
|
||||
<TitleBar
|
||||
name={accountInfo[id].nickname}
|
||||
nameDiscriminator={
|
||||
@@ -125,6 +180,7 @@ export default {
|
||||
|
||||
<PoweredByBlitzStars />
|
||||
</Wrapper>,
|
||||
WrapperSize.Roomy,
|
||||
);
|
||||
|
||||
const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
@@ -133,6 +189,15 @@ export default {
|
||||
);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds:
|
||||
rows.length >= 6
|
||||
? [
|
||||
infoEmbed(
|
||||
'Very large image!',
|
||||
'Zoom in or click "Open in Browser."',
|
||||
),
|
||||
]
|
||||
: undefined,
|
||||
files: [image],
|
||||
components: [actionRow],
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import getWN8Percentile from '../../../core/blitz/getWN8Percentile.js';
|
||||
import { RowDiscriminator } from './RowDiscriminator.js';
|
||||
import { RowStat } from './RowStat.js';
|
||||
|
||||
@@ -22,8 +23,8 @@ export function Row({
|
||||
careerWinrate,
|
||||
WN8,
|
||||
careerWN8,
|
||||
damage: averageDamage,
|
||||
careerDamage: careerAverageDamage,
|
||||
damage,
|
||||
careerDamage,
|
||||
survival,
|
||||
careerSurvival,
|
||||
battles,
|
||||
@@ -39,11 +40,17 @@ export function Row({
|
||||
career={`${(careerWinrate * 100).toFixed(2)}%`}
|
||||
delta={winrate - careerWinrate}
|
||||
/>
|
||||
<RowStat
|
||||
name="WN8"
|
||||
value={WN8.toFixed(0)}
|
||||
career={careerWN8.toFixed(0)}
|
||||
percentile={getWN8Percentile(WN8)}
|
||||
/>
|
||||
<RowStat
|
||||
name="Damage"
|
||||
value={averageDamage.toFixed(2)}
|
||||
career={careerAverageDamage.toFixed(2)}
|
||||
delta={averageDamage - careerAverageDamage}
|
||||
value={damage.toFixed(2)}
|
||||
career={careerDamage.toFixed(2)}
|
||||
delta={damage - careerDamage}
|
||||
/>
|
||||
<RowStat
|
||||
name="Survival"
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { Percentile } from '../../../constants/percentiles.js';
|
||||
import { theme } from '../../../stitches.config.js';
|
||||
import PercentileIndicator from '../../PercentileIndicator.js';
|
||||
|
||||
export interface RowStatProps {
|
||||
name: string;
|
||||
value: string;
|
||||
career: string;
|
||||
delta?: number;
|
||||
percentile?: Percentile;
|
||||
}
|
||||
|
||||
export function RowStat({ name, value, career, delta }: RowStatProps) {
|
||||
export function RowStat({
|
||||
name,
|
||||
value,
|
||||
career,
|
||||
delta,
|
||||
percentile,
|
||||
}: RowStatProps) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -31,7 +40,7 @@ export function RowStat({ name, value, career, delta }: RowStatProps) {
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{delta !== undefined && delta !== 0 && (
|
||||
{percentile === undefined && delta !== undefined && delta !== 0 && (
|
||||
<img
|
||||
src={
|
||||
(delta ?? 0) > 0
|
||||
@@ -41,6 +50,9 @@ export function RowStat({ name, value, career, delta }: RowStatProps) {
|
||||
style={{ width: 12, height: 12 }}
|
||||
/>
|
||||
)}
|
||||
{percentile !== undefined && (
|
||||
<PercentileIndicator percentile={percentile} />
|
||||
)}
|
||||
|
||||
<span
|
||||
style={{
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function GenericAllStats({
|
||||
['Winrate', `${(100 * (stats.wins / stats.battles)).toFixed(2)}%`],
|
||||
[
|
||||
'WN8',
|
||||
supplementaryStats?.WN8.toFixed(0) ?? -Infinity,
|
||||
supplementaryStats?.WN8.toFixed(0),
|
||||
supplementaryStats
|
||||
? getWN8Percentile(supplementaryStats.WN8)
|
||||
: undefined,
|
||||
@@ -31,7 +31,7 @@ export default function GenericAllStats({
|
||||
['Wins', stats.wins],
|
||||
['Losses', stats.losses],
|
||||
['Average damage', (stats.damage_dealt / stats.battles).toFixed(0)],
|
||||
['Average tier', supplementaryStats?.tier.toFixed(2) ?? -Infinity],
|
||||
['Average tier', supplementaryStats?.tier.toFixed(2)],
|
||||
['Average XP', (stats.xp / stats.battles).toFixed(0)],
|
||||
['Average shots', (stats.shots / stats.battles).toFixed(2)],
|
||||
['Average hits', (stats.hits / stats.battles).toFixed(2)],
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function GenericStatsRow({ stat }: GenericStatsRowProps) {
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
{stat[2] && <PercentileIndicator percentile={stat[2]} />}
|
||||
{stat[2] !== undefined && <PercentileIndicator percentile={stat[2]} />}
|
||||
{stat[1]}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Percentile } from '../../constants/percentiles.js';
|
||||
import GenericStatsColumn from './components/GenericStatsColumn.js';
|
||||
|
||||
export type Stat =
|
||||
| [string, string | number]
|
||||
| [string, string | number, Percentile | undefined];
|
||||
| [string, string | number | undefined]
|
||||
| [string, string | number | undefined, Percentile | undefined];
|
||||
|
||||
export interface GenericStatsProps {
|
||||
stats: Stat[];
|
||||
@@ -14,9 +14,7 @@ export default function GenericStats({
|
||||
stats,
|
||||
columnCount = 2,
|
||||
}: GenericStatsProps) {
|
||||
const filteredStats = stats.filter(
|
||||
([, value]) => `${value}` !== `${-Infinity}`,
|
||||
);
|
||||
const filteredStats = stats.filter(([, value]) => value !== undefined);
|
||||
const itemsPerRow = Math.ceil(filteredStats.length / columnCount);
|
||||
const columns: Stat[][] = [];
|
||||
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { theme } from '../stitches.config.js';
|
||||
|
||||
export interface WrapperProps {
|
||||
children: ReactNode;
|
||||
export enum WrapperSize {
|
||||
Regular,
|
||||
Roomy,
|
||||
}
|
||||
|
||||
export default function Wrapper({ children }: WrapperProps) {
|
||||
export interface WrapperProps {
|
||||
children: ReactNode;
|
||||
size?: WrapperSize;
|
||||
}
|
||||
|
||||
export const WRAPPER_SIZE_WIDTHS: Record<WrapperSize, number> = {
|
||||
[WrapperSize.Regular]: 640,
|
||||
[WrapperSize.Roomy]: 800,
|
||||
};
|
||||
|
||||
export default function Wrapper({
|
||||
children,
|
||||
size = WrapperSize.Regular,
|
||||
}: WrapperProps) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@@ -13,7 +27,7 @@ export default function Wrapper({ children }: WrapperProps) {
|
||||
flexDirection: 'column',
|
||||
padding: 16,
|
||||
gap: 32,
|
||||
width: 640,
|
||||
width: WRAPPER_SIZE_WIDTHS[size],
|
||||
background: 'url(https://i.imgur.com/PhS06NJ.png)',
|
||||
color: theme.colors.textHighContrast,
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import sumStats from '../blitz/sumStats.js';
|
||||
import { emptyAllStats } from './getTankStatsOverTime.js';
|
||||
import { tankAverages } from './tankAverages.js';
|
||||
|
||||
console.log('Calculating player average...');
|
||||
export const playerAverage = Object.entries(tankAverages).reduce(
|
||||
(accumulator, [, tankAverage]) => {
|
||||
return sumStats([accumulator, tankAverage.all]);
|
||||
},
|
||||
emptyAllStats,
|
||||
);
|
||||
console.log('Calculated player average');
|
||||
File renamed without changes.
@@ -3,7 +3,7 @@ export type TableInput = TableInputEntry[];
|
||||
|
||||
export default function cleanTable(input: TableInput) {
|
||||
return input
|
||||
.filter((array) => array[1] !== `${-Infinity}`)
|
||||
.filter((array) => array[1] !== undefined)
|
||||
.map((array) => (array.length === 0 ? '' : `**${array[0]}**: ${array[1]}`))
|
||||
.join('\n');
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Resvg } from '@resvg/resvg-js';
|
||||
import { readFile } from 'fs';
|
||||
import { ReactNode } from 'react';
|
||||
import satori from 'satori';
|
||||
import { WRAPPER_SIZE_WIDTHS, WrapperSize } from '../../components/Wrapper.js';
|
||||
|
||||
const FONT_NAME = 'Roboto';
|
||||
const FONT_FILES = ['Roboto', 'Roboto-Bold', 'Roboto-Black'];
|
||||
@@ -20,9 +21,12 @@ const [robotoFlex, robotoFlexBold, robotoFlexBlack] = await Promise.all(
|
||||
);
|
||||
console.log('Fonts imported');
|
||||
|
||||
export default async function render(element: ReactNode) {
|
||||
export default async function render(
|
||||
element: ReactNode,
|
||||
size: WrapperSize = WrapperSize.Regular,
|
||||
) {
|
||||
const svg = await satori(element, {
|
||||
width: 640,
|
||||
width: WRAPPER_SIZE_WIDTHS[size],
|
||||
fonts: [
|
||||
{ data: robotoFlex, name: FONT_NAME, weight: 400 },
|
||||
{ data: robotoFlexBold, name: FONT_NAME, weight: 700 },
|
||||
|
||||
Reference in new issue
Block a user