diff --git a/src/app/(stats)/player/[ign]/_stats/murder-mystery/murder-mystery.tsx b/src/app/(stats)/player/[ign]/_stats/murder-mystery/murder-mystery.tsx index 3cf858d..904d87b 100644 --- a/src/app/(stats)/player/[ign]/_stats/murder-mystery/murder-mystery.tsx +++ b/src/app/(stats)/player/[ign]/_stats/murder-mystery/murder-mystery.tsx @@ -5,6 +5,7 @@ import { formatNumber } from "@/lib/formatters" import { NonNullStats } from "@/lib/schema/player" import CollapsedStats from "../../_components/CollapsedStats" import MurderMysteryGeneralStats from "./stats" +import MurderMysteryStatTable from "./table" export default function MurderMysteryStats({ stats }: { stats: NonNullStats["MurderMystery"] }) { if (!stats) return null @@ -33,6 +34,9 @@ export default function MurderMysteryStats({ stats }: { stats: NonNullStats["Mur + + + diff --git a/src/app/(stats)/player/[ign]/_stats/murder-mystery/table.tsx b/src/app/(stats)/player/[ign]/_stats/murder-mystery/table.tsx new file mode 100644 index 0000000..2d71406 --- /dev/null +++ b/src/app/(stats)/player/[ign]/_stats/murder-mystery/table.tsx @@ -0,0 +1,69 @@ +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { formatNumber } from "@/lib/formatters" +import { getBestMurderMysteryMode, getModeTitle, getMurderMysteryModeStats } from "@/lib/hypixel/murder-mystery/general" +import { NonNullStats } from "@/lib/schema/player" +import { cn } from "@/lib/utils" + +export default function MurderMysteryStatTable({ stats }: { stats: NonNullStats["MurderMystery"] }) { + return ( + + + + + + + + + + +
+ ) +} + +function MurderMysteryTableHeader() { + const headerElements = [ + "Mode", + "Kills", + "Bow Kills", + "Knife Kills", + "Thrown Knife Kills", + "Wins", + "Losses", + "WL", + "Gold Collected" + ] + + return ( + + + {headerElements.map((v, i) => { + return {v} + })} + + + ) +} + +function MurderMysteryStat({ modeId, stats }: { modeId: Parameters[0], stats: NonNullStats["MurderMystery"] }) { + if (!stats) return null + + const title = getModeTitle(modeId) + const modeStats = getMurderMysteryModeStats(modeId, stats) + const bestMode = getBestMurderMysteryMode(stats) === modeId + + return ( + + {title} + {modeStats.map((v, i) => ( + + {formatNumber(v)} + + ))} + + ) +} diff --git a/src/data/hypixel/murder-mystery.ts b/src/data/hypixel/murder-mystery.ts index d5323ba..08af9f5 100644 --- a/src/data/hypixel/murder-mystery.ts +++ b/src/data/hypixel/murder-mystery.ts @@ -1,10 +1,10 @@ export const TITLE = "Murder Mystery" as const export const MODES = [ - { id: "_MURDER_CLASSIC", name: "Classic" }, - { id: "_MURDER_ASSASSINS", name: "Assassins" }, - { id: "_MURDER_DOUBLE_UP", name: "Double Up" }, - { id: "_MURDER_HARDCORE", name: "Hardcore" }, - { id: "_MURDER_SHOWDOWN", name: "Showdown" }, + { id: "MURDER_CLASSIC", name: "Classic" }, + { id: "MURDER_ASSASSINS", name: "Assassins" }, + { id: "MURDER_DOUBLE_UP", name: "Double Up" }, + { id: "MURDER_HARDCORE", name: "Hardcore" }, + { id: "MURDER_SHOWDOWN", name: "Showdown" }, { id: "", name: "Overall" } ] as const export const KNIFESKINS = { diff --git a/src/lib/hypixel/murder-mystery/general.ts b/src/lib/hypixel/murder-mystery/general.ts index d8b0254..71b1b9b 100644 --- a/src/lib/hypixel/murder-mystery/general.ts +++ b/src/lib/hypixel/murder-mystery/general.ts @@ -1,4 +1,6 @@ -import { KNIFESKINS } from "@/data/hypixel/murder-mystery" +import { KNIFESKINS, MODES } from "@/data/hypixel/murder-mystery" +import { NonNullStats } from "@/lib/schema/player" +import { devide } from "../general" export function getKnifeName(name?: string) { if (!name) return KNIFESKINS.undefined @@ -9,3 +11,60 @@ export function getKnifeName(name?: string) { return skin } + +export function getModeTitle(mode: Exclude | "all_modes") { + if (mode === "all_modes") return MODES.find(m => m.id === "")!.name + + return MODES.find(m => m.id === mode)!.name +} + +export function getBestMurderMysteryMode(stats: NonNullable) { + let best: typeof MODES[number] | null = null + let mostPlays = 0 + for (const mode of MODES.filter(m => m.id !== "")) { + const [, , , wins, losses] = getMurderMysteryModeStats(mode.id, stats) + const plays = (wins as number || 0) + (losses as number || 0) + if (plays > mostPlays) { + mostPlays = plays + best = mode + } + } + return best === null ? null : best.id +} + +export function getMurderMysteryModeStats( + index: Exclude | "all_modes", + stats: NonNullable +) { + return murderMysteryModeStats(index, stats) +} + +function murderMysteryModeStats(index: Exclude | "all_modes", stats: NonNullable) { + if (index === "all_modes") { + const losses = stats["games"] - stats["wins"] + + return [ + stats["kills"], + stats["bow_kills"], + stats["knife_kills"], + stats["thrown_knife_kills"], + stats["wins"], + losses, + devide(stats["wins"], losses), + stats["coins_pickedup"] + ] + } + + const losses = stats[`games_${index}`] - stats[`wins_${index}`] + + return [ + stats[`kills_${index}`], + stats[`bow_kills_${index}`], + stats[`knife_kills_${index}`], + stats[`thrown_knife_kills_${index}`], + stats[`wins_${index}`], + losses, + devide(stats[`wins_${index}`], losses), + stats[`coins_pickedup_${index}`] + ] +} diff --git a/src/lib/schema/stats.ts b/src/lib/schema/stats.ts index f476967..a0e35b6 100644 --- a/src/lib/schema/stats.ts +++ b/src/lib/schema/stats.ts @@ -331,15 +331,50 @@ export const duelsStatsSchema = z.looseObject({ ...duelsModeStats().bridge }) +function murderMysteryModeStats() { + const ids = [ + "MURDER_CLASSIC", + "MURDER_ASSASSINS", + "MURDER_DOUBLE_UP", + "MURDER_HARDCORE", + "MURDER_SHOWDOWN" + ] as const + + const stats = [ + "kills", + "bow_kills", + "knife_kills", + "thrown_knife_kills", + "wins", + "games", + "coins_pickedup" + ] as const + + const entries = new Map>() + + for (const id of ids) { + for (const stat of stats) { + entries.set(`${stat}_${id}`, z.number().default(0)) + } + } + + return Object.fromEntries(entries) as Record<`${typeof stats[number]}_${typeof ids[number]}`, z.ZodDefault> +} + export const murderMysteryStatsSchema = z.looseObject({ kills: z.number().default(0), deaths: z.number().default(0), wins: z.number().default(0), losses: z.number().default(0), coins: z.number().default(0), + games: z.number().default(0), + bow_kills: z.number().default(0), + knife_kills: z.number().default(0), + coins_pickedup: z.number().default(0), kills_as_murderer: z.number().default(0), thrown_knife_kills: z.number().default(0), active_knife_skin: z.string().optional(), quickest_detective_win_time_seconds: z.number().default(0), - quickest_murderer_win_time_seconds: z.number().default(0) + quickest_murderer_win_time_seconds: z.number().default(0), + ...murderMysteryModeStats() })