Finished first sw stats

This commit is contained in:
2025-08-22 13:51:18 +02:00
parent 4dd571c367
commit 054ec7841b
6 changed files with 153 additions and 3 deletions

View File

@@ -1,5 +1,6 @@
import { getSkyWarsIcon, getTextColor } from "@/lib/hypixel/skywars" import { getPrestigeName, getSkyWarsIcon, getTextColor } from "@/lib/hypixel/skywars"
import { getSkywarsLevel } from "@/lib/hypixel/skyWarsLevel" import { getSkywarsLevel } from "@/lib/hypixel/skyWarsLevel"
import { cn } from "@/lib/utils"
export function SkywarsLevel({ xp, icon }: { xp: number, icon: string | undefined }) { export function SkywarsLevel({ xp, icon }: { xp: number, icon: string | undefined }) {
const level = getSkywarsLevel(xp) const level = getSkywarsLevel(xp)
@@ -49,3 +50,77 @@ export function SkywarsLevel({ xp, icon }: { xp: number, icon: string | undefine
</p> </p>
) )
} }
export function SkywarsProgress({ level, percent }: { level: number, percent: number }) {
return (
<div className="flex items-center mb-10">
<LevelNumber level={level} className="mr-2" />
<Progress level={level} percent={percent} />
<div className="flex-1 h-5 rounded-r-md bg-background"></div>
<LevelNumber level={level + 1} className="ml-2" />
</div>
)
}
function LevelNumber({ level, className }: { level: number, className?: string }) {
if (level > 150 || level === 50) {
return (
<div
className={cn(level > 150 ? "font-bold" : undefined, className)}
style={{
backgroundImage: "linear-gradient(to left,#a0a,#f5f,#5ff,#5f5,#ff5,#fa0,#f55)",
WebkitBackgroundClip: "text",
color: "transparent"
}}
>
{level}
</div>
)
}
return <div className={cn(`text-mc-${getTextColor(level).text}`, className)}>{level}</div>
}
function Progress({ level, percent }: { level: number, percent: number }) {
if (level > 150 || level === 50) {
return (
<div
className="h-5 rounded-l-md"
style={{
width: `${percent}%`,
background: "repeating-linear-gradient(to right,#f55,#fa0,#ff5,#5f5,#5ff,#f5f,#a0a,#f55 16rem)"
}}
>
</div>
)
}
return <div className={`h-5 bg-mc-${getTextColor(level).text} rounded-l-md`} style={{ width: `${percent}%` }}></div>
}
export function SkywarsPrestige({ level, icon }: { level: number, icon?: string }) {
const swIcon = getSkyWarsIcon(icon)
const pres = getPrestigeName(level)
const val = `${pres} ${swIcon}`
if (level > 150 || level === 50) {
return (
<span
style={{
backgroundImage: "linear-gradient(to left,#a0a,#f5f,#5ff,#5f5,#ff5,#fa0,#f55)",
WebkitBackgroundClip: "text",
color: "transparent"
}}
className={level > 150 ? "font-bold" : undefined}
>
{val}
</span>
)
}
return (
<span className={`text-mc-${getTextColor(level).text}`}>
{val}
</span>
)
}

View File

