Added first part of duel stats
This commit is contained in:
72
src/app/(stats)/player/[ign]/_stats/duels/duels.tsx
Normal file
72
src/app/(stats)/player/[ign]/_stats/duels/duels.tsx
Normal file
@@ -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 (
|
||||||
|
<AccordionItem value="duels">
|
||||||
|
<Card className="py-0">
|
||||||
|
<CardContent>
|
||||||
|
<AccordionTrigger className="items-center hover:no-underline hover:cursor-pointer">
|
||||||
|
<h1 className="text-xl font-bold">Duels</h1>
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<CollapsedStats
|
||||||
|
stats={[
|
||||||
|
{
|
||||||
|
title: <p>Division</p>,
|
||||||
|
stat: (
|
||||||
|
<p className="font-bold">
|
||||||
|
{div !== null ?
|
||||||
|
(
|
||||||
|
<span className={`text-mc-${div.color}`}>
|
||||||
|
{`${div.name} ${romanize(div.level)}`}
|
||||||
|
</span>
|
||||||
|
) :
|
||||||
|
<span className="text-muted-foreground">-</span>}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: <p>Wins</p>,
|
||||||
|
stat: (
|
||||||
|
<p className="font-bold">
|
||||||
|
{mostPlayed !== null ?
|
||||||
|
(
|
||||||
|
<span>
|
||||||
|
{mostPlayed.name}
|
||||||
|
</span>
|
||||||
|
) :
|
||||||
|
<span className="text-muted-foreground">-</span>}
|
||||||
|
</p>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <p>Wins</p>,
|
||||||
|
stat: <p className="text-muted-foreground">{formatNumber(stats.wins)}</p>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <p>WL</p>,
|
||||||
|
stat: <p className="text-muted-foreground">{wl}</p>
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
</AccordionContent>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</AccordionItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import { Loader2Icon } from "lucide-react"
|
|||||||
import { Suspense } from "react"
|
import { Suspense } from "react"
|
||||||
import Sidebar from "./_components/Sidebar"
|
import Sidebar from "./_components/Sidebar"
|
||||||
import BedwarsStats from "./_stats/bedwars/bedwars"
|
import BedwarsStats from "./_stats/bedwars/bedwars"
|
||||||
|
import DuelsStats from "./_stats/duels/duels"
|
||||||
import SkyWarsStats from "./_stats/skywars/skywars"
|
import SkyWarsStats from "./_stats/skywars/skywars"
|
||||||
|
|
||||||
export default async function PlayerPage({
|
export default async function PlayerPage({
|
||||||
@@ -91,6 +92,7 @@ async function SuspendedPage({ ign: pign }: { ign: string }) {
|
|||||||
stats={player.stats.SkyWars}
|
stats={player.stats.SkyWars}
|
||||||
achievements_skywars_opal_obsession={player.achievements?.["skywars_opal_obsession"] ?? 0}
|
achievements_skywars_opal_obsession={player.achievements?.["skywars_opal_obsession"] ?? 0}
|
||||||
/>
|
/>
|
||||||
|
<DuelsStats stats={player.stats.Duels} />
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</div>
|
||||||
) :
|
) :
|
||||||
|
|||||||
43
src/data/hypixel/duels.ts
Normal file
43
src/data/hypixel/duels.ts
Normal file
@@ -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
|
||||||
33
src/lib/hypixel/duels/duels.ts
Normal file
33
src/lib/hypixel/duels/duels.ts
Normal file
@@ -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<NonNullStats["Duels"]>) {
|
||||||
|
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<NonNullStats["Duels"]>) {
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -33,13 +33,40 @@ export function concatStatsArray<T extends Record<string, number>>(keys: (keyof
|
|||||||
return base as T
|
return base as T
|
||||||
}
|
}
|
||||||
|
|
||||||
export function romanize(num: number): string {
|
export function romanize(num: number) {
|
||||||
if (!Number.isFinite(num)) return "NaN"
|
if (!Number.isFinite(num)) return "NaN"
|
||||||
const digits = Math.trunc(num).toString().split("")
|
const digits = Math.trunc(num).toString().split("")
|
||||||
const key = [
|
const key = [
|
||||||
"","C","CC","CCC","CD","D","DC","DCC","DCCC","CM",
|
"",
|
||||||
"","X","XX","XXX","XL","L","LX","LXX","LXXX","XC",
|
"C",
|
||||||
"","I","II","III","IV","V","VI","VII","VIII","IX"
|
"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
|
] as const
|
||||||
|
|
||||||
let roman = ""
|
let roman = ""
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { bedwarsStatsSchema, skywarsStatsSchema } from "./stats"
|
import { bedwarsStatsSchema, duelsStatsSchema, skywarsStatsSchema } from "./stats"
|
||||||
|
|
||||||
export const playerSchema = z.looseObject({
|
export const playerSchema = z.looseObject({
|
||||||
player: z.looseObject({
|
player: z.looseObject({
|
||||||
@@ -15,7 +15,8 @@ export const playerSchema = z.looseObject({
|
|||||||
achievements: z.record(z.string(), z.number()).optional(),
|
achievements: z.record(z.string(), z.number()).optional(),
|
||||||
stats: z.looseObject({
|
stats: z.looseObject({
|
||||||
Bedwars: bedwarsStatsSchema.optional(),
|
Bedwars: bedwarsStatsSchema.optional(),
|
||||||
SkyWars: skywarsStatsSchema.optional()
|
SkyWars: skywarsStatsSchema.optional(),
|
||||||
|
Duels: duelsStatsSchema.optional()
|
||||||
}).optional(),
|
}).optional(),
|
||||||
quests: z.record(
|
quests: z.record(
|
||||||
z.string(),
|
z.string(),
|
||||||
|
|||||||
@@ -268,3 +268,73 @@ export const skywarsStatsSchema = z.looseObject({
|
|||||||
}))
|
}))
|
||||||
}).optional()
|
}).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)
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user