mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 18:57:03 +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 { tankIcon } from '../../../core/blitzkit/tankIcon';
|
||||
import { tankAverages } from '../../../core/blitzstars/tankAverages';
|
||||
import { deltaTankStats } from '../../../core/statistics/deltaTankStats';
|
||||
import { theme } from '../../../stitches.config';
|
||||
import { useApp } from '../../../stores/app';
|
||||
import mutateSession, {
|
||||
@@ -71,6 +72,15 @@ export default function Page() {
|
||||
[session.tracking && session.player.id],
|
||||
);
|
||||
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 () => {
|
||||
if (!input.current) return;
|
||||
@@ -248,7 +258,12 @@ export default function Page() {
|
||||
<Button
|
||||
key={player.account_id}
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
const stats = (await getTankStats(
|
||||
player.region,
|
||||
player.account_id,
|
||||
))!;
|
||||
|
||||
setShowSearch(false);
|
||||
mutateSession((draft) => {
|
||||
if (!input.current) return;
|
||||
@@ -258,6 +273,7 @@ export default function Page() {
|
||||
id: player.account_id,
|
||||
region: player.region,
|
||||
since: Date.now(),
|
||||
stats,
|
||||
};
|
||||
|
||||
input.current.value = player.nickname;
|
||||
@@ -308,7 +324,7 @@ export default function Page() {
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{session.tracking && tankStatsB && tankStatsB.length > 0 && (
|
||||
{session.tracking && delta && delta.length > 0 && (
|
||||
<Table.Root variant="surface">
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
@@ -322,84 +338,81 @@ export default function Page() {
|
||||
</Table.Header>
|
||||
|
||||
<Table.Body>
|
||||
{tankStatsB
|
||||
.filter((stat) => stat.all.battles > 0)
|
||||
.sort((a, b) => b.last_battle_time - a.last_battle_time)
|
||||
.map((entry) => {
|
||||
const tank = awaitedTankDefinitions[entry.tank_id];
|
||||
const average = awaitedTankAvearges[tank.id];
|
||||
const stats = prettifyStats(
|
||||
generateStats(entry.all, average?.all),
|
||||
);
|
||||
{delta.map((entry) => {
|
||||
const tank = awaitedTankDefinitions[entry.tank_id];
|
||||
const average = awaitedTankAvearges[tank.id];
|
||||
const stats = prettifyStats(
|
||||
generateStats(entry.all, average?.all),
|
||||
);
|
||||
|
||||
return (
|
||||
<Table.Row
|
||||
key={tank.id}
|
||||
return (
|
||||
<Table.Row
|
||||
key={tank.id}
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Table.RowHeaderCell
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
paddingLeft: 32,
|
||||
position: 'relative',
|
||||
overflowY: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Table.RowHeaderCell
|
||||
style={{
|
||||
paddingLeft: 32,
|
||||
position: 'relative',
|
||||
overflowY: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Link href={`/tools/tankopedia/${tank.id}`}>
|
||||
<img
|
||||
draggable={false}
|
||||
src={tankIcon(tank.id)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: 128 + 32,
|
||||
height: '200%',
|
||||
top: '-50%',
|
||||
left: 0,
|
||||
objectFit: 'contain',
|
||||
objectPosition: '50% 50%',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
/>
|
||||
<Link href={`/tools/tankopedia/${tank.id}`}>
|
||||
<img
|
||||
draggable={false}
|
||||
src={tankIcon(tank.id)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
width: 128 + 32,
|
||||
height: '200%',
|
||||
top: '-50%',
|
||||
left: 0,
|
||||
objectFit: 'contain',
|
||||
objectPosition: '50% 50%',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'red',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: '100%',
|
||||
width: 128,
|
||||
background:
|
||||
'linear-gradient(90deg, #00000080, #00000000)',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'red',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: '100%',
|
||||
width: 128,
|
||||
background:
|
||||
'linear-gradient(90deg, #00000080, #00000000)',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
color: theme.colors.textHighContrast,
|
||||
position: 'relative',
|
||||
textWrap: 'nowrap',
|
||||
textShadow: 'black 0 0 4px',
|
||||
}}
|
||||
>
|
||||
{tank.name}
|
||||
</Text>
|
||||
</Link>
|
||||
</Table.RowHeaderCell>
|
||||
{session.columns.map((column) => (
|
||||
<Table.Cell key={column} align="right">
|
||||
{stats[column]}
|
||||
</Table.Cell>
|
||||
))}
|
||||
</Table.Row>
|
||||
);
|
||||
})}
|
||||
<Text
|
||||
style={{
|
||||
color: theme.colors.textHighContrast,
|
||||
position: 'relative',
|
||||
textWrap: 'nowrap',
|
||||
textShadow: 'black 0 0 4px',
|
||||
}}
|
||||
>
|
||||
{tank.name}
|
||||
</Text>
|
||||
</Link>
|
||||
</Table.RowHeaderCell>
|
||||
{session.columns.map((column) => (
|
||||
<Table.Cell key={column} align="right">
|
||||
{stats[column]}
|
||||
</Table.Cell>
|
||||
))}
|
||||
</Table.Row>
|
||||
);
|
||||
})}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
)}
|
||||
|
||||
{session.tracking && tankStatsB && tankStatsB.length === 0 && (
|
||||
{session.tracking && delta && delta.length === 0 && (
|
||||
<Flex
|
||||
align="center"
|
||||
justify="center"
|
||||
|
||||
@@ -15,12 +15,8 @@ import {
|
||||
Spinner,
|
||||
} from '@radix-ui/themes';
|
||||
import Link from 'next/link';
|
||||
import { Suspense, useEffect, useState } from 'react';
|
||||
import {
|
||||
Region,
|
||||
REGIONS,
|
||||
UNLOCALIZED_REGION_NAMES,
|
||||
} from '../../constants/regions';
|
||||
import { Suspense, useEffect } from 'react';
|
||||
import { REGIONS, UNLOCALIZED_REGION_NAMES } from '../../constants/regions';
|
||||
import { TOOLS } from '../../constants/tools';
|
||||
import { authURL } from '../../core/blitz/authURL';
|
||||
import { extendAuth } from '../../core/blitz/extendAuth';
|
||||
@@ -37,7 +33,6 @@ export default function Navbar() {
|
||||
const isFullScreen = useFullScreen();
|
||||
const wideFormat = useWideFormat(480);
|
||||
const login = useApp((state) => state.login);
|
||||
const [region, setRegion] = useState<Region>('com');
|
||||
|
||||
useEffect(() => {
|
||||
if (!login) return;
|
||||
|
||||
@@ -1,68 +1,47 @@
|
||||
import {
|
||||
IndividualTankStats,
|
||||
NormalizedTankStats,
|
||||
emptyIndividualTankStats,
|
||||
} from '../../types/tanksStats';
|
||||
import { IndividualTankStats } from '../../types/tanksStats';
|
||||
|
||||
export function deltaTankStats(
|
||||
aRaw: NormalizedTankStats,
|
||||
bRaw: NormalizedTankStats,
|
||||
time?: number,
|
||||
tanksA: IndividualTankStats[],
|
||||
tanksB: IndividualTankStats[],
|
||||
) {
|
||||
const bTanks = Object.keys(bRaw);
|
||||
const changedTanks = bTanks.filter((id) => {
|
||||
return (
|
||||
aRaw[id as unknown as number]?.all.battles !==
|
||||
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];
|
||||
return tanksB
|
||||
.map((b) => {
|
||||
const a = tanksA.find((a) => a.tank_id === b.tank_id);
|
||||
if (!a) return null;
|
||||
|
||||
const tankDiff = {
|
||||
account_id: b.account_id,
|
||||
battle_life_time: b.battle_life_time,
|
||||
frags: (b.frags ?? a.frags ?? 0) - (a.frags ?? 0),
|
||||
max_frags: Math.max(a.max_frags, b.max_frags),
|
||||
in_garage: b.in_garage,
|
||||
max_xp: Math.max(a.max_xp, b.max_xp),
|
||||
in_garage_updated: b.in_garage_updated,
|
||||
last_battle_time: Math.max(b.last_battle_time, a.last_battle_time),
|
||||
mark_of_mastery: b.mark_of_mastery,
|
||||
tank_id: b.tank_id,
|
||||
return {
|
||||
account_id: b.account_id,
|
||||
battle_life_time: b.battle_life_time - a.battle_life_time,
|
||||
frags: b.frags === null || a.frags === null ? null : b.frags - a.frags,
|
||||
in_garage: b.in_garage,
|
||||
in_garage_updated: b.in_garage_updated,
|
||||
last_battle_time: b.last_battle_time,
|
||||
mark_of_mastery: b.mark_of_mastery,
|
||||
max_frags: b.max_frags,
|
||||
max_xp: b.max_xp,
|
||||
tank_id: b.tank_id,
|
||||
|
||||
all: {
|
||||
battles: b.all.battles - a.all.battles,
|
||||
wins: b.all.wins - a.all.wins,
|
||||
capture_points: b.all.capture_points - a.all.capture_points,
|
||||
damage_dealt: b.all.damage_dealt - a.all.damage_dealt,
|
||||
damage_received: b.all.damage_received - a.all.damage_received,
|
||||
dropped_capture_points:
|
||||
b.all.dropped_capture_points - a.all.dropped_capture_points,
|
||||
frags: b.all.frags - a.all.frags,
|
||||
frags8p: b.all.frags8p - a.all.frags8p,
|
||||
hits: b.all.hits - a.all.hits,
|
||||
losses: b.all.losses - a.all.losses,
|
||||
max_frags: Math.max(a.max_frags, b.max_frags),
|
||||
max_xp: Math.max(a.max_xp, b.max_xp),
|
||||
shots: b.all.shots - a.all.shots,
|
||||
spotted: b.all.spotted - a.all.spotted,
|
||||
survived_battles: b.all.survived_battles - a.all.survived_battles,
|
||||
win_and_survived: b.all.win_and_survived - a.all.win_and_survived,
|
||||
xp: b.all.xp - a.all.xp,
|
||||
},
|
||||
} satisfies IndividualTankStats;
|
||||
|
||||
return tankDiff;
|
||||
});
|
||||
const normalized = diff.reduce<NormalizedTankStats>(
|
||||
(accumulator, diff) => ({ ...accumulator, [diff.tank_id]: diff }),
|
||||
{},
|
||||
);
|
||||
|
||||
return normalized;
|
||||
all: {
|
||||
battles: b.all.battles - a.all.battles,
|
||||
capture_points: b.all.capture_points - a.all.capture_points,
|
||||
damage_dealt: b.all.damage_dealt - a.all.damage_dealt,
|
||||
damage_received: b.all.damage_received - a.all.damage_received,
|
||||
dropped_capture_points:
|
||||
b.all.dropped_capture_points - a.all.dropped_capture_points,
|
||||
frags: b.all.frags - a.all.frags,
|
||||
frags8p: b.all.frags8p - a.all.frags8p,
|
||||
hits: b.all.hits - a.all.hits,
|
||||
losses: b.all.losses - a.all.losses,
|
||||
max_frags: b.all.max_frags,
|
||||
max_xp: b.all.max_xp,
|
||||
shots: b.all.shots - a.all.shots,
|
||||
spotted: b.all.spotted - a.all.spotted,
|
||||
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,
|
||||
wins: b.all.wins - a.all.wins,
|
||||
},
|
||||
} satisfies IndividualTankStats;
|
||||
})
|
||||
.filter((a) => a !== null && a.all.battles > 0) as IndividualTankStats[];
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { Region } from '../constants/regions';
|
||||
import { Stat } from '../core/blitz/generateStats';
|
||||
import { IndividualTankStats } from '../types/tanksStats';
|
||||
|
||||
interface SessionBase {
|
||||
columns: Stat[];
|
||||
@@ -15,6 +16,7 @@ export interface SessionTracking extends SessionBase {
|
||||
id: number;
|
||||
region: Region;
|
||||
since: number;
|
||||
stats: IndividualTankStats[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user