Add XP and Level leaderboards
This commit is contained in:
@@ -8,7 +8,7 @@ import axios from 'axios'
|
|||||||
import { GetIconForUser } from '@/util/bd'
|
import { GetIconForUser } from '@/util/bd'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
|
|
||||||
interface LeaderboardEntry {
|
interface BaseEntry {
|
||||||
id: number
|
id: number
|
||||||
username: string
|
username: string
|
||||||
value: number
|
value: number
|
||||||
@@ -19,15 +19,100 @@ interface LeaderboardEntry {
|
|||||||
customIcon: string | null
|
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 () {
|
export default function BerryDashLeaderboards () {
|
||||||
const [selected, setSelected] = useState<number>(-1)
|
const [selected, setSelected] = useState<number>(-1)
|
||||||
const [selectedBerryOption, setSelectedBerryOption] = useState<number>(0)
|
const [selectedBerryOption, setSelectedBerryOption] = useState<number>(0)
|
||||||
const [gettingEntries, setGettingEntries] = useState<boolean>(false)
|
const [gettingEntries, setGettingEntries] = useState<boolean>(false)
|
||||||
const [entries, setEntries] = useState<LeaderboardEntry[]>([])
|
const [entries, setEntries] = useState<(LeaderboardEntry | Account)[]>([])
|
||||||
|
|
||||||
const Refresh = useCallback(async () => {
|
const Refresh = useCallback(async () => {
|
||||||
setGettingEntries(true)
|
setGettingEntries(true)
|
||||||
try {
|
try {
|
||||||
|
if (selected == 3 || selected == 4) {
|
||||||
|
const result = await axios.get('/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(
|
const result = await axios.get(
|
||||||
'/api/berrydash/leaderboard/' +
|
'/api/berrydash/leaderboard/' +
|
||||||
(selected == 0
|
(selected == 0
|
||||||
@@ -36,12 +121,13 @@ export default function BerryDashLeaderboards () {
|
|||||||
? 'berry?berry=' + selectedBerryOption
|
? 'berry?berry=' + selectedBerryOption
|
||||||
: selected == 2
|
: selected == 2
|
||||||
? 'coin'
|
? 'coin'
|
||||||
: selected == 3
|
: selected == 5
|
||||||
? 'legacy'
|
? 'legacy'
|
||||||
: 'total')
|
: 'total')
|
||||||
)
|
)
|
||||||
if (result.data.success) {
|
if (result.data.success) {
|
||||||
setEntries(result.data.data)
|
setEntries(result.data.data as LeaderboardEntry[])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
setEntries([])
|
setEntries([])
|
||||||
@@ -82,14 +168,10 @@ export default function BerryDashLeaderboards () {
|
|||||||
<button onClick={() => setSelected(0)}>Score Leaderboard</button>
|
<button onClick={() => setSelected(0)}>Score Leaderboard</button>
|
||||||
<button onClick={() => setSelected(1)}>Berry Leaderboard</button>
|
<button onClick={() => setSelected(1)}>Berry Leaderboard</button>
|
||||||
<button onClick={() => setSelected(2)}>Coins Leaderboard</button>
|
<button onClick={() => setSelected(2)}>Coins Leaderboard</button>
|
||||||
<button onClick={() => setSelected(3)} disabled={true}>
|
<button onClick={() => setSelected(3)}>Level Leaderboard</button>
|
||||||
Level Leaderboard
|
<button onClick={() => setSelected(4)}>Total XP Leaderboard</button>
|
||||||
</button>
|
|
||||||
<button onClick={() => setSelected(4)} disabled={true}>
|
|
||||||
Total XP Leaderboard
|
|
||||||
</button>
|
|
||||||
<button onClick={() => setSelected(5)}>Legacy Leaderboard</button>
|
<button onClick={() => setSelected(5)}>Legacy Leaderboard</button>
|
||||||
<button onClick={() => setSelected(4)}>
|
<button onClick={() => setSelected(6)}>
|
||||||
Total Berries Leaderboard
|
Total Berries Leaderboard
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,6 +180,9 @@ export default function BerryDashLeaderboards () {
|
|||||||
<>
|
<>
|
||||||
<div className='flex flex-col gap-2'>
|
<div className='flex flex-col gap-2'>
|
||||||
{entries.map((item, index) => {
|
{entries.map((item, index) => {
|
||||||
|
const isAccount = 'stats' in item
|
||||||
|
const isLeaderboard = 'value' in item && !isAccount
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -108,32 +193,40 @@ export default function BerryDashLeaderboards () {
|
|||||||
src={
|
src={
|
||||||
!item.customIcon
|
!item.customIcon
|
||||||
? `https://games-r2.lncvrt.xyz/game-assets/berrydash/icons/bird_${
|
? `https://games-r2.lncvrt.xyz/game-assets/berrydash/icons/bird_${
|
||||||
item.icon == 1
|
item.icon === 1
|
||||||
? GetIconForUser(item.id)
|
? GetIconForUser(item.id)
|
||||||
: item.icon
|
: item.icon
|
||||||
}.png`
|
}.png`
|
||||||
: '/api/berrydash/icon-marketplace/icon?id=' +
|
: `/api/berrydash/icon-marketplace/icon?id=${item.customIcon}&raw=true`
|
||||||
item.customIcon +
|
|
||||||
'&raw=true'
|
|
||||||
}
|
}
|
||||||
className='pointer-events-none'
|
className='pointer-events-none'
|
||||||
width={48}
|
width={48}
|
||||||
height={48}
|
height={48}
|
||||||
alt=''
|
alt=''
|
||||||
unoptimized={true}
|
unoptimized
|
||||||
/>
|
/>
|
||||||
<p>
|
<p>
|
||||||
{item.username} (#{index + 1})
|
{item.username} (#{index + 1})
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isLeaderboard ? (
|
||||||
<p>
|
<p>
|
||||||
{selected == 1 || selected == 4
|
{selected === 1 || selected === 6
|
||||||
? 'Berries'
|
? 'Berries'
|
||||||
: selected == 2
|
: selected === 2
|
||||||
? 'Coins'
|
? 'Coins'
|
||||||
: 'Score'}
|
: 'Score'}
|
||||||
: {item.value.toLocaleString('en-US')}
|
: {item.value.toLocaleString('en-US')}
|
||||||
</p>
|
</p>
|
||||||
|
) : isAccount ? (
|
||||||
|
<p>
|
||||||
|
{selected === 3 ? 'Level' : 'XP'}:{' '}
|
||||||
|
{selected === 3
|
||||||
|
? calculateLevel(item.xp).toLocaleString('en-US')
|
||||||
|
: item.xp.toLocaleString('en-US')}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2017",
|
"target": "es2024",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user