diff --git a/src/app/multi/[multiId]/page.tsx b/src/app/multi/[multiId]/page.tsx index ee222ab2..973a116f 100644 --- a/src/app/multi/[multiId]/page.tsx +++ b/src/app/multi/[multiId]/page.tsx @@ -29,7 +29,10 @@ export default async function Page({ params }: PageProps) {

{multi.name}

- instance.instanceId)} /> + instance.instanceId)} + /> ) diff --git a/src/components/multi/multi-combinator.tsx b/src/components/multi/multi-combinator.tsx deleted file mode 100644 index e5ff35d3..00000000 --- a/src/components/multi/multi-combinator.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import { Collection } from "@discordjs/collection" -import { useMemo } from "react" -import type { - CombinedCharacterStats, - CombinedData, - CombinedPlayerStats, - CombinedWeaponData -} from "~/lib/multi/multi-types" -import { generateSortScore } from "~/lib/pgcr/riis" -import { type RaidHubInstanceExtended } from "~/services/raidhub/types" -import { Card, CardContent, CardHeader } from "~/shad/card" -import { MultiDetails } from "./multi-details" -import { MultiPlayer } from "./multi-player" -import { MultiWeaponsList } from "./multi-weapons-list" - -export const MultiInstanceCombinator = (props: { data: RaidHubInstanceExtended[] }) => { - const combined = useMemo(() => { - const reduced = props.data.reduce>( - (acc, instance) => { - acc.instances.push({ - activityName: instance.metadata.activityName, - versionName: instance.metadata.versionName, - instanceId: instance.instanceId, - duration: instance.duration, - start: new Date(instance.dateStarted), - end: new Date(instance.dateCompleted), - completed: instance.completed - }) - - acc.duration += instance.duration - if (instance.completed) { - acc.completions += 1 - } - - instance.players.forEach(player => { - const existingPlayer = acc.players.get(player.playerInfo.membershipId) - - const playerWeapons = - existingPlayer?.weapons ?? new Collection() - - const characters = player.characters.reduce((charAcc, char) => { - const existingChar = charAcc.get(char.characterId) - - char.weapons.forEach(weap => { - playerWeapons.set(weap.weaponHash, { - hash: weap.weaponHash, - kills: - weap.kills + (playerWeapons.get(weap.weaponHash)?.kills ?? 0), - precisionKills: - weap.precisionKills + - (playerWeapons.get(weap.weaponHash)?.precisionKills ?? 0) - }) - acc.weapons.set(weap.weaponHash, { - hash: weap.weaponHash, - kills: weap.kills + (acc.weapons.get(weap.weaponHash)?.kills ?? 0), - precisionKills: - weap.precisionKills + - (acc.weapons.get(weap.weaponHash)?.precisionKills ?? 0) - }) - }) - - if (existingChar) { - existingChar.timePlayedSeconds += char.timePlayedSeconds - existingChar.score += char.score - existingChar.kills += char.kills - existingChar.deaths += char.deaths - existingChar.assists += char.assists - existingChar.precisionKills += char.precisionKills - existingChar.superKills += char.superKills - existingChar.grenadeKills += char.grenadeKills - existingChar.meleeKills += char.meleeKills - } else { - charAcc.set(char.characterId, { - classHash: char.classHash, - startSeconds: 0, - completed: char.completed, - timePlayedSeconds: char.timePlayedSeconds, - score: char.score, - kills: char.kills, - deaths: char.deaths, - assists: char.assists, - precisionKills: char.precisionKills, - superKills: char.superKills, - grenadeKills: char.grenadeKills, - meleeKills: char.meleeKills - }) - } - return charAcc - }, existingPlayer?.characters ?? new Collection()) - - const { kills, deaths, assists } = player.characters.reduce( - (stats, char) => { - stats.kills += char.kills - stats.deaths += char.deaths - stats.assists += char.assists - return stats - }, - { kills: 0, deaths: 0, assists: 0 } - ) - - if (existingPlayer) { - acc.players.set(player.playerInfo.membershipId, { - kills: existingPlayer.kills + kills, - deaths: existingPlayer.deaths + deaths, - assists: existingPlayer.assists + assists, - playerInfo: player.playerInfo, - characters, - completions: +player.completed + existingPlayer.completions, - timePlayedSeconds: - player.timePlayedSeconds + existingPlayer.timePlayedSeconds, - riis: 0, - weapons: playerWeapons - }) - } else { - return acc.players.set(player.playerInfo.membershipId, { - kills, - deaths, - assists, - playerInfo: player.playerInfo, - characters, - completions: +player.completed, - timePlayedSeconds: player.timePlayedSeconds, - riis: 0, - weapons: playerWeapons - }) - } - }) - - return acc - }, - { - instances: [], - duration: 0, - completions: 0, - players: new Collection(), - weapons: new Collection() - } - ) - - reduced.players.forEach(player => { - // Calculate the RIIS score for each player - player.riis = generateSortScore( - { - characters: Object.freeze(Array.from(player.characters.values())), - timePlayedSeconds: player.timePlayedSeconds - }, - { - capTPS: false - } - ) - }) - - return { - ...reduced, - players: reduced.players.sort((a, b) => - a.completions === b.completions ? b.riis - a.riis : b.completions - a.completions - ), - dates: reduced.instances.sort((a, b) => a.start.getTime() - b.start.getTime()), - overallStart: reduced.instances.reduce( - (min, d) => (d.start < min ? d.start : min), - new Date("9999-12-31") - ), - overallEnd: reduced.instances.reduce( - (max, d) => (d.end > max ? d.end : max), - new Date(0) - ) - } - }, [props.data]) - - return ( - - - - - -
-
KILLS
-
DEATHS
-
ASSISTS
-
TIME PLAYED
-
RIIS
-
-
- {combined.players.map((p, membershipId) => ( - - ))} -
- -
- -
-
-
- ) -} diff --git a/src/components/multi/multi-loader.tsx b/src/components/multi/multi-loader.tsx index fc10d795..986a0188 100644 --- a/src/components/multi/multi-loader.tsx +++ b/src/components/multi/multi-loader.tsx @@ -1,9 +1,10 @@ "use client" +import PGCR from "~/components/pgcr/pgcr-view" +import { mergeRaidHubInstances } from "~/lib/multi/merge-instances" import { useRaidHubInstanceList } from "~/services/raidhub/useRaidHubInstance" -import { MultiInstanceCombinator } from "./multi-combinator" -export const MultiLoader = ({ instances }: { instances: string[] }) => { +export const MultiLoader = ({ multiId, instances }: { multiId: string; instances: string[] }) => { const queries = useRaidHubInstanceList(instances) const isLoading = queries.some(q => q.isLoading) @@ -12,12 +13,10 @@ export const MultiLoader = ({ instances }: { instances: string[] }) => { const progress = 1 - queries.filter(q => q.isLoading).length / queries.length return (
- {/* Progress bar shows how many instances have finished loading for the user */} Loading data: {Math.round(progress * 100)}%
- {/* The filled part represents the percentage of loaded instances */}
{
) } - return q.data!).filter(Boolean)} /> + + const loaded = queries.map(q => q.data).filter((d): d is NonNullable => !!d) + if (loaded.length === 0) { + return

Could not load instance data.

+ } + + const mergedMulti = loaded.length > 1 + const { merged, timeline } = mergedMulti + ? mergeRaidHubInstances(loaded, multiId) + : { merged: loaded[0], timeline: undefined } + + return ( + + ) } diff --git a/src/components/multi/multi-player.tsx b/src/components/multi/multi-player.tsx deleted file mode 100644 index a8940948..00000000 --- a/src/components/multi/multi-player.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import { ChevronDown } from "lucide-react" -import { useState } from "react" -import { useGetCharacterClass } from "~/hooks/pgcr/useCharacterClass" -import { type CombinedPlayerStats } from "~/lib/multi/multi-types" -import { cn } from "~/lib/tw" -import { Avatar, AvatarFallback, AvatarImage } from "~/shad/avatar" -import { Badge } from "~/shad/badge" -import { Button } from "~/shad/button" -import { Card, CardContent, CardHeader, CardTitle } from "~/shad/card" -import { Tooltip, TooltipContent, TooltipTrigger } from "~/shad/tooltip" -import { bungieIconUrl, getBungieDisplayName } from "~/util/destiny" -import { secondsToHMS } from "~/util/presentation/formatting" -import { MultiWeaponsList } from "./multi-weapons-list" - -export const MultiPlayer = ({ player }: { player: CombinedPlayerStats }) => { - const [isExpanded, setIsExpanded] = useState(false) - const getCharacterIcon = useGetCharacterClass() - - return ( -
-
-
-
-
- {player.characters.map((character, characterId) => { - const [CharacterIcon, characterName] = getCharacterIcon( - character.classHash - ) - return ( - - - - - - {characterName} - - - ) - })} -
- - - - {player.playerInfo.displayName!.slice(0, 2).toUpperCase()} - - -
- {getBungieDisplayName(player.playerInfo, { excludeCode: true })} -
-
- -
-
- {player.completions > 0 ? ( - 0 && "bg-green-600", - player.completions > 1 && "bg-amber-400" - )}> - {player.completions > 1 - ? `Completed ${player.completions}x` - : "Completed"} - - ) : ( - Incomplete - )} -
-
-
{player.kills}
-
{player.deaths}
-
{player.assists}
-
- {secondsToHMS(player.timePlayedSeconds, false)} -
-
{player.riis.toFixed(3)}
-
- -
- -
-
- - {isExpanded && } -
- ) -} - -const MultiPlayerExpanded = ({ player }: { player: CombinedPlayerStats }) => { - const getCharacterIcon = useGetCharacterClass() - return ( -
- - - Stats - - -
-
- Kills - {player.kills} -
-
- Deaths - {player.deaths} -
-
- Assists - {player.assists} -
-
- Time Played - - {secondsToHMS(player.timePlayedSeconds, false)} - -
-
- RIIS - {player.riis.toFixed(3)} -
-
-
-
- - - Characters - - - {player.characters.map((ch, cid) => { - const [, characterClass] = getCharacterIcon(ch.classHash) - return ( -
-
-
-
{characterClass}
-
- K {ch.kills} • D {ch.deaths} • A {ch.assists} • Time{" "} - {secondsToHMS(ch.timePlayedSeconds, false)} -
-
-
- Melee {ch.meleeKills} • - Grenade {ch.grenadeKills} • - Super {ch.superKills} -
-
-
- ) - })} -
-
- - - - Weapons - - - - - -
- ) -} diff --git a/src/components/multi/multi-weapons-list.tsx b/src/components/multi/multi-weapons-list.tsx deleted file mode 100644 index e62086ba..00000000 --- a/src/components/multi/multi-weapons-list.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import Image from "next/image" -import { useItemDefinition } from "~/hooks/dexie" -import { type CombinedWeaponData } from "~/lib/multi/multi-types" -import { bungieIconUrl } from "~/util/destiny" - -export const MultiWeaponsList = ({ weapons }: { weapons: CombinedWeaponData[] }) => { - return ( -
- {weapons - .sort((a, b) => b.kills - a.kills) - .map(weapon => ( - - ))} -
- ) -} - -const WeaponItem = ({ weapon }: { weapon: CombinedWeaponData }) => { - const definition = useItemDefinition(weapon.hash) - const icon = bungieIconUrl(definition?.displayProperties.icon) - return ( -
- {definition?.displayProperties.name -
-
- {definition?.displayProperties.name ?? "Unknown Weapon"} -
-
- Kills: {weapon.kills} ({weapon.precisionKills}) -
-
-
- ) -} diff --git a/src/components/pgcr/pgcr-menu.tsx b/src/components/pgcr/pgcr-menu.tsx index afb1a5f6..938568bb 100644 --- a/src/components/pgcr/pgcr-menu.tsx +++ b/src/components/pgcr/pgcr-menu.tsx @@ -19,7 +19,7 @@ import { ReportModal } from "./pgcr-report-modal" export const PGCRMenu = () => { const session = useSession() - const { data, setIsBlacklisted } = usePGCRContext() + const { data, setIsBlacklisted, allowsReporting } = usePGCRContext() const [isMenuOpen, setIsMenuOpen] = useState(false) const [isReportModalOpen, setIsReportModalOpen] = useState(false) const [isBlacklistModalOpen, setIsBlacklistModalOpen] = useState(false) @@ -55,7 +55,7 @@ export const PGCRMenu = () => { Share - {session.status === "authenticated" && ( + {session.status === "authenticated" && allowsReporting && ( <> diff --git a/src/components/multi/multi-details.tsx b/src/components/pgcr/pgcr-multi-timeline.tsx similarity index 56% rename from src/components/multi/multi-details.tsx rename to src/components/pgcr/pgcr-multi-timeline.tsx index fb493e76..3a30d0a3 100644 --- a/src/components/multi/multi-details.tsx +++ b/src/components/pgcr/pgcr-multi-timeline.tsx @@ -1,46 +1,65 @@ +"use client" + import Link from "next/link" -import { useCallback } from "react" -import { type CombinedData } from "~/lib/multi/multi-types" +import { useCallback, useMemo } from "react" +import { type MultiInstanceTimelineSegment } from "~/lib/multi/multi-types" import { cn } from "~/lib/tw" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/shad/card" import { Tooltip, TooltipContent, TooltipTrigger } from "~/shad/tooltip" import { secondsToHMS } from "~/util/presentation/formatting" -export const MultiDetails = ({ data }: { data: CombinedData }) => { +export const PGCRMultiTimeline = ({ segments }: { segments: MultiInstanceTimelineSegment[] }) => { + const { overallStart, overallEnd } = useMemo(() => { + if (!segments.length) { + return { overallStart: new Date(0), overallEnd: new Date(0) } + } + return { + overallStart: segments.reduce( + (min, d) => (d.start < min ? d.start : min), + segments[0].start + ), + overallEnd: segments.reduce((max, d) => (d.end > max ? d.end : max), segments[0].end) + } + }, [segments]) + const getRatio = useCallback( (date: Date) => { - const total = data.overallEnd.getTime() - data.overallStart.getTime() - return (date.getTime() - data.overallStart.getTime()) / total + const total = overallEnd.getTime() - overallStart.getTime() + if (total <= 0) return 0 + return (date.getTime() - overallStart.getTime()) / total }, - [data.overallEnd, data.overallStart] + [overallEnd, overallStart] + ) + + const totalDurationSeconds = useMemo( + () => segments.reduce((s, d) => s + d.duration, 0), + [segments] ) + const totalElapsedSeconds = (overallEnd.getTime() - overallStart.getTime()) / 1000 + + if (!segments.length) { + return null + } return ( - - - Timeline + + + Session timeline - Total Playtime: {secondsToHMS(data.duration, false)} -
- Total Elapsed Time:{" "} - {secondsToHMS( - (data.overallEnd.getTime() - data.overallStart.getTime()) / 1000, - false - )} + Total in-activity time: {secondsToHMS(totalDurationSeconds, false)}
- {data.completions > 1 && `Completions: ${data.completions}`} + Total elapsed: {secondsToHMS(totalElapsedSeconds, false)}
- +
- {/* Base filled segments */} - {data.instances.map((d, i) => { + {segments.map((d, i) => { if (!d.start) return null const startPct = getRatio(d.start) * 100 const endPct = getRatio(d.end) * 100 return ( - + { })}
- {data.overallStart.toLocaleString()} - {data.overallEnd.toLocaleString()} + {overallStart.toLocaleString()} + {overallEnd.toLocaleString()}
diff --git a/src/components/pgcr/pgcr-view.tsx b/src/components/pgcr/pgcr-view.tsx index 39c54c22..068b9500 100644 --- a/src/components/pgcr/pgcr-view.tsx +++ b/src/components/pgcr/pgcr-view.tsx @@ -1,24 +1,37 @@ +"use client" + import { Collection } from "@discordjs/collection" import { ClientStateManager } from "~/hooks/pgcr/ClientStateManager" +import { type MultiInstanceTimelineSegment } from "~/lib/multi/multi-types" import { generateSortScore } from "~/lib/pgcr/riis" import { type RaidHubInstanceExtended } from "~/services/raidhub/types" import { Card } from "~/shad/card" import { Separator } from "~/shad/separator" import { PGCRHeader } from "./pgcr-header" +import { PGCRMultiTimeline } from "./pgcr-multi-timeline" import { PGCRPlayers } from "./pgcr-players" import { PlayerDetailsPanelWrapper } from "./player-details-panel" interface PGCRProps { data: RaidHubInstanceExtended + /** Merged multi-instance sessions use uncapped time in RIIS (matches legacy multi combinator). */ + sortScoreOptions?: { capTPS: boolean } + multiTimeline?: MultiInstanceTimelineSegment[] + allowsReporting?: boolean } -export default function PGCR({ data }: PGCRProps) { +export default function PGCR({ + data, + sortScoreOptions = { capTPS: true }, + multiTimeline, + allowsReporting = true +}: PGCRProps) { const sortScores = new Collection( data.players.map(p => [ p.playerInfo.membershipId, { completed: p.completed, - score: generateSortScore(p) + score: generateSortScore(p, sortScoreOptions) } ]) ).toSorted((a, b) => { @@ -67,13 +80,21 @@ export default function PGCR({ data }: PGCRProps) { data={data} mvp={mvp} scores={Array.from(sortScores.entries())} - playerStatsMerged={Array.from(playerMergedStats.entries())}> + playerStatsMerged={Array.from(playerMergedStats.entries())} + allowsReporting={allowsReporting}>
+ {multiTimeline && multiTimeline.length > 1 && ( + <> + + + + )} + + /** When false (e.g. merged multi-instance PGCR), hide reporting — no single RaidHub instance applies. */ + allowsReporting?: boolean children: ReactNode } @@ -58,6 +60,7 @@ interface PGCRState { setIsReportModalOpen: Dispatch> query: RaidHubQueryParams selectedFeats: RaidHubFeatDefinition[] + allowsReporting: boolean } const PGCRContext = createContext(undefined) @@ -74,6 +77,7 @@ export const ClientStateManager = ({ playerStatsMerged, mvp, scores, + allowsReporting = true, children }: ClientStateManagerProps) => { const queryClient = useQueryClient() @@ -135,7 +139,8 @@ export const ClientStateManager = ({ isReportModalOpen, setIsReportModalOpen, query, - selectedFeats + selectedFeats, + allowsReporting }}> {children} diff --git a/src/lib/multi/merge-instances.ts b/src/lib/multi/merge-instances.ts new file mode 100644 index 00000000..2a34b22a --- /dev/null +++ b/src/lib/multi/merge-instances.ts @@ -0,0 +1,182 @@ +import type { MultiInstanceTimelineSegment } from "~/lib/multi/multi-types" +import type { + RaidHubInstanceCharacter, + RaidHubInstanceExtended, + RaidHubInstancePlayerExtended +} from "~/services/raidhub/types" + +type InstanceCharacterWeapon = RaidHubInstanceCharacter["weapons"][number] + +function segmentFromInstance(instance: RaidHubInstanceExtended): MultiInstanceTimelineSegment { + return { + instanceId: instance.instanceId, + duration: instance.duration, + start: new Date(instance.dateStarted), + end: new Date(instance.dateCompleted), + activityName: instance.metadata.activityName, + versionName: instance.metadata.versionName, + completed: instance.completed + } +} + +function mergeWeaponLists( + a: readonly InstanceCharacterWeapon[], + b: readonly InstanceCharacterWeapon[] +): InstanceCharacterWeapon[] { + const map = new Map() + for (const w of [...a, ...b]) { + const prev = map.get(w.weaponHash) + map.set(w.weaponHash, { + weaponHash: w.weaponHash, + kills: (prev?.kills ?? 0) + w.kills, + precisionKills: (prev?.precisionKills ?? 0) + w.precisionKills + }) + } + return [...map.values()] +} + +function mergeCharacterStats( + existing: RaidHubInstanceCharacter, + incoming: RaidHubInstanceCharacter +): RaidHubInstanceCharacter { + return { + characterId: existing.characterId, + classHash: existing.classHash ?? incoming.classHash, + emblemHash: incoming.emblemHash ?? existing.emblemHash, + completed: existing.completed && incoming.completed, + timePlayedSeconds: existing.timePlayedSeconds + incoming.timePlayedSeconds, + startSeconds: Math.min(existing.startSeconds, incoming.startSeconds), + score: existing.score + incoming.score, + kills: existing.kills + incoming.kills, + deaths: existing.deaths + incoming.deaths, + assists: existing.assists + incoming.assists, + precisionKills: existing.precisionKills + incoming.precisionKills, + superKills: existing.superKills + incoming.superKills, + grenadeKills: existing.grenadeKills + incoming.grenadeKills, + meleeKills: existing.meleeKills + incoming.meleeKills, + weapons: mergeWeaponLists(existing.weapons, incoming.weapons) + } +} + +type PlayerMergeAcc = { + playerInfo: RaidHubInstancePlayerExtended["playerInfo"] + characters: Map + completions: number + instancesParticipated: number + isFirstClear: boolean + sherpas: number + timePlayedSeconds: number +} + +function mergeNullableBool(values: readonly (boolean | null)[]): boolean | null { + if (values.length === 0) return null + if (values.every(v => v === true)) return true + if (values.every(v => v === false)) return false + if (values.every(v => v === null)) return null + return false +} + +/** + * Merges multiple PGCR instances (same fireteam session) into one synthetic {@link RaidHubInstanceExtended} + * so the standard PGCR UI can render aggregated stats. + */ +export function mergeRaidHubInstances( + instances: RaidHubInstanceExtended[], + multiId: string +): { + merged: RaidHubInstanceExtended + timeline: MultiInstanceTimelineSegment[] +} { + const sorted = [...instances].sort( + (a, b) => new Date(a.dateStarted).getTime() - new Date(b.dateStarted).getTime() + ) + + const timeline = sorted.map(segmentFromInstance) + + if (sorted.length === 0) { + throw new Error("mergeRaidHubInstances: empty instances") + } + + if (sorted.length === 1) { + const only = sorted[0] + return { merged: only, timeline } + } + + const first = sorted[0] + const last = sorted[sorted.length - 1] + + const playerMap = new Map() + + for (const instance of sorted) { + for (const player of instance.players) { + const existing = playerMap.get(player.playerInfo.membershipId) + const charMap = new Map() + for (const c of player.characters) { + charMap.set(c.characterId, c) + } + + if (!existing) { + playerMap.set(player.playerInfo.membershipId, { + playerInfo: player.playerInfo, + characters: charMap, + completions: player.completed ? 1 : 0, + instancesParticipated: 1, + isFirstClear: player.isFirstClear, + sherpas: player.sherpas, + timePlayedSeconds: player.timePlayedSeconds + }) + } else { + existing.completions += player.completed ? 1 : 0 + existing.instancesParticipated += 1 + existing.isFirstClear = existing.isFirstClear || player.isFirstClear + existing.sherpas += player.sherpas + existing.timePlayedSeconds += player.timePlayedSeconds + existing.playerInfo = player.playerInfo + + for (const c of player.characters) { + const prevChar = existing.characters.get(c.characterId) + existing.characters.set( + c.characterId, + prevChar ? mergeCharacterStats(prevChar, c) : c + ) + } + } + } + } + + const players: RaidHubInstancePlayerExtended[] = [...playerMap.values()].map(acc => ({ + playerInfo: acc.playerInfo, + completed: acc.completions === acc.instancesParticipated, + isFirstClear: acc.isFirstClear, + sherpas: acc.sherpas, + timePlayedSeconds: acc.timePlayedSeconds, + characters: [...acc.characters.values()] + })) + + const merged: RaidHubInstanceExtended = { + ...first, + instanceId: `multi:${multiId}`, + completed: sorted.every(i => i.completed), + flawless: mergeNullableBool(sorted.map(i => i.flawless)), + fresh: mergeNullableBool(sorted.map(i => i.fresh)), + playerCount: players.length, + skullHashes: [...new Set(sorted.flatMap(i => [...i.skullHashes]))], + score: sorted.reduce((s, i) => s + i.score, 0), + dateStarted: first.dateStarted, + dateCompleted: last.dateCompleted, + season: first.season, + duration: sorted.reduce((s, i) => s + i.duration, 0), + platformType: first.platformType, + activityId: first.activityId, + versionId: first.versionId, + isDayOne: sorted.every(i => i.isDayOne), + isContest: sorted.every(i => i.isContest), + isWeekOne: sorted.every(i => i.isWeekOne), + isBlacklisted: sorted.some(i => i.isBlacklisted), + leaderboardRank: null, + metadata: first.metadata, + players + } + + return { merged, timeline } +} diff --git a/src/lib/multi/multi-types.ts b/src/lib/multi/multi-types.ts index 1df214f7..7cba2197 100644 --- a/src/lib/multi/multi-types.ts +++ b/src/lib/multi/multi-types.ts @@ -1,49 +1,10 @@ -import { type Collection } from "@discordjs/collection" -import { type RaidHubPlayerInfo } from "~/services/raidhub/types" - -export type CombinedData = { - instances: { - instanceId: string - duration: number - start: Date - end: Date - activityName: string - versionName: string - completed: boolean - }[] - overallStart: Date - overallEnd: Date +/** Timeline strip for multi-instance PGCR views (session composed of multiple instance IDs). */ +export type MultiInstanceTimelineSegment = { + instanceId: string duration: number - players: Collection - completions: number - weapons: Collection -} - -export type CombinedPlayerStats = { - playerInfo: RaidHubPlayerInfo - characters: Collection - completions: number - timePlayedSeconds: number - kills: number - deaths: number - assists: number - riis: number - weapons: Collection -} - -export type CombinedCharacterStats = { - classHash: number | null - startSeconds: number + start: Date + end: Date + activityName: string + versionName: string completed: boolean - timePlayedSeconds: number - score: number - kills: number - deaths: number - assists: number - precisionKills: number - superKills: number - grenadeKills: number - meleeKills: number } - -export type CombinedWeaponData = { hash: number; kills: number; precisionKills: number }