From 0163e09b02967a152b59be4accb371c110dfab89 Mon Sep 17 00:00:00 2001 From: Lncvrt Date: Sat, 14 Feb 2026 14:56:47 -0700 Subject: [PATCH] Add berry dash leaderboards --- src/app/game/berrydash/leaderboards/page.tsx | 307 ++++++++++++++++++ .../game/berrydash/leaderboards/styles.css | 14 + src/app/game/page.tsx | 13 +- src/componets/Sidebar.tsx | 18 +- src/lib/BerryDash.ts | 7 + src/types/BerryDash/BirdColor.ts | 1 + 6 files changed, 352 insertions(+), 8 deletions(-) create mode 100644 src/app/game/berrydash/leaderboards/page.tsx create mode 100644 src/app/game/berrydash/leaderboards/styles.css create mode 100644 src/lib/BerryDash.ts create mode 100644 src/types/BerryDash/BirdColor.ts diff --git a/src/app/game/berrydash/leaderboards/page.tsx b/src/app/game/berrydash/leaderboards/page.tsx new file mode 100644 index 0000000..1535a97 --- /dev/null +++ b/src/app/game/berrydash/leaderboards/page.tsx @@ -0,0 +1,307 @@ +'use client' + +import { useCallback, useEffect, useState } from 'react' +import { BirdColor } from '@/types/BerryDash/BirdColor' +import axios from 'axios' +import { GetIconForUser } from '@/lib/BerryDash' +import Image from 'next/image' +import './styles.css' +import { useRouter } from 'next/navigation' + +interface BaseEntry { + id: number + username: string + value: number + icon: number + overlay: number + birdColor: BirdColor + overlayColor: BirdColor + customIcon: string | null +} + +interface Stats { + highScore: string + totalNormalBerries: string + totalPoisonBerries: string + totalSlowBerries: string + totalUltraBerries: string + totalSpeedyBerries: string + totalCoinBerries: string + totalRandomBerries: string + totalAntiBerries: string + totalGoldenBerries: string + coins: string +} + +interface LeaderboardEntry extends BaseEntry { + type: 'leaderboard' + value: number +} + +interface Account extends BaseEntry { + type: 'account' + stats: Stats + xp: bigint +} + +export function calculateXP ( + normalBerries: bigint, + poisonBerries: bigint, + slowBerries: bigint, + ultraBerries: bigint, + speedyBerries: bigint, + coinBerries: bigint, + randomBerries: bigint, + antiBerries: bigint, + goldenBerries: bigint +): bigint { + let totalXp = 0n + totalXp += normalBerries + totalXp -= poisonBerries + totalXp -= slowBerries + totalXp += ultraBerries * 5n + totalXp += speedyBerries * 10n + totalXp += coinBerries * 10n + totalXp += randomBerries + totalXp -= antiBerries + totalXp += goldenBerries * 4n + + if (totalXp < 0n) totalXp = 0n + return totalXp +} + +export function calculateLevel (xp: bigint): number { + const levelDivisor = 50.0 + + const xpNumber = Number(xp) + const discriminant = 95 * 95 + levelDivisor * 2 * xpNumber + const level = (-95 + Math.sqrt(discriminant)) / levelDivisor + return Math.floor(level) + 1 +} + +export default function BerryDashLeaderboards () { + const [selected, setSelected] = useState(-1) + const [selectedBerryOption, setSelectedBerryOption] = useState(0) + const [entries, setEntries] = useState<(LeaderboardEntry | Account)[]>([]) + + const router = useRouter() + + const Refresh = useCallback(async () => { + try { + if (selected == 3 || selected == 4) { + const result = await axios.get( + 'https://games.lncvrt.xyz/api/berrydash/account?username=' + ) + if (result.data.success) { + let accounts = result.data.data as Account[] + + accounts = accounts.map(acc => { + const xp = calculateXP( + BigInt(acc.stats.totalNormalBerries), + BigInt(acc.stats.totalPoisonBerries), + BigInt(acc.stats.totalSlowBerries), + BigInt(acc.stats.totalUltraBerries), + BigInt(acc.stats.totalSpeedyBerries), + BigInt(acc.stats.totalCoinBerries), + BigInt(acc.stats.totalRandomBerries), + BigInt(acc.stats.totalAntiBerries), + BigInt(acc.stats.totalGoldenBerries) + ) + return { ...acc, xp } + }) + + accounts.sort((a, b) => (b.xp > a.xp ? 1 : b.xp < a.xp ? -1 : 0)) + + setEntries(accounts) + } + } else { + const result = await axios.get( + 'https://games.lncvrt.xyz/api/berrydash/leaderboard/' + + (selected == 0 + ? 'score' + : selected == 1 + ? 'berry?berry=' + selectedBerryOption + : selected == 2 + ? 'coin' + : selected == 5 + ? 'legacy' + : 'total') + ) + if (result.data.success) { + setEntries(result.data.data as LeaderboardEntry[]) + } + } + } catch { + setEntries([]) + } + }, [selected, selectedBerryOption]) + + useEffect(() => { + document.title = 'Lncvrt Games - Berry Dash Leaderboards' + }, []) + + useEffect(() => { + if (selected != -1) setTimeout(() => Refresh(), 0) + }, [selected, Refresh]) + + return ( +
+
+

Berry Dash Leaderboards

+
+ + +
+
+
+ {selected == -1 ? ( + <> +

Select a Leaderboard

+
+ + + + + + + +
+ + ) : ( + <> +
+ {entries.map((item, index) => { + const isAccount = 'stats' in item + const isLeaderboard = 'value' in item && !isAccount + + return ( +
+
+ +

+ {item.username} (#{index + 1}) +

+
+ + {isLeaderboard ? ( +

+ {selected === 1 || selected === 6 + ? 'Berries' + : selected === 2 + ? 'Coins' + : 'Score'} + : {item.value.toLocaleString('en-US')} +

+ ) : isAccount ? ( +

+ {selected === 3 ? 'Level' : 'XP'}:{' '} + {selected === 3 + ? calculateLevel(item.xp).toLocaleString('en-US') + : item.xp.toLocaleString('en-US')} +

+ ) : null} +
+ ) + })} +
+ {selected == 1 && ( +
+ +
+ )} + + )} +
+
+ ) +} diff --git a/src/app/game/berrydash/leaderboards/styles.css b/src/app/game/berrydash/leaderboards/styles.css new file mode 100644 index 0000000..d72a46a --- /dev/null +++ b/src/app/game/berrydash/leaderboards/styles.css @@ -0,0 +1,14 @@ +@import "tailwindcss"; + +.box { + @apply bg-(--col1) border border-(--col3) rounded-lg w-auto p-1 h-[calc(100vh-84px)]; +} + +.leaderboard-button, +.leaderboard-select { + @apply bg-(--col2) hover:bg-(--col4) border border-(--col4) hover:border-(--col6) rounded-lg px-4 py-2 inline-block transition-all duration-200; +} + +.leaderboard-entry { + @apply rounded-lg text-gray-200 text-lg px-1 py-0.5 transition-colors bg-(--col2) hover:bg-(--col3) border border-(--col4) hover:border-(--col5) cursor-pointer; +} diff --git a/src/app/game/page.tsx b/src/app/game/page.tsx index a27af46..737634d 100644 --- a/src/app/game/page.tsx +++ b/src/app/game/page.tsx @@ -4,7 +4,7 @@ import { useEffect } from 'react' import '@/app/Installs.css' import { invoke } from '@tauri-apps/api/core' import { useGlobal } from '@/app/GlobalProvider' -import { useSearchParams } from 'next/navigation' +import { useRouter, useSearchParams } from 'next/navigation' import { platform } from '@tauri-apps/plugin-os' import { faWarning } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' @@ -34,6 +34,7 @@ export default function Installs () { } = useGlobal() const params = useSearchParams() + const router = useRouter() const id = Number(params.get('id') || 0) const game = serverVersionList?.games.find(g => g.id === id) @@ -65,6 +66,16 @@ export default function Installs () {

Installs

+