mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: embeds
This commit is contained in:
1 parent
1990322d6d
commit
34f782e747
3 files changed
+299
-281
No files matched your search
@@ -0,0 +1,278 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import * as Breakdown from '../../../components/Breakdown';
|
||||||
|
import { TreeTypeEnum } from '../../../components/Tanks';
|
||||||
|
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
|
||||||
|
import calculateWN8 from '../../../core/blitz/calculateWN8';
|
||||||
|
import { diffNormalizedTankStats } from '../../../core/blitz/diffNormalizedTankStats';
|
||||||
|
import getWN8Percentile from '../../../core/blitz/getWN8Percentile';
|
||||||
|
import getWargamingResponse from '../../../core/blitz/getWargamingResponse';
|
||||||
|
import { TankopediaEntry, tankopedia } from '../../../core/blitz/tankopedia';
|
||||||
|
import { tankAverages } from '../../../core/blitzstars/tankAverages';
|
||||||
|
import { useSession } from '../../../stores/session';
|
||||||
|
import { IndividualTankStats, TanksStats } from '../../../types/tanksStats';
|
||||||
|
|
||||||
|
const REFRESH_RATE = 1 / 5;
|
||||||
|
|
||||||
|
export default function SessionPage() {
|
||||||
|
const session = useSession();
|
||||||
|
const since = session.isTracking ? new Date(session.time) : undefined;
|
||||||
|
const [diff, setDiff] = useState<
|
||||||
|
| {
|
||||||
|
careerWN8: number;
|
||||||
|
|
||||||
|
list: {
|
||||||
|
stats: IndividualTankStats;
|
||||||
|
tankopedia?: TankopediaEntry;
|
||||||
|
career: IndividualTankStats;
|
||||||
|
currentWN8?: number;
|
||||||
|
careerWN8?: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
>(undefined);
|
||||||
|
const battles = diff?.list.reduce(
|
||||||
|
(accumulator, { stats }) => accumulator + stats.all.battles,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const tankStatsArray = session.isTracking
|
||||||
|
? (Object.values(session.tankStats) as IndividualTankStats[])
|
||||||
|
: [];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function recalculateDiff() {
|
||||||
|
if (session.isTracking) {
|
||||||
|
const { id, region } = session;
|
||||||
|
const careerRaw = await getWargamingResponse<TanksStats>(
|
||||||
|
`https://api.wotblitz.${region}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
||||||
|
);
|
||||||
|
const career = careerRaw[id].reduce<
|
||||||
|
Record<number, IndividualTankStats>
|
||||||
|
>(
|
||||||
|
(accumulator, curr) => ({
|
||||||
|
...accumulator,
|
||||||
|
[curr.tank_id]: curr,
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const awaitedTankopedia = await tankopedia;
|
||||||
|
const awaitedTankAverages = await tankAverages;
|
||||||
|
const careerWN8 =
|
||||||
|
careerRaw[id].reduce(
|
||||||
|
(accumulator, { tank_id, all }) =>
|
||||||
|
accumulator +
|
||||||
|
(awaitedTankAverages[tank_id] && all.battles > 0
|
||||||
|
? all.battles *
|
||||||
|
calculateWN8(awaitedTankAverages[tank_id].all, all)
|
||||||
|
: 0),
|
||||||
|
0,
|
||||||
|
) /
|
||||||
|
careerRaw[id].reduce(
|
||||||
|
(accumulator, { tank_id, all }) =>
|
||||||
|
accumulator +
|
||||||
|
(awaitedTankAverages[tank_id] && all.battles > 0
|
||||||
|
? all.battles
|
||||||
|
: 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
setDiff({
|
||||||
|
careerWN8,
|
||||||
|
|
||||||
|
list: (
|
||||||
|
Object.values(
|
||||||
|
diffNormalizedTankStats(session.tankStats, career),
|
||||||
|
) as IndividualTankStats[]
|
||||||
|
).map((stats) => {
|
||||||
|
return {
|
||||||
|
stats,
|
||||||
|
tankopedia: awaitedTankopedia[stats.tank_id],
|
||||||
|
career: career[stats.tank_id],
|
||||||
|
currentWN8: awaitedTankAverages[stats.tank_id]
|
||||||
|
? calculateWN8(
|
||||||
|
awaitedTankAverages[stats.tank_id].all,
|
||||||
|
stats.all,
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
careerWN8: awaitedTankAverages[stats.tank_id]
|
||||||
|
? calculateWN8(
|
||||||
|
awaitedTankAverages[stats.tank_id].all,
|
||||||
|
career[stats.tank_id].all,
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recalculateDiff();
|
||||||
|
const interval = setInterval(recalculateDiff, 1000 / REFRESH_RATE);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Breakdown.Root>
|
||||||
|
{session.isTracking && since !== undefined && diff !== undefined && (
|
||||||
|
<Breakdown.Row
|
||||||
|
title={`Since ${since.toLocaleString(undefined, {
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
hour12: true,
|
||||||
|
month: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
day: 'numeric',
|
||||||
|
})}`}
|
||||||
|
stats={[
|
||||||
|
{
|
||||||
|
title: 'Battles',
|
||||||
|
current: battles!.toLocaleString(),
|
||||||
|
career: tankStatsArray
|
||||||
|
.reduce(
|
||||||
|
(accumulator, stats) => accumulator + stats.all.battles,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.toLocaleString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Winrate',
|
||||||
|
current: battles
|
||||||
|
? `${(
|
||||||
|
100 *
|
||||||
|
(diff.list.reduce(
|
||||||
|
(accumulator, { stats }) => accumulator + stats.all.wins,
|
||||||
|
0,
|
||||||
|
) /
|
||||||
|
battles)
|
||||||
|
).toFixed(2)}%`
|
||||||
|
: '--',
|
||||||
|
career: `${(
|
||||||
|
100 *
|
||||||
|
(tankStatsArray.reduce(
|
||||||
|
(accumulator, stats) => accumulator + stats.all.wins,
|
||||||
|
0,
|
||||||
|
) /
|
||||||
|
tankStatsArray.reduce(
|
||||||
|
(accumulator, stats) => accumulator + stats.all.battles,
|
||||||
|
0,
|
||||||
|
))
|
||||||
|
).toFixed(2)}%`,
|
||||||
|
},
|
||||||
|
(() => {
|
||||||
|
const battlesWithWN8 = diff.list.reduce(
|
||||||
|
(accumulator, { currentWN8, stats }) =>
|
||||||
|
(currentWN8 === undefined ? 0 : stats.all.battles) +
|
||||||
|
accumulator,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
const currentWN8 =
|
||||||
|
diff.list.reduce(
|
||||||
|
(accumulator, { currentWN8, stats }) =>
|
||||||
|
stats.all.battles * (currentWN8 ?? 0) + accumulator,
|
||||||
|
0,
|
||||||
|
) / battlesWithWN8;
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: 'WN8',
|
||||||
|
current: battlesWithWN8
|
||||||
|
? Math.round(currentWN8).toLocaleString()
|
||||||
|
: '--',
|
||||||
|
percentile: battlesWithWN8
|
||||||
|
? getWN8Percentile(currentWN8)
|
||||||
|
: undefined,
|
||||||
|
career: Math.round(diff.careerWN8).toLocaleString(),
|
||||||
|
};
|
||||||
|
})(),
|
||||||
|
{
|
||||||
|
title: 'Damage',
|
||||||
|
current: battles
|
||||||
|
? Math.round(
|
||||||
|
diff.list.reduce(
|
||||||
|
(accumulator, { stats }) =>
|
||||||
|
accumulator + stats.all.damage_dealt,
|
||||||
|
0,
|
||||||
|
) / battles,
|
||||||
|
).toLocaleString()
|
||||||
|
: '--',
|
||||||
|
career: Math.round(
|
||||||
|
tankStatsArray.reduce(
|
||||||
|
(accumulator, stats) => accumulator + stats.all.damage_dealt,
|
||||||
|
0,
|
||||||
|
) /
|
||||||
|
tankStatsArray.reduce(
|
||||||
|
(accumulator, stats) => accumulator + stats.all.battles,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
).toLocaleString(),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{diff?.list
|
||||||
|
.sort((a, b) => b.stats.last_battle_time - a.stats.last_battle_time)
|
||||||
|
.map(({ stats, tankopedia, career, careerWN8, currentWN8 }) => (
|
||||||
|
<Breakdown.Row
|
||||||
|
key={stats.tank_id}
|
||||||
|
title={tankopedia?.name ?? `Unknown tank ${stats.tank_id}`}
|
||||||
|
type="tank"
|
||||||
|
tankType={tankopedia?.type}
|
||||||
|
treeType={(() => {
|
||||||
|
if (tankopedia?.is_collectible) return TreeTypeEnum.Collector;
|
||||||
|
if (tankopedia?.is_premium) return TreeTypeEnum.Premium;
|
||||||
|
})()}
|
||||||
|
stats={[
|
||||||
|
{
|
||||||
|
title: 'Battles',
|
||||||
|
current: stats.all.battles.toLocaleString(),
|
||||||
|
career: career.all.battles.toLocaleString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Winrate',
|
||||||
|
current: `${(
|
||||||
|
100 *
|
||||||
|
(stats.all.wins / stats.all.battles)
|
||||||
|
).toFixed(2)}%`,
|
||||||
|
career: `${(
|
||||||
|
100 *
|
||||||
|
(career.all.wins / career.all.battles)
|
||||||
|
).toFixed(2)}%`,
|
||||||
|
delta:
|
||||||
|
stats.all.wins / stats.all.battles -
|
||||||
|
career.all.wins / career.all.battles,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'WN8',
|
||||||
|
current:
|
||||||
|
currentWN8 === undefined
|
||||||
|
? undefined
|
||||||
|
: Math.round(currentWN8).toLocaleString(),
|
||||||
|
career:
|
||||||
|
careerWN8 === undefined
|
||||||
|
? undefined
|
||||||
|
: Math.round(careerWN8).toLocaleString(),
|
||||||
|
percentile:
|
||||||
|
currentWN8 === undefined
|
||||||
|
? undefined
|
||||||
|
: getWN8Percentile(currentWN8),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Damage',
|
||||||
|
current: Math.round(
|
||||||
|
stats.all.damage_dealt / stats.all.battles,
|
||||||
|
).toLocaleString(),
|
||||||
|
career: Math.round(
|
||||||
|
career.all.damage_dealt / career.all.battles,
|
||||||
|
).toLocaleString(),
|
||||||
|
delta:
|
||||||
|
stats.all.damage_dealt / stats.all.battles -
|
||||||
|
career.all.damage_dealt / career.all.battles,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Breakdown.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
+10
-2
@@ -1,5 +1,8 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import { config } from 'dotenv';
|
import { config } from 'dotenv';
|
||||||
import { Roboto_Flex } from 'next/font/google';
|
import { Roboto_Flex } from 'next/font/google';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import Navbar from '../components/Navbar';
|
import Navbar from '../components/Navbar';
|
||||||
|
|
||||||
@@ -15,15 +18,20 @@ const robotoFlex = Roboto_Flex({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default function RootLayout({ children }: RootLayoutProps) {
|
export default function RootLayout({ children }: RootLayoutProps) {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const isEmbed = pathname.split('/')[1] === 'embeds';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en" className={robotoFlex.className}>
|
<html lang="en" className={robotoFlex.className}>
|
||||||
<body
|
<body
|
||||||
style={{
|
style={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
background: 'url(https://i.imgur.com/PhS06NJ.png)',
|
background: isEmbed
|
||||||
|
? 'transparent'
|
||||||
|
: 'url(https://i.imgur.com/PhS06NJ.png)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Navbar />
|
{!isEmbed && <Navbar />}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
+11
-279
@@ -1,35 +1,24 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { CopyIcon, ListBulletIcon, ReloadIcon } from '@radix-ui/react-icons';
|
import { CopyIcon, ReloadIcon } from '@radix-ui/react-icons';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
import { ChangeEvent, useRef, useState } from 'react';
|
||||||
import * as Breakdown from '../../../components/Breakdown';
|
|
||||||
import { Button } from '../../../components/Button';
|
import { Button } from '../../../components/Button';
|
||||||
import PageWrapper from '../../../components/PageWrapper';
|
import PageWrapper from '../../../components/PageWrapper';
|
||||||
import { SearchBar } from '../../../components/SearchBar';
|
import { SearchBar } from '../../../components/SearchBar';
|
||||||
import { TreeTypeEnum } from '../../../components/Tanks';
|
|
||||||
import { REGION_NAMES, Region } from '../../../constants/regions';
|
import { REGION_NAMES, Region } from '../../../constants/regions';
|
||||||
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
|
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
|
||||||
import calculateWN8 from '../../../core/blitz/calculateWN8';
|
|
||||||
import { diffNormalizedTankStats } from '../../../core/blitz/diffNormalizedTankStats';
|
|
||||||
import getWN8Percentile from '../../../core/blitz/getWN8Percentile';
|
|
||||||
import getWargamingResponse from '../../../core/blitz/getWargamingResponse';
|
import getWargamingResponse from '../../../core/blitz/getWargamingResponse';
|
||||||
import listPlayers, {
|
import listPlayers, {
|
||||||
AccountListWithServer,
|
AccountListWithServer,
|
||||||
} from '../../../core/blitz/listPlayers';
|
} from '../../../core/blitz/listPlayers';
|
||||||
import { TankopediaEntry, tankopedia } from '../../../core/blitz/tankopedia';
|
|
||||||
import { tankAverages } from '../../../core/blitzstars/tankAverages';
|
|
||||||
import { theme } from '../../../stitches.config';
|
import { theme } from '../../../stitches.config';
|
||||||
import { useSession } from '../../../stores/session';
|
import { useSession } from '../../../stores/session';
|
||||||
import {
|
import { NormalizedTankStats, TanksStats } from '../../../types/tanksStats';
|
||||||
IndividualTankStats,
|
import SessionPage from '../../embeds/session/page';
|
||||||
NormalizedTankStats,
|
|
||||||
TanksStats,
|
|
||||||
} from '../../../types/tanksStats';
|
|
||||||
import * as styles from './page.css';
|
import * as styles from './page.css';
|
||||||
|
|
||||||
// 1 once per 5 seconds
|
// 1 once per 5 seconds
|
||||||
const REFRESH_RATE = 1 / 5;
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const input = useRef<HTMLInputElement>(null);
|
const input = useRef<HTMLInputElement>(null);
|
||||||
@@ -49,24 +38,6 @@ export default function Page() {
|
|||||||
500,
|
500,
|
||||||
);
|
);
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const tankStatsArray = session.isTracking
|
|
||||||
? (Object.values(session.tankStats) as IndividualTankStats[])
|
|
||||||
: [];
|
|
||||||
const since = session.isTracking ? new Date(session.time) : undefined;
|
|
||||||
const [diff, setDiff] = useState<
|
|
||||||
| {
|
|
||||||
careerWN8: number;
|
|
||||||
|
|
||||||
list: {
|
|
||||||
stats: IndividualTankStats;
|
|
||||||
tankopedia?: TankopediaEntry;
|
|
||||||
career: IndividualTankStats;
|
|
||||||
currentWN8?: number;
|
|
||||||
careerWN8?: number;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
| undefined
|
|
||||||
>(undefined);
|
|
||||||
|
|
||||||
async function setSession(region: Region, id: number, nickname: string) {
|
async function setSession(region: Region, id: number, nickname: string) {
|
||||||
input.current!.value = nickname;
|
input.current!.value = nickname;
|
||||||
@@ -94,86 +65,6 @@ export default function Page() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function recalculateDiff() {
|
|
||||||
// const session = useSession.getState();
|
|
||||||
|
|
||||||
if (session.isTracking) {
|
|
||||||
const { id, region } = session;
|
|
||||||
const careerRaw = await getWargamingResponse<TanksStats>(
|
|
||||||
`https://api.wotblitz.${region}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
|
|
||||||
);
|
|
||||||
const career = careerRaw[id].reduce<
|
|
||||||
Record<number, IndividualTankStats>
|
|
||||||
>(
|
|
||||||
(accumulator, curr) => ({
|
|
||||||
...accumulator,
|
|
||||||
[curr.tank_id]: curr,
|
|
||||||
}),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
const awaitedTankopedia = await tankopedia;
|
|
||||||
const awaitedTankAverages = await tankAverages;
|
|
||||||
const careerWN8 =
|
|
||||||
careerRaw[id].reduce(
|
|
||||||
(accumulator, { tank_id, all }) =>
|
|
||||||
accumulator +
|
|
||||||
(awaitedTankAverages[tank_id] && all.battles > 0
|
|
||||||
? all.battles *
|
|
||||||
calculateWN8(awaitedTankAverages[tank_id].all, all)
|
|
||||||
: 0),
|
|
||||||
0,
|
|
||||||
) /
|
|
||||||
careerRaw[id].reduce(
|
|
||||||
(accumulator, { tank_id, all }) =>
|
|
||||||
accumulator +
|
|
||||||
(awaitedTankAverages[tank_id] && all.battles > 0
|
|
||||||
? all.battles
|
|
||||||
: 0),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
setDiff({
|
|
||||||
careerWN8,
|
|
||||||
|
|
||||||
list: (
|
|
||||||
Object.values(
|
|
||||||
diffNormalizedTankStats(session.tankStats, career),
|
|
||||||
) as IndividualTankStats[]
|
|
||||||
).map((stats) => {
|
|
||||||
return {
|
|
||||||
stats,
|
|
||||||
tankopedia: awaitedTankopedia[stats.tank_id],
|
|
||||||
career: career[stats.tank_id],
|
|
||||||
currentWN8: awaitedTankAverages[stats.tank_id]
|
|
||||||
? calculateWN8(
|
|
||||||
awaitedTankAverages[stats.tank_id].all,
|
|
||||||
stats.all,
|
|
||||||
)
|
|
||||||
: undefined,
|
|
||||||
careerWN8: awaitedTankAverages[stats.tank_id]
|
|
||||||
? calculateWN8(
|
|
||||||
awaitedTankAverages[stats.tank_id].all,
|
|
||||||
career[stats.tank_id].all,
|
|
||||||
)
|
|
||||||
: undefined,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
recalculateDiff();
|
|
||||||
const interval = setInterval(recalculateDiff, 1000 / REFRESH_RATE);
|
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, [session]);
|
|
||||||
|
|
||||||
const battles = diff?.list.reduce(
|
|
||||||
(accumulator, { stats }) => accumulator + stats.all.battles,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageWrapper>
|
<PageWrapper>
|
||||||
<div className={styles.toolBar}>
|
<div className={styles.toolBar}>
|
||||||
@@ -279,177 +170,18 @@ export default function Page() {
|
|||||||
>
|
>
|
||||||
<ReloadIcon /> Reset
|
<ReloadIcon /> Reset
|
||||||
</Button>
|
</Button>
|
||||||
<Button className={styles.toolbarButton}>
|
<Button
|
||||||
|
className={styles.toolbarButton}
|
||||||
|
onClick={() =>
|
||||||
|
navigator.clipboard.writeText(`${location.origin}/embeds/session`)
|
||||||
|
}
|
||||||
|
>
|
||||||
<CopyIcon /> Embed
|
<CopyIcon /> Embed
|
||||||
</Button>
|
</Button>
|
||||||
<Button className={styles.toolbarButton}>
|
|
||||||
<ListBulletIcon /> Options
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Breakdown.Root>
|
<SessionPage />
|
||||||
{session.isTracking && since !== undefined && diff !== undefined && (
|
|
||||||
<Breakdown.Row
|
|
||||||
title={`Since ${since.toLocaleString(undefined, {
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
hour12: true,
|
|
||||||
month: 'short',
|
|
||||||
year: 'numeric',
|
|
||||||
day: 'numeric',
|
|
||||||
})}`}
|
|
||||||
stats={[
|
|
||||||
{
|
|
||||||
title: 'Battles',
|
|
||||||
current: battles!.toLocaleString(),
|
|
||||||
career: tankStatsArray
|
|
||||||
.reduce(
|
|
||||||
(accumulator, stats) => accumulator + stats.all.battles,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
.toLocaleString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Winrate',
|
|
||||||
current: battles
|
|
||||||
? `${(
|
|
||||||
100 *
|
|
||||||
(diff.list.reduce(
|
|
||||||
(accumulator, { stats }) =>
|
|
||||||
accumulator + stats.all.wins,
|
|
||||||
0,
|
|
||||||
) /
|
|
||||||
battles)
|
|
||||||
).toFixed(2)}%`
|
|
||||||
: '--',
|
|
||||||
career: `${(
|
|
||||||
100 *
|
|
||||||
(tankStatsArray.reduce(
|
|
||||||
(accumulator, stats) => accumulator + stats.all.wins,
|
|
||||||
0,
|
|
||||||
) /
|
|
||||||
tankStatsArray.reduce(
|
|
||||||
(accumulator, stats) => accumulator + stats.all.battles,
|
|
||||||
0,
|
|
||||||
))
|
|
||||||
).toFixed(2)}%`,
|
|
||||||
},
|
|
||||||
(() => {
|
|
||||||
const battlesWithWN8 = diff.list.reduce(
|
|
||||||
(accumulator, { currentWN8, stats }) =>
|
|
||||||
(currentWN8 === undefined ? 0 : stats.all.battles) +
|
|
||||||
accumulator,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
const currentWN8 =
|
|
||||||
diff.list.reduce(
|
|
||||||
(accumulator, { currentWN8, stats }) =>
|
|
||||||
stats.all.battles * (currentWN8 ?? 0) + accumulator,
|
|
||||||
0,
|
|
||||||
) / battlesWithWN8;
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: 'WN8',
|
|
||||||
current: battlesWithWN8
|
|
||||||
? Math.round(currentWN8).toLocaleString()
|
|
||||||
: '--',
|
|
||||||
percentile: battlesWithWN8
|
|
||||||
? getWN8Percentile(currentWN8)
|
|
||||||
: undefined,
|
|
||||||
career: Math.round(diff.careerWN8).toLocaleString(),
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
{
|
|
||||||
title: 'Damage',
|
|
||||||
current: battles
|
|
||||||
? Math.round(
|
|
||||||
diff.list.reduce(
|
|
||||||
(accumulator, { stats }) =>
|
|
||||||
accumulator + stats.all.damage_dealt,
|
|
||||||
0,
|
|
||||||
) / battles,
|
|
||||||
).toLocaleString()
|
|
||||||
: '--',
|
|
||||||
career: Math.round(
|
|
||||||
tankStatsArray.reduce(
|
|
||||||
(accumulator, stats) =>
|
|
||||||
accumulator + stats.all.damage_dealt,
|
|
||||||
0,
|
|
||||||
) /
|
|
||||||
tankStatsArray.reduce(
|
|
||||||
(accumulator, stats) => accumulator + stats.all.battles,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
).toLocaleString(),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{diff?.list
|
|
||||||
.sort((a, b) => b.stats.last_battle_time - a.stats.last_battle_time)
|
|
||||||
.map(({ stats, tankopedia, career, careerWN8, currentWN8 }) => (
|
|
||||||
<Breakdown.Row
|
|
||||||
key={stats.tank_id}
|
|
||||||
title={tankopedia?.name ?? `Unknown tank ${stats.tank_id}`}
|
|
||||||
type="tank"
|
|
||||||
tankType={tankopedia?.type}
|
|
||||||
treeType={(() => {
|
|
||||||
if (tankopedia?.is_collectible) return TreeTypeEnum.Collector;
|
|
||||||
if (tankopedia?.is_premium) return TreeTypeEnum.Premium;
|
|
||||||
})()}
|
|
||||||
stats={[
|
|
||||||
{
|
|
||||||
title: 'Battles',
|
|
||||||
current: stats.all.battles.toLocaleString(),
|
|
||||||
career: career.all.battles.toLocaleString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Winrate',
|
|
||||||
current: `${(
|
|
||||||
100 *
|
|
||||||
(stats.all.wins / stats.all.battles)
|
|
||||||
).toFixed(2)}%`,
|
|
||||||
career: `${(
|
|
||||||
100 *
|
|
||||||
(career.all.wins / career.all.battles)
|
|
||||||
).toFixed(2)}%`,
|
|
||||||
delta:
|
|
||||||
stats.all.wins / stats.all.battles -
|
|
||||||
career.all.wins / career.all.battles,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'WN8',
|
|
||||||
current:
|
|
||||||
currentWN8 === undefined
|
|
||||||
? undefined
|
|
||||||
: Math.round(currentWN8).toLocaleString(),
|
|
||||||
career:
|
|
||||||
careerWN8 === undefined
|
|
||||||
? undefined
|
|
||||||
: Math.round(careerWN8).toLocaleString(),
|
|
||||||
percentile:
|
|
||||||
currentWN8 === undefined
|
|
||||||
? undefined
|
|
||||||
: getWN8Percentile(currentWN8),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Damage',
|
|
||||||
current: Math.round(
|
|
||||||
stats.all.damage_dealt / stats.all.battles,
|
|
||||||
).toLocaleString(),
|
|
||||||
career: Math.round(
|
|
||||||
career.all.damage_dealt / career.all.battles,
|
|
||||||
).toLocaleString(),
|
|
||||||
delta:
|
|
||||||
stats.all.damage_dealt / stats.all.battles -
|
|
||||||
career.all.damage_dealt / career.all.battles,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Breakdown.Root>
|
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user