Added first stat on bedwars

This commit is contained in:
2025-08-17 19:42:58 +02:00
parent c79d06f272
commit e3a4a65e2d
12 changed files with 531 additions and 27 deletions

View File

@@ -10,6 +10,7 @@
"fmt": "dprint fmt src/**/*.ts src/**/*.tsx"
},
"dependencies": {
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@t3-oss/env-nextjs": "^0.13.8",

139
pnpm-lock.yaml generated
View File

@@ -8,6 +8,9 @@ importers:
.:
dependencies:
'@radix-ui/react-collapsible':
specifier: ^1.1.12
version: 1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-separator':
specifier: ^1.1.7
version: 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -428,6 +431,22 @@ packages:
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
engines: {node: '>=12.4.0'}
'@radix-ui/primitive@1.1.3':
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
'@radix-ui/react-collapsible@1.1.12':
resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-compose-refs@1.1.2':
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
peerDependencies:
@@ -437,6 +456,37 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-context@1.1.2':
resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-id@1.1.1':
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-presence@1.1.5':
resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-primitive@2.1.3':
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
peerDependencies:
@@ -472,6 +522,33 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-use-controllable-state@1.2.2':
resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-effect-event@0.0.2':
resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-layout-effect@1.1.1':
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
@@ -2278,12 +2355,53 @@ snapshots:
'@nolyfill/is-core-module@1.0.39': {}
'@radix-ui/primitive@1.1.3': {}
'@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/primitive': 1.1.3
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
'@radix-ui/react-context': 1.1.2(@types/react@19.1.8)(react@19.1.0)
'@radix-ui/react-id': 1.1.1(@types/react@19.1.8)(react@19.1.0)
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.8)(react@19.1.0)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.8
'@types/react-dom': 19.1.6(@types/react@19.1.8)
'@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@19.1.0)':
dependencies:
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.8
'@radix-ui/react-context@1.1.2(@types/react@19.1.8)(react@19.1.0)':
dependencies:
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.8
'@radix-ui/react-id@1.1.1(@types/react@19.1.8)(react@19.1.0)':
dependencies:
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.8
'@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.8
'@types/react-dom': 19.1.6(@types/react@19.1.8)
'@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0)
@@ -2309,6 +2427,27 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.8
'@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.8)(react@19.1.0)':
dependencies:
'@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.8)(react@19.1.0)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.8
'@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.8)(react@19.1.0)':
dependencies:
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.8)(react@19.1.0)
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.8
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.8)(react@19.1.0)':
dependencies:
react: 19.1.0
optionalDependencies:
'@types/react': 19.1.8
'@rtsao/scc@1.1.0': {}
'@rushstack/eslint-patch@1.12.0': {}

View File

@@ -38,7 +38,9 @@ export default function Sidebar({ level, ign, player, guild }: SidebarProps) {
</p>
<p>
<span className="font-bold">{"Total coins: "}</span>
<span className="text-mc-gold">{formatNumber(getTotalCoins(player.stats))}</span>
<span className="text-mc-gold">
{formatNumber(getTotalCoins(player.stats as Record<string, Record<"coins", number | undefined>>))}
</span>
</p>
</div>
<Separator className="my-4" />

View File

@@ -0,0 +1,87 @@
"use client"
import { Card, CardContent } from "@/components/ui/card"
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
import { getBWLevelForExp } from "@/lib/hypixel/bedwarsLevel"
import { bedwarsLevelColors } from "@/lib/hypixelFormatters"
import { Player } from "@/lib/schema/player"
import { cn } from "@/lib/utils"
import { ChevronDown, ChevronUp, Menu } from "lucide-react"
import { useEffect, useRef, useState } from "react"
export default function BedwarsStats({ stats }: { stats: Player["player"]["stats"]["Bedwars"] }) {
const ref = useRef<HTMLDivElement>(null)
const [opened, setOpened] = useState(false)
useEffect(() => {
if (!ref.current) return
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === "attributes" && mutation.attributeName === "data-state") {
const dataState = ref.current?.getAttribute("data-state")
setOpened(dataState === "open")
}
})
})
observer.observe(ref.current, {
attributes: true,
attributeFilter: ["data-state"]
})
return () => observer.disconnect()
}, [])
if (!stats) return null
return (
<Card>
<CardContent>
<Collapsible ref={ref}>
<div className="flex justify-between">
<h1 className="text-xl font-bold">Bedwars</h1>
<div className="flex gap-4">
<div className="text-center">
<p>Level</p>
<BedwarsLevel xp={stats.Experience} />
</div>
</div>
<div className="flex gap-2 items-center">
<CollapsibleTrigger className="transition-all">
{opened === false ? <ChevronDown /> : <ChevronUp />}
</CollapsibleTrigger>
<Menu />
</div>
</div>
<CollapsibleContent>
<h1>{stats.Experience}</h1>
</CollapsibleContent>
</Collapsible>
</CardContent>
</Card>
)
}
function BedwarsLevel({ xp }: { xp: number }) {
const level = getBWLevelForExp(xp)
const color = bedwarsLevelColors(level)
if (typeof color === "string") {
return (
<p className={cn(color, "font-bold")}>
{`[${level}⭐]`}
</p>
)
}
const levelArray = `[${level}⭐]`.split("")
return (
<p className="font-bold">
{levelArray.map((v, i) => {
return <span key={i} className={color[i]}>{v}</span>
})}
</p>
)
}

