Finished general stats

This commit is contained in:
2025-09-02 22:13:56 +02:00
parent 7008b44a00
commit e536054d2d
5 changed files with 106 additions and 5 deletions

View File

@@ -1,22 +1,39 @@
import { formatNumber } from "@/lib/formatters" import { formatNumber, formatSecondsToTime } from "@/lib/formatters"
import { devide } from "@/lib/hypixel/general"
import { getKnifeName } from "@/lib/hypixel/murder-mystery/general"
import { NonNullStats } from "@/lib/schema/player" import { NonNullStats } from "@/lib/schema/player"
import { BasicStat } from "../../_components/Stats" import { BasicStat } from "../../_components/Stats"
export default function MurderMysteryGeneralStats({ statsChecked }: { statsChecked: NonNullStats["MurderMystery"] }) { export default function MurderMysteryGeneralStats({ statsChecked }: { statsChecked: NonNullStats["MurderMystery"] }) {
const stats = statsChecked! const stats = statsChecked!
const kd = formatNumber(stats.kills / stats.deaths) const kd = formatNumber(devide(stats.kills, stats.deaths))
const wl = formatNumber(devide(stats.wins, stats.losses))
const weapon = getKnifeName(stats.active_knife_skin)
return ( return (
<div className="flex mb-10"> <div className="flex mb-10">
<div className="flex-1"> <div className="flex-1">
<BasicStat title="Tokens: " value={formatNumber(stats.coins)} className="font-bold text-mc-dark-green" /> <BasicStat title="Tokens: " value={formatNumber(stats.coins)} className="font-bold text-mc-dark-green" />
<p>
<br />
</p>
<BasicStat title="Kills: " value={formatNumber(stats.kills)} /> <BasicStat title="Kills: " value={formatNumber(stats.kills)} />
<BasicStat title="Kills as Murderer: " value={formatNumber(stats.kills_as_murderer)} /> <BasicStat title="Kills as Murderer: " value={formatNumber(stats.kills_as_murderer)} />
<BasicStat title="Thrown Knife Kills: " value={formatNumber(stats.thrown_knife_kills)} />
<BasicStat title="Deaths: " value={formatNumber(stats.deaths)} /> <BasicStat title="Deaths: " value={formatNumber(stats.deaths)} />
<BasicStat title="Kill/Death Ratio: " value={kd} /> <BasicStat title="Kill/Death Ratio: " value={kd} />
</div> </div>
<div className="flex-1"> <div className="flex-1">
<BasicStat title="Murder Weapon: " value={weapon} className="font-bold text-mc-red" />
<p>
<br />
</p>
<BasicStat title="Wins: " value={formatNumber(stats.wins)} />
<BasicStat title="Losses: " value={formatNumber(stats.losses)} />
<BasicStat title="Win/Loss Ratio: " value={wl} />
<BasicStat title="Fastest Detective Win: " value={formatSecondsToTime(stats.quickest_detective_win_time_seconds)} />
<BasicStat title="Fastest Murderer Win: " value={formatSecondsToTime(stats.quickest_murderer_win_time_seconds)} />
</div> </div>
</div> </div>
) )

View File

@@ -0,0 +1,49 @@
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: "", name: "Overall" }
] as const
export const KNIFESKINS = {
knife_skin_apple: "Healthy Treat",
knife_skin_basted_turkey: "Basted Turkey",
knife_skin_bone: "Big Bone",
knife_skin_blaze_stick: "Blaze Rod",
knife_skin_bloody_brick: "Bloody Brick",
knife_skin_carrot_on_stick: "Carrot on a Stick",
knife_skin_cheapo: "Cheapo Sword",
knife_skin_cheese: "Cheese",
knife_skin_chewed_bush: "Chewed Up Bush",
knife_skin_diamond_shovel: "Only the Best",
knife_skin_easter_basket: "Easter Basket",
knife_skin_feather: "Jagged Knife",
knife_skin_glistening_melon: "Glistening Melon",
knife_skin_gold_digger: "Gold Digger",
knife_skin_grilled_steak: "Grilled Steak",
knife_skin_grimoire: "Grimoire",
knife_skin_ice_shard: "Ice Shard",
knife_skin_mouse_trap: "Mouse Trap",
knife_skin_mvp: "MVP Diamond Sword",
knife_skin_none: "Iron Sword",
knife_skin_prickly: "Prickly",
knife_skin_pumpkin_pie: "Pumpkin Pie",
knife_skin_scythe: "Reaper Scythe",
knife_skin_rudolphs_snack: "Rudolph's Favourite Snack",
knife_skin_rudolphs_nose: "Rudolph's Nose",
knife_skin_salmon: "Salmon",
knife_skin_shears: "Shears",
knife_skin_shovel: "Shovel",
knife_skin_shiny_snack: "Sparkly Snack",
knife_skin_stake: "Stake",
knife_skin_stick: "Stick",
knife_skin_stick_with_hat: "Stick with a Hat",
knife_skin_sweet_treat: "Sweet Treat",
knife_skin_timber: "Timber",
knife_skin_vip: "VIP Gold Sword",
knife_skin_wood_axe: "Wood Axe",
random_cosmetic: "Random",
undefined: "Iron Sword"
} as const

View File

@@ -13,12 +13,31 @@ const dateFormatter = new Intl.DateTimeFormat("en-GB", {
hour12: false hour12: false
}) })
export function formatNumber(num: number): string { export function formatNumber(num: number) {
return numberFormatter.format(num) return numberFormatter.format(num)
} }
export function formatDate(timestamp: number): string { export function formatDate(timestamp: number) {
return dateFormatter.format(new Date(timestamp)) return dateFormatter.format(new Date(timestamp))
} }
export function formatSecondsToTime(seconds: number) {
if (!Number.isFinite(seconds)) return "0s"
seconds = Math.floor(Math.max(0, seconds))
const days = Math.floor(seconds / 86400)
const hours = Math.floor((seconds % 86400) / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const secs = seconds % 60
const parts: string[] = []
if (days) parts.push(days + "d")
if (hours) parts.push(hours + "h")
if (minutes) parts.push(minutes + "m")
if (secs || parts.length === 0) parts.push(secs + "s")
if (parts.length === 1) return parts[0]
if (parts.length === 2) return parts[0] + " and " + parts[1]
// For 3+ parts: separate first n-1 by comma+space, last preceded by 'and'
return parts.slice(0, -1).join(", ") + " and " + parts[parts.length - 1]
}
export function formatRelativeTime(timestamp: number, type: "past" | "future") { export function formatRelativeTime(timestamp: number, type: "past" | "future") {
const now = Date.now() const now = Date.now()

View File

@@ -0,0 +1,11 @@
import { KNIFESKINS } from "@/data/hypixel/murder-mystery"
export function getKnifeName(name?: string) {
if (!name) return KNIFESKINS.undefined
const skin = (KNIFESKINS as Record<string, string>)[name]
if (!skin) return KNIFESKINS.undefined
return skin
}

View File

@@ -333,8 +333,13 @@ export const duelsStatsSchema = z.looseObject({
export const murderMysteryStatsSchema = z.looseObject({ export const murderMysteryStatsSchema = z.looseObject({
kills: z.number().default(0), kills: z.number().default(0),
deaths: z.number().default(0),
wins: z.number().default(0), wins: z.number().default(0),
losses: z.number().default(0),
coins: z.number().default(0), coins: z.number().default(0),
kills_as_murderer: z.number().default(0), kills_as_murderer: z.number().default(0),
deaths: 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)
}) })