mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 12:09:51 +02:00
feat: deltas
This commit is contained in:
1 parent
cceebd1980
commit
c24639dd0c
5 files changed
+128
-477
No files matched your search
@@ -1,338 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { ContextMenu } from '@radix-ui/themes';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import * as Breakdown from '../../../components/Breakdown';
|
|
||||||
import fetchBlitz from '../../../core/blitz/fetchBlitz';
|
|
||||||
import { resetSession } from '../../../core/blitzkit/resetSession';
|
|
||||||
import {
|
|
||||||
TankDefinition,
|
|
||||||
tankDefinitions,
|
|
||||||
} from '../../../core/blitzkit/tankDefinitions';
|
|
||||||
import { tankAverages } from '../../../core/blitzstars/tankAverages';
|
|
||||||
import calculateWN8 from '../../../core/statistics/calculateWN8';
|
|
||||||
import { deltaTankStats } from '../../../core/statistics/deltaTankStats';
|
|
||||||
import getWN8Percentile from '../../../core/statistics/getWN8Percentile';
|
|
||||||
import mutateSession, { useSession } from '../../../stores/session';
|
|
||||||
import { IndividualTankStats, TanksStats } from '../../../types/tanksStats';
|
|
||||||
import { CustomColumnDisplay } from '../../tools/session/components/CustomColumn';
|
|
||||||
import { Menu } from '../../tools/session/components/Menu';
|
|
||||||
|
|
||||||
const REFRESH_RATE = 1 / 5;
|
|
||||||
|
|
||||||
interface SessionPageProps {
|
|
||||||
naked?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function SessionPage({ naked = true }: SessionPageProps) {
|
|
||||||
const session = useSession();
|
|
||||||
const since = session.isTracking ? new Date(session.time) : undefined;
|
|
||||||
const [diff, setDiff] = useState<
|
|
||||||
| {
|
|
||||||
careerWN8: number;
|
|
||||||
|
|
||||||
list: {
|
|
||||||
stats: IndividualTankStats;
|
|
||||||
tankDefinitions?: TankDefinition;
|
|
||||||
career: IndividualTankStats;
|
|
||||||
currentWN8?: number;
|
|
||||||
careerWN8?: number;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
| undefined
|
|
||||||
>(undefined);
|
|
||||||
const sessionBattles = diff?.list.reduce(
|
|
||||||
(accumulator, { stats }) => accumulator + stats.all.battles,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
const tankStatsArray = session.isTracking
|
|
||||||
? (Object.values(session.tankStats) as IndividualTankStats[])
|
|
||||||
: [];
|
|
||||||
const sessionWinrate = diff
|
|
||||||
? diff.list.reduce(
|
|
||||||
(accumulator, { stats }) => accumulator + stats.all.wins,
|
|
||||||
0,
|
|
||||||
) / sessionBattles!
|
|
||||||
: 0;
|
|
||||||
const careerBattles = tankStatsArray.reduce(
|
|
||||||
(accumulator, stats) => accumulator + stats.all.battles,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
const careerWinrate =
|
|
||||||
tankStatsArray.reduce(
|
|
||||||
(accumulator, stats) => accumulator + stats.all.wins,
|
|
||||||
0,
|
|
||||||
) / careerBattles;
|
|
||||||
const currentDamage = diff
|
|
||||||
? diff.list.reduce(
|
|
||||||
(accumulator, { stats }) => accumulator + stats.all.damage_dealt,
|
|
||||||
0,
|
|
||||||
) / sessionBattles!
|
|
||||||
: 0;
|
|
||||||
const careerDamage =
|
|
||||||
tankStatsArray.reduce(
|
|
||||||
(accumulator, stats) => accumulator + stats.all.damage_dealt,
|
|
||||||
0,
|
|
||||||
) / careerBattles;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (session.title === 'Untitled session') {
|
|
||||||
mutateSession((draft) => {
|
|
||||||
if (draft.isTracking) draft.title = draft.nickname;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [session.title]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function recalculateDiff() {
|
|
||||||
if (session.isTracking) {
|
|
||||||
const { id, region } = session;
|
|
||||||
const careerRaw = await fetchBlitz<TanksStats>(region, 'tanks/stats', {
|
|
||||||
account_id: id,
|
|
||||||
});
|
|
||||||
const career = careerRaw[id]!.reduce<
|
|
||||||
Record<number, IndividualTankStats>
|
|
||||||
>(
|
|
||||||
(accumulator, curr) => ({
|
|
||||||
...accumulator,
|
|
||||||
[curr.tank_id]: curr,
|
|
||||||
}),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
const awaitedTankDefinitions = await tankDefinitions;
|
|
||||||
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(
|
|
||||||
deltaTankStats(session.tankStats, career, session.time / 1000),
|
|
||||||
) as IndividualTankStats[]
|
|
||||||
).map((stats) => {
|
|
||||||
return {
|
|
||||||
stats,
|
|
||||||
tankDefinitions: awaitedTankDefinitions[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 totalRowStats: Record<
|
|
||||||
CustomColumnDisplay,
|
|
||||||
Breakdown.RowStatItem | undefined
|
|
||||||
> = {
|
|
||||||
battles: {
|
|
||||||
title: 'Battles',
|
|
||||||
current: sessionBattles?.toLocaleString(),
|
|
||||||
career: tankStatsArray
|
|
||||||
.reduce((accumulator, stats) => accumulator + stats.all.battles, 0)
|
|
||||||
.toLocaleString(),
|
|
||||||
},
|
|
||||||
winrate: {
|
|
||||||
title: 'Winrate',
|
|
||||||
current: sessionBattles ? `${(100 * sessionWinrate).toFixed(2)}%` : '--',
|
|
||||||
career: `${(100 * careerWinrate).toFixed(2)}%`,
|
|
||||||
delta: sessionBattles === 0 ? 0 : sessionWinrate - careerWinrate,
|
|
||||||
},
|
|
||||||
wn8: (() => {
|
|
||||||
if (!diff) return undefined;
|
|
||||||
|
|
||||||
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(),
|
|
||||||
};
|
|
||||||
})(),
|
|
||||||
damage: {
|
|
||||||
title: 'Damage',
|
|
||||||
delta: sessionBattles === 0 ? 0 : currentDamage - careerDamage,
|
|
||||||
current: sessionBattles
|
|
||||||
? Math.round(currentDamage).toLocaleString()
|
|
||||||
: '--',
|
|
||||||
career: Math.round(careerDamage).toLocaleString(),
|
|
||||||
},
|
|
||||||
none: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ContextMenu.Root>
|
|
||||||
<ContextMenu.Trigger>
|
|
||||||
<div>
|
|
||||||
<Breakdown.Root>
|
|
||||||
{session.showTotal &&
|
|
||||||
session.isTracking &&
|
|
||||||
since !== undefined &&
|
|
||||||
diff !== undefined && (
|
|
||||||
<Breakdown.Row
|
|
||||||
naked={naked}
|
|
||||||
color={session.color}
|
|
||||||
minimized={!session.showCareer}
|
|
||||||
title={session.title}
|
|
||||||
stats={session.customColumns.map((customColumn) => {
|
|
||||||
const rowStat = totalRowStats[customColumn.display];
|
|
||||||
|
|
||||||
if (!rowStat) return undefined;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...rowStat,
|
|
||||||
delta:
|
|
||||||
customColumn.showDelta &&
|
|
||||||
rowStat.percentile === undefined
|
|
||||||
? rowStat.delta
|
|
||||||
: undefined,
|
|
||||||
};
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{diff?.list
|
|
||||||
.sort(
|
|
||||||
(a, b) => b.stats.last_battle_time - a.stats.last_battle_time,
|
|
||||||
)
|
|
||||||
.map(
|
|
||||||
({ stats, tankDefinitions, career, careerWN8, currentWN8 }) => {
|
|
||||||
const rowStats: Record<
|
|
||||||
CustomColumnDisplay,
|
|
||||||
Breakdown.RowStatItem | undefined
|
|
||||||
> = {
|
|
||||||
battles: {
|
|
||||||
title: 'Battles',
|
|
||||||
current: stats.all.battles.toLocaleString(),
|
|
||||||
career: career.all.battles.toLocaleString(),
|
|
||||||
},
|
|
||||||
winrate: {
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
wn8: {
|
|
||||||
title: 'WN8',
|
|
||||||
current:
|
|
||||||
currentWN8 === undefined
|
|
||||||
? undefined
|
|
||||||
: Math.round(currentWN8).toLocaleString(),
|
|
||||||
career:
|
|
||||||
careerWN8 === undefined
|
|
||||||
? undefined
|
|
||||||
: Math.round(careerWN8).toLocaleString(),
|
|
||||||
percentile:
|
|
||||||
currentWN8 === undefined
|
|
||||||
? undefined
|
|
||||||
: getWN8Percentile(currentWN8),
|
|
||||||
},
|
|
||||||
damage: {
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
none: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Breakdown.Row
|
|
||||||
naked={naked}
|
|
||||||
color={session.color}
|
|
||||||
key={stats.tank_id}
|
|
||||||
minimized={!session.showCareer}
|
|
||||||
title={
|
|
||||||
tankDefinitions?.name ?? `Unknown tank ${stats.tank_id}`
|
|
||||||
}
|
|
||||||
type="tank"
|
|
||||||
tankType={tankDefinitions?.class}
|
|
||||||
treeType={tankDefinitions?.treeType}
|
|
||||||
stats={session.customColumns.map((customColumn) => {
|
|
||||||
const rowStat = rowStats[customColumn.display];
|
|
||||||
|
|
||||||
if (!rowStat) return undefined;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...rowStat,
|
|
||||||
delta:
|
|
||||||
customColumn.showDelta &&
|
|
||||||
rowStat.percentile === undefined
|
|
||||||
? rowStat.delta
|
|
||||||
: undefined,
|
|
||||||
};
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</Breakdown.Root>
|
|
||||||
</div>
|
|
||||||
</ContextMenu.Trigger>
|
|
||||||
|
|
||||||
<Menu reset={resetSession} Builder={ContextMenu} />
|
|
||||||
</ContextMenu.Root>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -38,6 +38,7 @@ import searchPlayersAcrossRegions, {
|
|||||||
import { tankDefinitions } from '../../../core/blitzkit/tankDefinitions';
|
import { tankDefinitions } from '../../../core/blitzkit/tankDefinitions';
|
||||||
import { tankIcon } from '../../../core/blitzkit/tankIcon';
|
import { tankIcon } from '../../../core/blitzkit/tankIcon';
|
||||||
import { tankAverages } from '../../../core/blitzstars/tankAverages';
|
import { tankAverages } from '../../../core/blitzstars/tankAverages';
|
||||||
|
import { deltaTankStats } from '../../../core/statistics/deltaTankStats';
|
||||||
import { theme } from '../../../stitches.config';
|
import { theme } from '../../../stitches.config';
|
||||||
import { useApp } from '../../../stores/app';
|
import { useApp } from '../../../stores/app';
|
||||||
import mutateSession, {
|
import mutateSession, {
|
||||||
@@ -71,6 +72,15 @@ export default function Page() {
|
|||||||
[session.tracking && session.player.id],
|
[session.tracking && session.player.id],
|
||||||
);
|
);
|
||||||
const accountInfo = use(accountInfoPromise);
|
const accountInfo = use(accountInfoPromise);
|
||||||
|
const delta = useMemo(
|
||||||
|
() =>
|
||||||
|
session.tracking && tankStatsB
|
||||||
|
? deltaTankStats(session.player.stats, tankStatsB).sort(
|
||||||
|
(a, b) => b.last_battle_time - a.last_battle_time,
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
[session.tracking && session.player, tankStatsB],
|
||||||
|
);
|
||||||
|
|
||||||
const search = debounce(async () => {
|
const search = debounce(async () => {
|
||||||
if (!input.current) return;
|
if (!input.current) return;
|
||||||
@@ -248,7 +258,12 @@ export default function Page() {
|
|||||||
<Button
|
<Button
|
||||||
key={player.account_id}
|
key={player.account_id}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
|
const stats = (await getTankStats(
|
||||||
|
player.region,
|
||||||
|
player.account_id,
|
||||||
|
))!;
|
||||||
|
|
||||||
setShowSearch(false);
|
setShowSearch(false);
|
||||||
mutateSession((draft) => {
|
mutateSession((draft) => {
|
||||||
if (!input.current) return;
|
if (!input.current) return;
|
||||||
@@ -258,6 +273,7 @@ export default function Page() {
|
|||||||
id: player.account_id,
|
id: player.account_id,
|
||||||
region: player.region,
|
region: player.region,
|
||||||
since: Date.now(),
|
since: Date.now(),
|
||||||
|
stats,
|
||||||
};
|
};
|
||||||
|
|
||||||
input.current.value = player.nickname;
|
input.current.value = player.nickname;
|
||||||
@@ -308,7 +324,7 @@ export default function Page() {
|
|||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{session.tracking && tankStatsB && tankStatsB.length > 0 && (
|
{session.tracking && delta && delta.length > 0 && (
|
||||||
<Table.Root variant="surface">
|
<Table.Root variant="surface">
|
||||||
<Table.Header>
|
<Table.Header>
|
||||||
<Table.Row>
|
<Table.Row>
|
||||||
@@ -322,84 +338,81 @@ export default function Page() {
|
|||||||
</Table.Header>
|
</Table.Header>
|
||||||
|
|
||||||
<Table.Body>
|
<Table.Body>
|
||||||
{tankStatsB
|
{delta.map((entry) => {
|
||||||
.filter((stat) => stat.all.battles > 0)
|
const tank = awaitedTankDefinitions[entry.tank_id];
|
||||||
.sort((a, b) => b.last_battle_time - a.last_battle_time)
|
const average = awaitedTankAvearges[tank.id];
|
||||||
.map((entry) => {
|
const stats = prettifyStats(
|
||||||
const tank = awaitedTankDefinitions[entry.tank_id];
|
generateStats(entry.all, average?.all),
|
||||||
const average = awaitedTankAvearges[tank.id];
|
);
|
||||||
const stats = prettifyStats(
|
|
||||||
generateStats(entry.all, average?.all),
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table.Row
|
<Table.Row
|
||||||
key={tank.id}
|
key={tank.id}
|
||||||
|
style={{
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table.RowHeaderCell
|
||||||
style={{
|
style={{
|
||||||
overflow: 'hidden',
|
paddingLeft: 32,
|
||||||
|
position: 'relative',
|
||||||
|
overflowY: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Table.RowHeaderCell
|
<Link href={`/tools/tankopedia/${tank.id}`}>
|
||||||
style={{
|
<img
|
||||||
paddingLeft: 32,
|
draggable={false}
|
||||||
position: 'relative',
|
src={tankIcon(tank.id)}
|
||||||
overflowY: 'hidden',
|
style={{
|
||||||
}}
|
position: 'absolute',
|
||||||
>
|
width: 128 + 32,
|
||||||
<Link href={`/tools/tankopedia/${tank.id}`}>
|
height: '200%',
|
||||||
<img
|
top: '-50%',
|
||||||
draggable={false}
|
left: 0,
|
||||||
src={tankIcon(tank.id)}
|
objectFit: 'contain',
|
||||||
style={{
|
objectPosition: '50% 50%',
|
||||||
position: 'absolute',
|
overflow: 'hidden',
|
||||||
width: 128 + 32,
|
}}
|
||||||
height: '200%',
|
/>
|
||||||
top: '-50%',
|
|
||||||
left: 0,
|
|
||||||
objectFit: 'contain',
|
|
||||||
objectPosition: '50% 50%',
|
|
||||||
overflow: 'hidden',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'red',
|
backgroundColor: 'red',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: 128,
|
width: 128,
|
||||||
background:
|
background:
|
||||||
'linear-gradient(90deg, #00000080, #00000000)',
|
'linear-gradient(90deg, #00000080, #00000000)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
textWrap: 'nowrap',
|
textWrap: 'nowrap',
|
||||||
textShadow: 'black 0 0 4px',
|
textShadow: 'black 0 0 4px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tank.name}
|
{tank.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Link>
|
</Link>
|
||||||
</Table.RowHeaderCell>
|
</Table.RowHeaderCell>
|
||||||
{session.columns.map((column) => (
|
{session.columns.map((column) => (
|
||||||
<Table.Cell key={column} align="right">
|
<Table.Cell key={column} align="right">
|
||||||
{stats[column]}
|
{stats[column]}
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
))}
|
))}
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Table.Body>
|
</Table.Body>
|
||||||
</Table.Root>
|
</Table.Root>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{session.tracking && tankStatsB && tankStatsB.length === 0 && (
|
{session.tracking && delta && delta.length === 0 && (
|
||||||
<Flex
|
<Flex
|
||||||
align="center"
|
align="center"
|
||||||
justify="center"
|
justify="center"
|
||||||
|
|||||||
@@ -15,12 +15,8 @@ import {
|
|||||||
Spinner,
|
Spinner,
|
||||||
} from '@radix-ui/themes';
|
} from '@radix-ui/themes';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Suspense, useEffect, useState } from 'react';
|
import { Suspense, useEffect } from 'react';
|
||||||
import {
|
import { REGIONS, UNLOCALIZED_REGION_NAMES } from '../../constants/regions';
|
||||||
Region,
|
|
||||||
REGIONS,
|
|
||||||
UNLOCALIZED_REGION_NAMES,
|
|
||||||
} from '../../constants/regions';
|
|
||||||
import { TOOLS } from '../../constants/tools';
|
import { TOOLS } from '../../constants/tools';
|
||||||
import { authURL } from '../../core/blitz/authURL';
|
import { authURL } from '../../core/blitz/authURL';
|
||||||
import { extendAuth } from '../../core/blitz/extendAuth';
|
import { extendAuth } from '../../core/blitz/extendAuth';
|
||||||
@@ -37,7 +33,6 @@ export default function Navbar() {
|
|||||||
const isFullScreen = useFullScreen();
|
const isFullScreen = useFullScreen();
|
||||||
const wideFormat = useWideFormat(480);
|
const wideFormat = useWideFormat(480);
|
||||||
const login = useApp((state) => state.login);
|
const login = useApp((state) => state.login);
|
||||||
const [region, setRegion] = useState<Region>('com');
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!login) return;
|
if (!login) return;
|
||||||
|
|||||||
@@ -1,68 +1,47 @@
|
|||||||
import {
|
import { IndividualTankStats } from '../../types/tanksStats';
|
||||||
IndividualTankStats,
|
|
||||||
NormalizedTankStats,
|
|
||||||
emptyIndividualTankStats,
|
|
||||||
} from '../../types/tanksStats';
|
|
||||||
|
|
||||||
export function deltaTankStats(
|
export function deltaTankStats(
|
||||||
aRaw: NormalizedTankStats,
|
tanksA: IndividualTankStats[],
|
||||||
bRaw: NormalizedTankStats,
|
tanksB: IndividualTankStats[],
|
||||||
time?: number,
|
|
||||||
) {
|
) {
|
||||||
const bTanks = Object.keys(bRaw);
|
return tanksB
|
||||||
const changedTanks = bTanks.filter((id) => {
|
.map((b) => {
|
||||||
return (
|
const a = tanksA.find((a) => a.tank_id === b.tank_id);
|
||||||
aRaw[id as unknown as number]?.all.battles !==
|
if (!a) return null;
|
||||||
bRaw[id as unknown as number].all.battles &&
|
|
||||||
(time === undefined
|
|
||||||
? true
|
|
||||||
: bRaw[id as unknown as number].last_battle_time > time)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
const diff = changedTanks.map((id) => {
|
|
||||||
const a = aRaw[id as unknown as number] ?? emptyIndividualTankStats;
|
|
||||||
const b = bRaw[id as unknown as number];
|
|
||||||
|
|
||||||
const tankDiff = {
|
return {
|
||||||
account_id: b.account_id,
|
account_id: b.account_id,
|
||||||
battle_life_time: b.battle_life_time,
|
battle_life_time: b.battle_life_time - a.battle_life_time,
|
||||||
frags: (b.frags ?? a.frags ?? 0) - (a.frags ?? 0),
|
frags: b.frags === null || a.frags === null ? null : b.frags - a.frags,
|
||||||
max_frags: Math.max(a.max_frags, b.max_frags),
|
in_garage: b.in_garage,
|
||||||
in_garage: b.in_garage,
|
in_garage_updated: b.in_garage_updated,
|
||||||
max_xp: Math.max(a.max_xp, b.max_xp),
|
last_battle_time: b.last_battle_time,
|
||||||
in_garage_updated: b.in_garage_updated,
|
mark_of_mastery: b.mark_of_mastery,
|
||||||
last_battle_time: Math.max(b.last_battle_time, a.last_battle_time),
|
max_frags: b.max_frags,
|
||||||
mark_of_mastery: b.mark_of_mastery,
|
max_xp: b.max_xp,
|
||||||
tank_id: b.tank_id,
|
tank_id: b.tank_id,
|
||||||
|
|
||||||
all: {
|
all: {
|
||||||
battles: b.all.battles - a.all.battles,
|
battles: b.all.battles - a.all.battles,
|
||||||
wins: b.all.wins - a.all.wins,
|
capture_points: b.all.capture_points - a.all.capture_points,
|
||||||
capture_points: b.all.capture_points - a.all.capture_points,
|
damage_dealt: b.all.damage_dealt - a.all.damage_dealt,
|
||||||
damage_dealt: b.all.damage_dealt - a.all.damage_dealt,
|
damage_received: b.all.damage_received - a.all.damage_received,
|
||||||
damage_received: b.all.damage_received - a.all.damage_received,
|
dropped_capture_points:
|
||||||
dropped_capture_points:
|
b.all.dropped_capture_points - a.all.dropped_capture_points,
|
||||||
b.all.dropped_capture_points - a.all.dropped_capture_points,
|
frags: b.all.frags - a.all.frags,
|
||||||
frags: b.all.frags - a.all.frags,
|
frags8p: b.all.frags8p - a.all.frags8p,
|
||||||
frags8p: b.all.frags8p - a.all.frags8p,
|
hits: b.all.hits - a.all.hits,
|
||||||
hits: b.all.hits - a.all.hits,
|
losses: b.all.losses - a.all.losses,
|
||||||
losses: b.all.losses - a.all.losses,
|
max_frags: b.all.max_frags,
|
||||||
max_frags: Math.max(a.max_frags, b.max_frags),
|
max_xp: b.all.max_xp,
|
||||||
max_xp: Math.max(a.max_xp, b.max_xp),
|
shots: b.all.shots - a.all.shots,
|
||||||
shots: b.all.shots - a.all.shots,
|
spotted: b.all.spotted - a.all.spotted,
|
||||||
spotted: b.all.spotted - a.all.spotted,
|
survived_battles: b.all.survived_battles - a.all.survived_battles,
|
||||||
survived_battles: b.all.survived_battles - a.all.survived_battles,
|
xp: b.all.xp - a.all.xp,
|
||||||
win_and_survived: b.all.win_and_survived - a.all.win_and_survived,
|
win_and_survived: b.all.win_and_survived - a.all.win_and_survived,
|
||||||
xp: b.all.xp - a.all.xp,
|
wins: b.all.wins - a.all.wins,
|
||||||
},
|
},
|
||||||
} satisfies IndividualTankStats;
|
} satisfies IndividualTankStats;
|
||||||
|
})
|
||||||
return tankDiff;
|
.filter((a) => a !== null && a.all.battles > 0) as IndividualTankStats[];
|
||||||
});
|
|
||||||
const normalized = diff.reduce<NormalizedTankStats>(
|
|
||||||
(accumulator, diff) => ({ ...accumulator, [diff.tank_id]: diff }),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
return normalized;
|
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import { create } from 'zustand';
|
|||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
import { Region } from '../constants/regions';
|
import { Region } from '../constants/regions';
|
||||||
import { Stat } from '../core/blitz/generateStats';
|
import { Stat } from '../core/blitz/generateStats';
|
||||||
|
import { IndividualTankStats } from '../types/tanksStats';
|
||||||
|
|
||||||
interface SessionBase {
|
interface SessionBase {
|
||||||
columns: Stat[];
|
columns: Stat[];
|
||||||
@@ -15,6 +16,7 @@ export interface SessionTracking extends SessionBase {
|
|||||||
id: number;
|
id: number;
|
||||||
region: Region;
|
region: Region;
|
||||||
since: number;
|
since: number;
|
||||||
|
stats: IndividualTankStats[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user