View File

@@ -4,6 +4,7 @@ import { getUuid } from "@/lib/hypixel/api/mojang"
import { getPlayer } from "@/lib/hypixel/api/player"
import { getExactLevel } from "@/lib/hypixel/level"
import Sidebar from "./_components/Sidebar"
import BedwarsStats from "./_components/stats/bedewars"
export default async function PlayerPage({
params
@@ -53,9 +54,8 @@ export default async function PlayerPage({
</h1>
<div className="flex gap-6 px-6 mt-8 w-full max-w-7xl">
<Sidebar level={level} ign={pign} player={player} guild={guild ?? undefined} />
<div className="p-6 w-3/4 rounded-xl border shadow-sm bg-card">
<h2 className="mb-4 text-xl font-semibold">Game Statistics</h2>
<p>Game stats will be displayed here...</p>
<div className="w-3/4">
<BedwarsStats stats={player.stats.Bedwars} />
</div>
</div>
</div>

View File

@@ -0,0 +1,33 @@
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
function Collapsible({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
}
function CollapsibleTrigger({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
return (
<CollapsiblePrimitive.CollapsibleTrigger
data-slot="collapsible-trigger"
{...props}
/>
)
}
function CollapsibleContent({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
return (
<CollapsiblePrimitive.CollapsibleContent
data-slot="collapsible-content"
{...props}
/>
)
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent }

View File

@@ -34,3 +34,42 @@ export function getColor(color?: string, type: "text" | "bg" = "text", defaultCo
return type === "text" ? `text-mc-${defaultColor}` : `bg-mc-${defaultColor}`
}
}
export function getColorFromCode(c?: string) {
switch (c) {
case "0":
return "text-mc-black"
case "1":
return "text-mc-dark-blue"
case "2":
return "text-mc-dark-green"
case "3":
return "text-mc-dark-aqua"
case "4":
return "text-mc-dark-red"
case "5":
return "text-mc-dark-purple"
case "6":
return "text-mc-gold"
case "7":
return "text-mc-gray"
case "8":
return "text-mc-dark-gray"
case "9":
return "text-mc-blue"
case "a":
return "text-mc-green"
case "b":
return "text-mc-aqua"
case "c":
return "text-mc-red"
case "d":
return "text-mc-light-purple"
case "e":
return "text-mc-yellow"
case "f":
return "text-mc-white"
default:
return "text-mc-gray"
}
}

126
src/data/hypixel/bedwars.ts Normal file
View File

@@ -0,0 +1,126 @@
export const TITLE = "Bed Wars"
export const PRESTIGES = [
{ level: 0, colormap: "7", color: "gray", name: "None" },
{ level: 100, colormap: "f", color: "white", name: "Iron" },
{ level: 200, colormap: "6", color: "gold", name: "Gold" },
{ level: 300, colormap: "b", color: "aqua", name: "Diamond" },
{ level: 400, colormap: "2", color: "darkgreen", name: "Emerald" },
{ level: 500, colormap: "3", color: "darkaqua", name: "Sapphire" },
{ level: 600, colormap: "4", color: "darkred", name: "Ruby" },
{ level: 700, colormap: "d", color: "pink", name: "Crystal" },
{ level: 800, colormap: "9", color: "blue", name: "Opal" },
{ level: 900, colormap: "5", color: "purple", name: "Amethyst" },
{ level: 1000, colormap: "c6eabd5", color: "rainbow", name: "Rainbow" },
{ level: 1100, colormap: "7ffff77", color: "white", name: "Iron Prime" },
{ level: 1200, colormap: "7eeee67", color: "yellow", name: "Gold Prime" },
{ level: 1300, colormap: "7bbbb37", color: "aqua", name: "Diamond Prime" },
{ level: 1400, colormap: "7aaaa27", color: "green", name: "Emerald Prime" },
{ level: 1500, colormap: "7333397", color: "darkaqua", name: "Sapphire Prime" },
{ level: 1600, colormap: "7cccc47", color: "red", name: "Ruby Prime" },
{ level: 1700, colormap: "7dddd57", color: "pink", name: "Crystal Prime" },
{ level: 1800, colormap: "7999917", color: "blue", name: "Opal Prime" },
{ level: 1900, colormap: "7555587", color: "purple", name: "Amethyst Prime" },
{ level: 2000, colormap: "87ff778", color: "white", name: "Mirror" },
{ level: 2100, colormap: "ffee666", color: "yellow", name: "Light" },
{ level: 2200, colormap: "66ffb33", color: "aqua", name: "Dawn" },
{ level: 2300, colormap: "55dd6ee", color: "purple", name: "Dusk" },
{ level: 2400, colormap: "bbff778", color: "white", name: "Air" },
{ level: 2500, colormap: "ffaa222", color: "green", name: "Wind" },
{ level: 2600, colormap: "44ccdd5", color: "darkred", name: "Nebula" },
{ level: 2700, colormap: "eeff777", color: "yellow", name: "Thunder" },
{ level: 2800, colormap: "aa2266e", color: "darkgreen", name: "Earth" },
{ level: 2900, colormap: "bb33991", color: "blue", name: "Water" },
{ level: 3000, colormap: "ee66cc4", color: "red", name: "Fire" },
{ level: 3100, colormap: "993366e", color: "blue", name: "Sunshine" },
{ level: 3200, colormap: "c4774cc", color: "darkred", name: "Eclipse" },
{ level: 3300, colormap: "999dcc4", color: "blue", name: "Gamma" },
{ level: 3400, colormap: "2add552", color: "green", name: "Majestic" },
{ level: 3500, colormap: "cc442aa", color: "red", name: "Andesine" },
{ level: 3600, colormap: "aaab991", color: "green", name: "Marine" },
{ level: 3700, colormap: "44ccb33", color: "darkred", name: "Element" },
{ level: 3800, colormap: "11955d1", color: "darkblue", name: "Galaxy" },
{ level: 3900, colormap: "ccaa399", color: "red", name: "Atomic" },
{ level: 4000, colormap: "55cc66e", color: "purple", name: "Sunset" },
{ level: 4100, colormap: "ee6cdd5", color: "yellow", name: "Time" },
{ level: 4200, colormap: "193bf77", color: "blue", name: "Winter" },
{ level: 4300, colormap: "0588550", color: "purple", name: "Obsidian" },
{ level: 4400, colormap: "22ae65d", color: "darkgreen", name: "Spring" },
{ level: 4500, colormap: "ffbb333", color: "white", name: "Ice" },
{ level: 4600, colormap: "3bee6d5", color: "aqua", name: "Summer" },
{ level: 4700, colormap: "f4cc919", color: "darkred", name: "Spinel" },
{ level: 4800, colormap: "55c6eb3", color: "purple", name: "Autumn" },
{ level: 4900, colormap: "2affaa2", color: "green", name: "Mystic" },
{ level: 5000, colormap: "4459910", color: "darkred", name: "Eternal" }
]
export const PRESTIGE_ICONS = [
{ level: 0, symbol: "✫" },
{ level: 1100, symbol: "✪" },
{ level: 2100, symbol: "⚝" },
{ level: 3100, symbol: "✥" }
]
export const MODES = [
{ id: "eight_one_", name: "Solo" },
{ id: "eight_two_", name: "Doubles" },
{ id: "four_three_", name: "3v3v3v3" },
{ id: "four_four_", name: "4v4v4v4" },
{ id: "two_four_", name: "4v4" },
{ id: "eight_one_rush_", name: "Rush Solo" },
{ id: "eight_two_rush_", name: "Rush Doubles" },
{ id: "four_four_rush_", name: "Rush 4v4v4v4" },
{ id: "eight_one_ultimate_", name: "Ultimate Solo" },
{ id: "eight_two_ultimate_", name: "Ultimate Doubles" },
{ id: "four_four_ultimate_", name: "Ultimate 4v4v4v4" },
{ id: "eight_two_lucky_", name: "Lucky Doubles" },
{ id: "four_four_lucky_", name: "Lucky 4v4v4v4" },
{ id: "eight_two_voidless_", name: "Voidless Doubles" },
{ id: "four_four_voidless_", name: "Voidless 4v4v4v4" },
{ id: "eight_two_armed_", name: "Armed Doubles" },
{ id: "four_four_armed_", name: "Armed 4v4v4v4" },
{ id: "eight_two_swap_", name: "Swappage Doubles" },
{ id: "four_four_swap_", name: "Swappage 4v4v4v4" },
{ id: "eight_two_underworld_", name: "Underworld Doubles" },
{ id: "four_four_underworld_", name: "Underworld 4v4v4v4" },
{ id: "castle_", name: "Castle" },
{ id: "", name: "Overall" }
]
export const PRACTICEMODES = [
{ id: "bridging", name: "Bridging" },
{ id: "mlg", name: "MLG" },
{ id: "fireball_jumping", name: "Fireball/TNT Jumping" },
{ id: "pearl_clutching", name: "Pearl Clutching" }
]
export const PRACTICEBRIDGING = {
bridging_distance: [30, 50, 100],
elevation: [
{ id: "NONE", name: "Flat" },
{ id: "SLIGHT", name: "Inclined" },
{ id: "STAIRCASE", name: "Stairs" }
],
angle: [
{ id: "STRAIGHT", name: "Straight" },
{ id: "DIAGONAL", name: "Diagonal" }
]
}
export const SLUMBER_WALLETS = {
"MINI_WALLET": 25,
"LIGHT_SLUMBERS_WALLET": 99,
"LIGHT_IMPERIAL_WALLET": 500,
"EXPLORERS_WALLET": 5000,
"HOTEL_STAFF_WALLET": 10000,
"PLATINUM_MEMBERSHIP_WALLET": 100000
}
export const SLUMBER_ROOMS = [
{ id: "room_1", name: "Throne Door" },
{ id: "room_2", name: "Hotel Door" },
{ id: "room_3", name: "Desert Door" },
{ id: "room_4", name: "Electronic Door" },
{ id: "room_5", name: "Door from the Sky" },
{ id: "room_6", name: "Door as seen on TV" },
{ id: "room_7", name: "Skyscraper Door" },
{ id: "room_8", name: "Arcade Door" },
{ id: "room_9", name: "Intricate Door" },
{ id: "room_10", name: "Space Door" },
{ id: "room_11", name: "D\xa0\xa0\xa0o\xa0\xa00\xa0\xa0\xa0\xa0\xa0\xa0r" },
{ id: "room_12", name: "Garage Door" },
{ id: "owners_office", name: "Owner's Office" }
]

View File

@@ -0,0 +1,55 @@
/*
* Functions for BedWars exp and level conversions.
*/
const EASY_LEVELS = 4
const EASY_LEVELS_XP = 7000
const XP_PER_PRESTIGE = 96 * 5000 + EASY_LEVELS_XP
const LEVELS_PER_PRESTIGE = 100
const HIGHEST_PRESTIGE = 10
export function getBWLevel(level: number) {
if (level > HIGHEST_PRESTIGE * LEVELS_PER_PRESTIGE) {
return level - HIGHEST_PRESTIGE * LEVELS_PER_PRESTIGE
}
return level % LEVELS_PER_PRESTIGE
}
export function getBWExpForLevel(level: number) {
if (level === 0) return 0
const respectedLevel = getBWLevel(level)
if (respectedLevel > EASY_LEVELS) {
return 5000
}
switch (respectedLevel) {
case 1:
return 500
case 2:
return 1000
case 3:
return 2000
case 4:
return 3500
default:
return 5000
}
}
export function getBWLevelForExp(exp: number) {
const prestiges = Math.floor(exp / XP_PER_PRESTIGE)
let level = prestiges * LEVELS_PER_PRESTIGE
let expWithoutPrestiges = exp - (prestiges * XP_PER_PRESTIGE)
let expForEasyLevel
for (let i = 1; i <= EASY_LEVELS; i += 1) {
expForEasyLevel = getBWExpForLevel(i)
if (expWithoutPrestiges < expForEasyLevel) {
break
}
level += 1
expWithoutPrestiges -= expForEasyLevel
}
return level + Math.floor(expWithoutPrestiges / 5000)
}

View File

@@ -3,28 +3,28 @@ import { Player } from "@/lib/schema/player"
export function getCoinMultiplier(level: number) {
if (level > MULTIPLIER[MULTIPLIER.length - 1].level) {
return MULTIPLIER[MULTIPLIER.length - 1].value;
return MULTIPLIER[MULTIPLIER.length - 1].value
}
for (let i = MULTIPLIER.length - 1; i >= 0; i--) {
if (level >= MULTIPLIER[i].level) {
return MULTIPLIER[i].value;
return MULTIPLIER[i].value
}
}
return MULTIPLIER[0].value
}
export function getTotalCoins(stats: Player["player"]["stats"]) {
return Object.values(stats).reduce((total, stat) => total + (stat.coins || 0), 0);
export function getTotalCoins(stats: Record<string, Record<"coins", number | undefined>>) {
return Object.values(stats).reduce((total, stat) => total + (stat.coins || 0), 0)
}
export function getTotalQuests(quests: Player["player"]["quests"]) {
return Object.values(quests).reduce((total, quest) => total + (quest.completions?.length || 0), 0);
return Object.values(quests).reduce((total, quest) => total + (quest.completions?.length || 0), 0)
}
export function getTotalChallenges(challenges: Player["player"]["challenges"]["all_time"]) {
return Object.values(challenges).reduce((total, challenge) => total + challenge, 0);
return Object.values(challenges).reduce((total, challenge) => total + challenge, 0)
}
export function rewardClaimed(claimedAt?: number) {
@@ -39,3 +39,4 @@ export function rewardClaimed(claimedAt?: number) {
return false
}
}

View File

@@ -0,0 +1,23 @@
import { getColorFromCode } from "@/data/colors"
import { PRESTIGES } from "@/data/hypixel/bedwars"
import { floorLevel } from "./hypixel/formatters"
export function bedwarsLevelColors(level: number) {
if (level < 0) return getColorFromCode()
const floored = floorLevel(level, 100)
if (floored < 1000) {
return getColorFromCode(PRESTIGES.find(v => v.level === floored)!.colormap)
}
if (floored > 5000) {
return PRESTIGES[PRESTIGES.length - 1].colormap.split("").map(v => {
return getColorFromCode(v)
})
}
return PRESTIGES.find(v => v.level === floored)!.colormap.split("").map(v => {
return getColorFromCode(v)
})
}

View File

@@ -1,7 +1,7 @@
import z from "zod"
export const playerSchema = z.object({
player: z.object({
export const playerSchema = z.looseObject({
player: z.looseObject({
displayname: z.string(),
uuid: z.string(),
newPackageRank: z.literal("VIP").or(z.literal("VIP_PLUS").or(z.literal("MVP")).or(z.literal("MVP_PLUS"))).optional(),
@@ -11,37 +11,36 @@ export const playerSchema = z.object({
networkExp: z.number(),
karma: z.number(),
achievementPoints: z.number().optional(),
stats: z.record(
z.string(),
z.object({
coins: z.number().optional()
})
),
stats: z.looseObject({
Bedwars: z.looseObject({
Experience: z.number()
}).optional()
}),
quests: z.record(
z.string(),
z.object({
z.looseObject({
completions: z.array(
z.object({
z.looseObject({
time: z.number()
}).optional()
).optional()
})
),
challenges: z.object({
challenges: z.looseObject({
all_time: z.record(z.string(), z.number())
}),
lastClaimedReward: z.number().optional(),
rewardHighScore: z.number().optional(),
rewardStreak: z.number().optional(),
totalRewards: z.number().optional(),
giftingMeta: z.object({
giftingMeta: z.looseObject({
giftsGiven: z.number().optional(),
ranksGiven: z.number().optional()
}).optional(),
firstLogin: z.number().optional(),
lastLogin: z.number().optional(),
socialMedia: z.object({
links: z.object({
socialMedia: z.looseObject({
links: z.looseObject({
DISCORD: z.string().optional(),
TWITCH: z.string().optional(),
HYPIXEL: z.string().optional(),
@@ -53,4 +52,3 @@ export const playerSchema = z.object({
})
export type Player = z.infer<typeof playerSchema>