feat: Implement a net worth leaderboard by aggregating user balance and inventory item values.
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
import { createCommand } from "@/lib/utils";
|
import { createCommand } from "@/lib/utils";
|
||||||
import { SlashCommandBuilder } from "discord.js";
|
import { SlashCommandBuilder } from "discord.js";
|
||||||
import { DrizzleClient } from "@/lib/DrizzleClient";
|
import { DrizzleClient } from "@/lib/DrizzleClient";
|
||||||
import { users } from "@/db/schema";
|
import { users, items, inventory } from "@/db/schema";
|
||||||
import { desc } from "drizzle-orm";
|
import { desc, sql, eq } from "drizzle-orm";
|
||||||
import { createWarningEmbed } from "@lib/embeds";
|
import { createWarningEmbed } from "@lib/embeds";
|
||||||
import { getLeaderboardEmbed } from "@/modules/leveling/leveling.view";
|
import { getLeaderboardEmbed } from "@/modules/leveling/leveling.view";
|
||||||
|
|
||||||
@@ -12,30 +12,49 @@ export const leaderboard = createCommand({
|
|||||||
.setDescription("View the top players")
|
.setDescription("View the top players")
|
||||||
.addStringOption(option =>
|
.addStringOption(option =>
|
||||||
option.setName("type")
|
option.setName("type")
|
||||||
.setDescription("Sort by XP or Balance")
|
.setDescription("Sort by XP, Balance, or Net Worth")
|
||||||
.setRequired(true)
|
.setRequired(true)
|
||||||
.addChoices(
|
.addChoices(
|
||||||
{ name: "Level / XP", value: "xp" },
|
{ name: "Level / XP", value: "xp" },
|
||||||
{ name: "Balance", value: "balance" }
|
{ name: "Balance", value: "balance" },
|
||||||
|
{ name: "Net Worth", value: "networth" }
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
execute: async (interaction) => {
|
execute: async (interaction) => {
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
|
|
||||||
const type = interaction.options.getString("type", true);
|
const type = interaction.options.getString("type", true);
|
||||||
const isXp = type === "xp";
|
|
||||||
|
|
||||||
const leaders = await DrizzleClient.query.users.findMany({
|
let leaders;
|
||||||
orderBy: isXp ? desc(users.xp) : desc(users.balance),
|
|
||||||
limit: 10
|
if (type === 'networth') {
|
||||||
});
|
leaders = await DrizzleClient.select({
|
||||||
|
username: users.username,
|
||||||
|
level: users.level,
|
||||||
|
xp: users.xp,
|
||||||
|
balance: users.balance,
|
||||||
|
netWorth: sql<bigint>`${users.balance} + COALESCE(SUM(${items.price} * ${inventory.quantity}), 0)`.as('net_worth')
|
||||||
|
})
|
||||||
|
.from(users)
|
||||||
|
.leftJoin(inventory, eq(users.id, inventory.userId))
|
||||||
|
.leftJoin(items, eq(inventory.itemId, items.id))
|
||||||
|
.groupBy(users.id)
|
||||||
|
.orderBy(desc(sql`net_worth`))
|
||||||
|
.limit(10);
|
||||||
|
} else {
|
||||||
|
const isXp = type === "xp";
|
||||||
|
leaders = await DrizzleClient.query.users.findMany({
|
||||||
|
orderBy: isXp ? desc(users.xp) : desc(users.balance),
|
||||||
|
limit: 10
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (leaders.length === 0) {
|
if (leaders.length === 0) {
|
||||||
await interaction.editReply({ embeds: [createWarningEmbed("No users found.", "Leaderboard")] });
|
await interaction.editReply({ embeds: [createWarningEmbed("No users found.", "Leaderboard")] });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const embed = getLeaderboardEmbed(leaders, isXp ? 'xp' : 'balance');
|
const embed = getLeaderboardEmbed(leaders, type as 'xp' | 'balance' | 'networth');
|
||||||
|
|
||||||
await interaction.editReply({ embeds: [embed] });
|
await interaction.editReply({ embeds: [embed] });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ interface LeaderboardUser {
|
|||||||
level: number | null;
|
level: number | null;
|
||||||
xp: bigint | null;
|
xp: bigint | null;
|
||||||
balance: bigint | null;
|
balance: bigint | null;
|
||||||
|
netWorth?: bigint | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,23 +24,45 @@ function getMedalEmoji(index: number): string {
|
|||||||
/**
|
/**
|
||||||
* Formats a single leaderboard entry based on type
|
* Formats a single leaderboard entry based on type
|
||||||
*/
|
*/
|
||||||
function formatLeaderEntry(user: LeaderboardUser, index: number, type: 'xp' | 'balance'): string {
|
function formatLeaderEntry(user: LeaderboardUser, index: number, type: 'xp' | 'balance' | 'networth'): string {
|
||||||
const medal = getMedalEmoji(index);
|
const medal = getMedalEmoji(index);
|
||||||
const value = type === 'xp'
|
let value = '';
|
||||||
? `Lvl ${user.level ?? 1} (${user.xp ?? 0n} XP)`
|
|
||||||
: `${user.balance ?? 0n} 🪙`;
|
switch (type) {
|
||||||
|
case 'xp':
|
||||||
|
value = `Lvl ${user.level ?? 1} (${user.xp ?? 0n} XP)`;
|
||||||
|
break;
|
||||||
|
case 'balance':
|
||||||
|
value = `${user.balance ?? 0n} 🪙`;
|
||||||
|
break;
|
||||||
|
case 'networth':
|
||||||
|
value = `${user.netWorth ?? 0n} 🪙 (Net Worth)`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return `${medal} **${user.username}** — ${value}`;
|
return `${medal} **${user.username}** — ${value}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a leaderboard embed for either XP or Balance rankings
|
* Creates a leaderboard embed for either XP, Balance or Net Worth rankings
|
||||||
*/
|
*/
|
||||||
export function getLeaderboardEmbed(leaders: LeaderboardUser[], type: 'xp' | 'balance'): EmbedBuilder {
|
export function getLeaderboardEmbed(leaders: LeaderboardUser[], type: 'xp' | 'balance' | 'networth'): EmbedBuilder {
|
||||||
const description = leaders.map((user, index) =>
|
const description = leaders.map((user, index) =>
|
||||||
formatLeaderEntry(user, index, type)
|
formatLeaderEntry(user, index, type)
|
||||||
).join("\n");
|
).join("\n");
|
||||||
|
|
||||||
const title = type === 'xp' ? "🏆 XP Leaderboard" : "💰 Richest Players";
|
let title = '';
|
||||||
|
switch (type) {
|
||||||
|
case 'xp':
|
||||||
|
title = "🏆 XP Leaderboard";
|
||||||
|
break;
|
||||||
|
case 'balance':
|
||||||
|
title = "💰 Richest Players";
|
||||||
|
break;
|
||||||
|
case 'networth':
|
||||||
|
title = "💎 Net Worth Leaderboard";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return new EmbedBuilder()
|
return new EmbedBuilder()
|
||||||
.setTitle(title)
|
.setTitle(title)
|
||||||
|
|||||||
Reference in New Issue
Block a user