@@ -4,11 +4,14 @@ import { Card, CardContent } from "@/components/ui/card"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible" import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
import { Separator } from "@/components/ui/separator" import { Separator } from "@/components/ui/separator"
import { formatNumber } from "@/lib/formatters" import { formatNumber } from "@/lib/formatters"
import { getProgress } from "@/lib/hypixel/general"
import { getSkywarsLevel, getSkywarsXpForLevel } from "@/lib/hypixel/skyWarsLevel"
import { Player } from "@/lib/schema/player" import { Player } from "@/lib/schema/player"
import { ChevronDown, ChevronUp } from "lucide-react" import { ChevronDown, ChevronUp } from "lucide-react"
import { useEffect, useRef, useState } from "react" import { useEffect, useRef, useState } from "react"
import CollapsedStats from "../../_components/CollapsedStats" import CollapsedStats from "../../_components/CollapsedStats"
import { SkywarsLevel } from "./skywars-components" import { SkywarsLevel, SkywarsProgress } from "./skywars-components"
import SkyWarsGeneralStats from "./stats"
export default function SkyWarsStats({ stats }: { stats: Player["player"]["stats"]["SkyWars"] }) { export default function SkyWarsStats({ stats }: { stats: Player["player"]["stats"]["SkyWars"] }) {
const ref = useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)
@@ -36,9 +39,16 @@ export default function SkyWarsStats({ stats }: { stats: Player["player"]["stats
if (!stats) return null if (!stats) return null
const level = getSkywarsLevel(stats.skywars_experience)
const kd = (stats.kills / stats.deaths).toFixed(2) const kd = (stats.kills / stats.deaths).toFixed(2)
const wl = (stats.wins / stats.losses).toFixed(2) const wl = (stats.wins / stats.losses).toFixed(2)
const percent = getProgress(
getSkywarsXpForLevel(Math.floor(level)),
stats.skywars_experience,
getSkywarsXpForLevel(Math.floor(level) + 1)
)
return ( return (
<Card> <Card>
<CardContent> <CardContent>
@@ -73,6 +83,8 @@ export default function SkyWarsStats({ stats }: { stats: Player["player"]["stats
</div> </div>
<CollapsibleContent> <CollapsibleContent>
<Separator className="my-4" /> <Separator className="my-4" />
<SkywarsProgress level={Math.floor(level)} percent={percent} />
<SkyWarsGeneralStats statsChecked={stats} level={level} />
</CollapsibleContent> </CollapsibleContent>
</Collapsible> </Collapsible>
</CardContent> </CardContent>

View File

@@ -0,0 +1,29 @@
import { formatNumber } from "@/lib/formatters"
import { Player } from "@/lib/schema/player"
import { BasicStat, Stat } from "../../_components/Stats"
import { SkywarsPrestige } from "./skywars-components"
export default function SkyWarsGeneralStats({
statsChecked,
level
}: {
statsChecked: Player["player"]["stats"]["SkyWars"]
level: number
}) {
const stats = statsChecked!
return (
<div className="flex mb-10">
<div className="flex-1">
<BasicStat title="Level: " value={level.toFixed(2)} />
<Stat title="Prestige: ">
<SkywarsPrestige level={Math.floor(level)} icon={stats.selected_prestige_icon} />
</Stat>
<BasicStat title="Coins: " value={formatNumber(stats.coins)} className="text-mc-gold" />
<BasicStat title="Token: " value={formatNumber(stats.cosmetic_tokens)} className="text-mc-dark-green" />
</div>
<div className="flex-1"></div>
<div className="flex-1"></div>
</div>
)
}

View File

@@ -11,3 +11,27 @@ export function getSkywarsLevel(xp: number) {
return 0 return 0
} }
export function getSkywarsXpForLevel(level: number) {
const xps = [0, 20, 70, 150, 250, 500, 1000, 2000, 3500, 6000, 10000, 15000]
if (level >= 12) {
return (level - 12) * 10000 + 15000
}
const baseLevel = Math.floor(level)
const progress = level - baseLevel
if (baseLevel >= xps.length) {
return xps[xps.length - 1]
}
if (baseLevel === 0) {
return progress * xps[1]
}
const baseXp = xps[baseLevel]
const nextXp = xps[baseLevel + 1] || 15000
return baseXp + progress * (nextXp - baseXp)
}

View File

@@ -28,3 +28,11 @@ export function getSkyWarsIcon(icon?: string) {
return icons[icon] ?? ICONS.default return icons[icon] ?? ICONS.default
} }
export function getPrestigeName(level: number) {
const floored = floorLevel(level, 5)
if (level > 150) return PRESTIGES.at(-1)!.name
return PRESTIGES.find(p => p.level === floored)!.name
}

View File

@@ -195,5 +195,7 @@ export const skywarsStatsSchema = z.looseObject({
kills: z.number().default(0), kills: z.number().default(0),
deaths: z.number().default(0), deaths: z.number().default(0),
wins: z.number().default(0), wins: z.number().default(0),
losses: z.number().default(0) losses: z.number().default(0),
cosmetic_tokens: z.number().default(0),
coins: z.number().default(0)
}) })