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

+
+
+

Level

+ +
+
+
+ + {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 -