diff --git a/src/app/(stats)/player/[ign]/_stats/duels/duels.tsx b/src/app/(stats)/player/[ign]/_stats/duels/duels.tsx new file mode 100644 index 0000000..ac6ab9d --- /dev/null +++ b/src/app/(stats)/player/[ign]/_stats/duels/duels.tsx @@ -0,0 +1,72 @@ +import { AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion" +import { Card, CardContent } from "@/components/ui/card" +import { formatNumber } from "@/lib/formatters" +import { getDivision, getMostPlayed } from "@/lib/hypixel/duels/duels" +import { romanize } from "@/lib/hypixel/general" +import { NonNullStats } from "@/lib/schema/player" +import CollapsedStats from "../../_components/CollapsedStats" + +export default function DuelsStats({ stats }: { stats: NonNullStats["Duels"] }) { + if (!stats) return null + + const wl = formatNumber(stats.wins / stats.losses) + const div = getDivision("all_modes", stats) + const mostPlayed = getMostPlayed(stats) + + return ( + + + + +

Duels

+
+ Division

, + stat: ( +

+ {div !== null ? + ( + + {`${div.name} ${romanize(div.level)}`} + + ) : + -} +

+ ) + }, + + { + title:

Wins

, + stat: ( +

+ {mostPlayed !== null ? + ( + + {mostPlayed.name} + + ) : + -} +

+ ) + }, + { + title:

Wins

, + stat:

{formatNumber(stats.wins)}

+ }, + { + title:

WL

, + stat:

{wl}

+ } + ]} + /> +
+
+ + +
+
+
+ ) +} diff --git a/src/app/(stats)/player/[ign]/page.tsx b/src/app/(stats)/player/[ign]/page.tsx index fb01b55..502f6fa 100644 --- a/src/app/(stats)/player/[ign]/page.tsx +++ b/src/app/(stats)/player/[ign]/page.tsx @@ -9,6 +9,7 @@ import { Loader2Icon } from "lucide-react" import { Suspense } from "react" import Sidebar from "./_components/Sidebar" import BedwarsStats from "./_stats/bedwars/bedwars" +import DuelsStats from "./_stats/duels/duels" import SkyWarsStats from "./_stats/skywars/skywars" export default async function PlayerPage({ @@ -91,6 +92,7 @@ async function SuspendedPage({ ign: pign }: { ign: string }) { stats={player.stats.SkyWars} achievements_skywars_opal_obsession={player.achievements?.["skywars_opal_obsession"] ?? 0} /> + ) : diff --git a/src/data/hypixel/duels.ts b/src/data/hypixel/duels.ts new file mode 100644 index 0000000..0479610 --- /dev/null +++ b/src/data/hypixel/duels.ts @@ -0,0 +1,43 @@ +export const TITLE = "Duels" as const +export const DIVISIONS = [ + { name: "Rookie", id: "rookie", color: "dark-gray", style: "§8" }, + { name: "Iron", id: "iron", color: "white", style: "§f" }, + { name: "Gold", id: "gold", color: "gold", style: "§6" }, + { name: "Diamond", id: "diamond", color: "dark-aqua", style: "§3" }, + { name: "Master", id: "master", color: "dark-green", style: "§2" }, + { name: "Legend", id: "legend", color: "dark-red", style: "§4§l" }, + { name: "Grandmaster", id: "grandmaster", color: "yellow", style: "§e§l" }, + { name: "Godlike", id: "godlike", color: "dark-purple", style: "§5§l" }, + { name: "CELESTIAL", id: "celestial", color: "aqua", style: "§b§l" }, + { name: "DIVINE", id: "divine", color: "light-purpl", style: "§d§l" }, + { name: "ASCENDED", id: "ascended", color: "red", style: "§c§l" } +] as const +export const MODES = [ + { id: "uhc_duel", divisionId: "uhc", name: "UHC 1v1" }, + { id: "uhc_doubles", divisionId: "uhc", name: "UHC 2v2" }, + { id: "uhc_four", divisionId: "uhc", name: "UHC 4v4" }, + { id: "uhc_meetup", divisionId: "uhc", name: "UHC Deathmatch" }, + { id: "op_duel", divisionId: "op", name: "OP 1v1" }, + { id: "op_doubles", divisionId: "op", name: "OP 2v2" }, + { id: "sw_duel", divisionId: "skywars", name: "SkyWars 1v1" }, + { id: "sw_doubles", divisionId: "skywars", name: "SkyWars 2v2" }, + { id: "bow_duel", divisionId: "bow", name: "Bow 1v1" }, + { id: "blitz_duel", divisionId: "blitz", name: "Blitz 1v1" }, + { id: "mw_duel", divisionId: "mega_walls", name: "MegaWalls" }, + { id: "mw_doubles", divisionId: "mega_walls", name: "MegaWalls Doubles" }, + { id: "sumo_duel", divisionId: "sumo", name: "Sumo 1v1" }, + { id: "bowspleef_duel", divisionId: "tnt_games", name: "Bow Spleef 1v1" }, + { id: "parkour_eight", divisionId: "parkour", name: "Parkour FFA" }, + { id: "boxing_duel", divisionId: "boxing", name: "Boxing 1v1" }, + { id: "classic_duel", divisionId: "classic", name: "Classic 1v1" }, + { id: "potion_duel", divisionId: "no_debuff", name: "NoDebuff 1v1" }, + { id: "combo_duel", divisionId: "combo", name: "Combo 1v1" }, + { id: "bridge_duel", divisionId: "bridge", name: "Bridge 1v1" }, + { id: "bridge_doubles", divisionId: "bridge", name: "Bridge 2v2" }, + { id: "bridge_threes", divisionId: "bridge", name: "Bridge 3v3" }, + { id: "bridge_four", divisionId: "bridge", name: "Bridge 4v4" }, + { id: "bridge_2v2v2v2", divisionId: "bridge", name: "Bridge 2v2v2v2" }, + { id: "bridge_3v3v3v3", divisionId: "bridge", name: "Bridge 3v3v3v3" }, + { id: "capture_threes", divisionId: "bridge", name: "Bridge CTF 3v3" }, + { id: "duel_arena", divisionId: "", name: "Duel Arena" } +] as const diff --git a/src/lib/hypixel/duels/duels.ts b/src/lib/hypixel/duels/duels.ts new file mode 100644 index 0000000..c15f9de --- /dev/null +++ b/src/lib/hypixel/duels/duels.ts @@ -0,0 +1,33 @@ +import { DIVISIONS, MODES } from "@/data/hypixel/duels" +import { NonNullStats } from "@/lib/schema/player" + +type DuelType = "all_modes" + +export function getDivision(duelType: DuelType, stats: NonNullable) { + for (const div of DIVISIONS.slice().reverse()) { + const index = `${duelType}_${div.id}_title_prestige` as const + const val = stats[index] + if (val > 0) { + return { + name: div.name, + level: val, + color: div.color + } + } + } + + return null +} + +export function getMostPlayed(stats: NonNullable) { + let mostPlayed: typeof MODES[number] | null = null + let mostPlays = 0 + for (const mode of MODES) { + const plays = stats[`${mode.id}_wins`] + stats[`${mode.id}_losses`] + if (plays > mostPlays && mode.id) { + mostPlays = plays + mostPlayed = mode + } + } + return mostPlayed +} diff --git a/src/lib/hypixel/general.ts b/src/lib/hypixel/general.ts index b4d0baf..2abb697 100644 --- a/src/lib/hypixel/general.ts +++ b/src/lib/hypixel/general.ts @@ -33,13 +33,40 @@ export function concatStatsArray>(keys: (keyof return base as T } -export function romanize(num: number): string { +export function romanize(num: number) { if (!Number.isFinite(num)) return "NaN" const digits = Math.trunc(num).toString().split("") const key = [ - "","C","CC","CCC","CD","D","DC","DCC","DCCC","CM", - "","X","XX","XXX","XL","L","LX","LXX","LXXX","XC", - "","I","II","III","IV","V","VI","VII","VIII","IX" + "", + "C", + "CC", + "CCC", + "CD", + "D", + "DC", + "DCC", + "DCCC", + "CM", + "", + "X", + "XX", + "XXX", + "XL", + "L", + "LX", + "LXX", + "LXXX", + "XC", + "", + "I", + "II", + "III", + "IV", + "V", + "VI", + "VII", + "VIII", + "IX" ] as const let roman = "" diff --git a/src/lib/schema/player.ts b/src/lib/schema/player.ts index 8a6b532..df468b8 100644 --- a/src/lib/schema/player.ts +++ b/src/lib/schema/player.ts @@ -1,5 +1,5 @@ import z from "zod" -import { bedwarsStatsSchema, skywarsStatsSchema } from "./stats" +import { bedwarsStatsSchema, duelsStatsSchema, skywarsStatsSchema } from "./stats" export const playerSchema = z.looseObject({ player: z.looseObject({ @@ -15,7 +15,8 @@ export const playerSchema = z.looseObject({ achievements: z.record(z.string(), z.number()).optional(), stats: z.looseObject({ Bedwars: bedwarsStatsSchema.optional(), - SkyWars: skywarsStatsSchema.optional() + SkyWars: skywarsStatsSchema.optional(), + Duels: duelsStatsSchema.optional() }).optional(), quests: z.record( z.string(), diff --git a/src/lib/schema/stats.ts b/src/lib/schema/stats.ts index e24c6e5..9100322 100644 --- a/src/lib/schema/stats.ts +++ b/src/lib/schema/stats.ts @@ -268,3 +268,73 @@ export const skywarsStatsSchema = z.looseObject({ })) }).optional() }) + +export const duelsStatsSchema = z.looseObject({ + wins: z.number().default(0), + losses: z.number().default(0), + all_modes_rookie_title_prestige: z.number().default(-1), + all_modes_iron_title_prestige: z.number().default(-1), + all_modes_gold_title_prestige: z.number().default(-1), + all_modes_diamond_title_prestige: z.number().default(-1), + all_modes_master_title_prestige: z.number().default(-1), + all_modes_legend_title_prestige: z.number().default(-1), + all_modes_grandmaster_title_prestige: z.number().default(-1), + all_modes_godlike_title_prestige: z.number().default(-1), + all_modes_celestial_title_prestige: z.number().default(-1), + all_modes_divine_title_prestige: z.number().default(-1), + all_modes_ascended_title_prestige: z.number().default(-1), + uhc_duel_wins: z.number().default(0), + uhc_doubles_wins: z.number().default(0), + uhc_four_wins: z.number().default(0), + uhc_meetup_wins: z.number().default(0), + op_duel_wins: z.number().default(0), + op_doubles_wins: z.number().default(0), + sw_duel_wins: z.number().default(0), + sw_doubles_wins: z.number().default(0), + bow_duel_wins: z.number().default(0), + blitz_duel_wins: z.number().default(0), + mw_duel_wins: z.number().default(0), + mw_doubles_wins: z.number().default(0), + sumo_duel_wins: z.number().default(0), + bowspleef_duel_wins: z.number().default(0), + parkour_eight_wins: z.number().default(0), + boxing_duel_wins: z.number().default(0), + classic_duel_wins: z.number().default(0), + potion_duel_wins: z.number().default(0), + combo_duel_wins: z.number().default(0), + bridge_duel_wins: z.number().default(0), + bridge_doubles_wins: z.number().default(0), + bridge_threes_wins: z.number().default(0), + bridge_four_wins: z.number().default(0), + bridge_2v2v2v2_wins: z.number().default(0), + bridge_3v3v3v3_wins: z.number().default(0), + capture_threes_wins: z.number().default(0), + duel_arena_wins: z.number().default(0), + uhc_duel_losses: z.number().default(0), + uhc_doubles_losses: z.number().default(0), + uhc_four_losses: z.number().default(0), + uhc_meetup_losses: z.number().default(0), + op_duel_losses: z.number().default(0), + op_doubles_losses: z.number().default(0), + sw_duel_losses: z.number().default(0), + sw_doubles_losses: z.number().default(0), + bow_duel_losses: z.number().default(0), + blitz_duel_losses: z.number().default(0), + mw_duel_losses: z.number().default(0), + mw_doubles_losses: z.number().default(0), + sumo_duel_losses: z.number().default(0), + bowspleef_duel_losses: z.number().default(0), + parkour_eight_losses: z.number().default(0), + boxing_duel_losses: z.number().default(0), + classic_duel_losses: z.number().default(0), + potion_duel_losses: z.number().default(0), + combo_duel_losses: z.number().default(0), + bridge_duel_losses: z.number().default(0), + bridge_doubles_losses: z.number().default(0), + bridge_threes_losses: z.number().default(0), + bridge_four_losses: z.number().default(0), + bridge_2v2v2v2_losses: z.number().default(0), + bridge_3v3v3v3_losses: z.number().default(0), + capture_threes_losses: z.number().default(0), + duel_arena_losses: z.number().default(0) +})