diff --git a/package.json b/package.json
index 049ef6f..4ece22c 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 84bcf97..9e6948d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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': {}
diff --git a/src/app/(stats)/player/[ign]/_components/Sidebar.tsx b/src/app/(stats)/player/[ign]/_components/Sidebar.tsx
index 3cbeb66..ea5f079 100644
--- a/src/app/(stats)/player/[ign]/_components/Sidebar.tsx
+++ b/src/app/(stats)/player/[ign]/_components/Sidebar.tsx
@@ -38,7 +38,9 @@ export default function Sidebar({ level, ign, player, guild }: SidebarProps) {
{"Total coins: "}
- {formatNumber(getTotalCoins(player.stats))}
+
+ {formatNumber(getTotalCoins(player.stats as Record>))}
+
diff --git a/src/app/(stats)/player/[ign]/_components/stats/bedewars.tsx b/src/app/(stats)/player/[ign]/_components/stats/bedewars.tsx
new file mode 100644
index 0000000..35fa8b6
--- /dev/null
+++ b/src/app/(stats)/player/[ign]/_components/stats/bedewars.tsx
@@ -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(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 (
+
+
+
+
+
Bedwars
+
+
+
+ {opened === false ? : }
+
+
+
+
+
+ {stats.Experience}
+
+
+
+
+ )
+}
+
+function BedwarsLevel({ xp }: { xp: number }) {
+ const level = getBWLevelForExp(xp)
+ const color = bedwarsLevelColors(level)
+
+ if (typeof color === "string") {
+ return (
+
+ {`[${level}⭐]`}
+
+ )
+ }
+
+ const levelArray = `[${level}⭐]`.split("")
+
+ return (
+
+ {levelArray.map((v, i) => {
+ return {v}
+ })}
+
+ )
+}
diff --git a/src/app/(stats)/player/[ign]/page.tsx b/src/app/(stats)/player/[ign]/page.tsx
index 34dee3c..b925fc2 100644
--- a/src/app/(stats)/player/[ign]/page.tsx
+++ b/src/app/(stats)/player/[ign]/page.tsx
@@ -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({
-
-
Game Statistics
-
Game stats will be displayed here...
+
+
diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx
new file mode 100644
index 0000000..ae9fad0
--- /dev/null
+++ b/src/components/ui/collapsible.tsx
@@ -0,0 +1,33 @@
+"use client"
+
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
+
+function Collapsible({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function CollapsibleTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CollapsibleContent({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/src/data/colors.ts b/src/data/colors.ts
index 37f27b7..67b4502 100644
--- a/src/data/colors.ts
+++ b/src/data/colors.ts
@@ -33,4 +33,43 @@ export function getColor(color?: string, type: "text" | "bg" = "text", defaultCo
default:
return type === "text" ? `text-mc-${defaultColor}` : `bg-mc-${defaultColor}`
}
-}
\ No newline at end of file
+}
+
+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"
+ }
+}
diff --git a/src/data/hypixel/bedwars.ts b/src/data/hypixel/bedwars.ts
new file mode 100644
index 0000000..95083b1
--- /dev/null
+++ b/src/data/hypixel/bedwars.ts
@@ -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" }
+]
diff --git a/src/lib/hypixel/bedwarsLevel.ts b/src/lib/hypixel/bedwarsLevel.ts
new file mode 100644
index 0000000..a4464e7
--- /dev/null
+++ b/src/lib/hypixel/bedwarsLevel.ts
@@ -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)
+}
diff --git a/src/lib/hypixel/stats.ts b/src/lib/hypixel/stats.ts
index f22fa0c..3f56577 100644
--- a/src/lib/hypixel/stats.ts
+++ b/src/lib/hypixel/stats.ts
@@ -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>) {
+ 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) {
@@ -38,4 +38,5 @@ export function rewardClaimed(claimedAt?: number) {
} else {
return false
}
-}
\ No newline at end of file
+}
+
diff --git a/src/lib/hypixelFormatters.ts b/src/lib/hypixelFormatters.ts
new file mode 100644
index 0000000..73fd19d
--- /dev/null
+++ b/src/lib/hypixelFormatters.ts
@@ -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)
+ })
+}
diff --git a/src/lib/schema/player.ts b/src/lib/schema/player.ts
index 8e9fa48..a34d82c 100644
--- a/src/lib/schema/player.ts
+++ b/src/lib/schema/player.ts
@@ -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
-