profile update with badges and achievements
This commit is contained in:
@@ -0,0 +1,22 @@
|
|||||||
|
const {
|
||||||
|
buildStatsEmbed,
|
||||||
|
buildProfileButtons,
|
||||||
|
getProfileTargetId
|
||||||
|
} = require("../../utils/profileSystem");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: "profileStats",
|
||||||
|
|
||||||
|
async execute(interaction) {
|
||||||
|
const userId = getProfileTargetId(interaction);
|
||||||
|
if (!userId) return;
|
||||||
|
|
||||||
|
const user = await interaction.client.users.fetch(userId);
|
||||||
|
const embed = await buildStatsEmbed(interaction.guildId, user);
|
||||||
|
|
||||||
|
return interaction.update({
|
||||||
|
embeds: [embed],
|
||||||
|
components: buildProfileButtons()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -18,6 +18,10 @@ const {
|
|||||||
formatPercentage
|
formatPercentage
|
||||||
} = require("../../utils/cookieEconomy");
|
} = require("../../utils/cookieEconomy");
|
||||||
|
|
||||||
|
const {
|
||||||
|
trackProfileEvent
|
||||||
|
} = require("../../utils/profileSystem");
|
||||||
|
|
||||||
|
|
||||||
const COLLECT_COOLDOWN =
|
const COLLECT_COOLDOWN =
|
||||||
60 * 60;
|
60 * 60;
|
||||||
@@ -214,6 +218,15 @@ async function collect(interaction) {
|
|||||||
amount
|
amount
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
userId,
|
||||||
|
{
|
||||||
|
type: "cookie_gain",
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
const nextCollect =
|
const nextCollect =
|
||||||
await setCooldown(
|
await setCooldown(
|
||||||
@@ -456,6 +469,44 @@ async function steal(interaction) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
user.id,
|
||||||
|
{
|
||||||
|
type: "steal_success",
|
||||||
|
amount,
|
||||||
|
targetUserId: target.id
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
user.id,
|
||||||
|
{
|
||||||
|
type: "cookie_gain",
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
target.id,
|
||||||
|
{
|
||||||
|
type: "stolen_from_me",
|
||||||
|
amount,
|
||||||
|
thiefUserId: user.id
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
target.id,
|
||||||
|
{
|
||||||
|
type: "cookie_loss",
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
yourBalanceAfter,
|
yourBalanceAfter,
|
||||||
targetBalanceAfter
|
targetBalanceAfter
|
||||||
@@ -605,6 +656,24 @@ async function steal(interaction) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
user.id,
|
||||||
|
{
|
||||||
|
type: "cookie_loss",
|
||||||
|
amount: penalty
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
target.id,
|
||||||
|
{
|
||||||
|
type: "cookie_gain",
|
||||||
|
amount: penalty
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
yourBalanceAfter,
|
yourBalanceAfter,
|
||||||
targetBalanceAfter
|
targetBalanceAfter
|
||||||
@@ -851,6 +920,24 @@ async function give(interaction) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
user.id,
|
||||||
|
{
|
||||||
|
type: "cookies_given",
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
target.id,
|
||||||
|
{
|
||||||
|
type: "cookie_gain",
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
yourBalance,
|
yourBalance,
|
||||||
theirBalance
|
theirBalance
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ const {
|
|||||||
formatPercentage
|
formatPercentage
|
||||||
} = require("./cookieEconomy");
|
} = require("./cookieEconomy");
|
||||||
|
|
||||||
|
const {
|
||||||
|
trackProfileEvent
|
||||||
|
} = require("./profileSystem");
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ============================================================
|
* ============================================================
|
||||||
@@ -394,6 +398,46 @@ async function resolveCookieHeist(
|
|||||||
100;
|
100;
|
||||||
|
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
playerId,
|
||||||
|
{
|
||||||
|
type:
|
||||||
|
"game_result",
|
||||||
|
|
||||||
|
game:
|
||||||
|
"heist",
|
||||||
|
|
||||||
|
result:
|
||||||
|
"win"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
playerId,
|
||||||
|
{
|
||||||
|
type:
|
||||||
|
"vault_steal",
|
||||||
|
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
playerId,
|
||||||
|
{
|
||||||
|
type:
|
||||||
|
"cookie_gain",
|
||||||
|
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
const embed =
|
const embed =
|
||||||
new EmbedBuilder()
|
new EmbedBuilder()
|
||||||
.setTitle(
|
.setTitle(
|
||||||
@@ -605,6 +649,35 @@ async function resolveCookieHeist(
|
|||||||
100;
|
100;
|
||||||
|
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
playerId,
|
||||||
|
{
|
||||||
|
type:
|
||||||
|
"game_result",
|
||||||
|
|
||||||
|
game:
|
||||||
|
"heist",
|
||||||
|
|
||||||
|
result:
|
||||||
|
"loss"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
playerId,
|
||||||
|
{
|
||||||
|
type:
|
||||||
|
"cookie_loss",
|
||||||
|
|
||||||
|
amount:
|
||||||
|
penalty
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
const embed =
|
const embed =
|
||||||
new EmbedBuilder()
|
new EmbedBuilder()
|
||||||
.setTitle(
|
.setTitle(
|
||||||
|
|||||||
+88
-2
@@ -17,6 +17,10 @@ const {
|
|||||||
getUnixTime
|
getUnixTime
|
||||||
} = require("./cookieEconomy");
|
} = require("./cookieEconomy");
|
||||||
|
|
||||||
|
const {
|
||||||
|
trackProfileEvent
|
||||||
|
} = require("./profileSystem");
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ============================================================
|
* ============================================================
|
||||||
@@ -1277,7 +1281,7 @@ async function finishBlackjack(
|
|||||||
|
|
||||||
.setFooter({
|
.setFooter({
|
||||||
text:
|
text:
|
||||||
`crumbBlackjackResult:${game.userId}:${game.bet}`
|
`crumbBlackjackResult:${game.userId}:${game.originalBet ?? game.bet}`
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -1350,6 +1354,88 @@ async function finishBlackjack(
|
|||||||
game.bet;
|
game.bet;
|
||||||
|
|
||||||
|
|
||||||
|
let profileResult;
|
||||||
|
|
||||||
|
if (
|
||||||
|
result ===
|
||||||
|
"Push"
|
||||||
|
) {
|
||||||
|
profileResult =
|
||||||
|
"push";
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (
|
||||||
|
[
|
||||||
|
"Win",
|
||||||
|
"DealerBust",
|
||||||
|
"Blackjack"
|
||||||
|
].includes(
|
||||||
|
result
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
profileResult =
|
||||||
|
"win";
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
profileResult =
|
||||||
|
"loss";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
game.userId,
|
||||||
|
{
|
||||||
|
type:
|
||||||
|
"game_result",
|
||||||
|
|
||||||
|
game:
|
||||||
|
"blackjack",
|
||||||
|
|
||||||
|
result:
|
||||||
|
profileResult
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
profit >
|
||||||
|
0
|
||||||
|
) {
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
game.userId,
|
||||||
|
{
|
||||||
|
type:
|
||||||
|
"cookie_gain",
|
||||||
|
|
||||||
|
amount:
|
||||||
|
profit
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (
|
||||||
|
profit <
|
||||||
|
0
|
||||||
|
) {
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
game.userId,
|
||||||
|
{
|
||||||
|
type:
|
||||||
|
"cookie_loss",
|
||||||
|
|
||||||
|
amount:
|
||||||
|
Math.abs(
|
||||||
|
profit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const description = [
|
const description = [
|
||||||
"**Dealer**",
|
"**Dealer**",
|
||||||
formatCards(
|
formatCards(
|
||||||
@@ -1439,7 +1525,7 @@ async function finishBlackjack(
|
|||||||
|
|
||||||
.setFooter({
|
.setFooter({
|
||||||
text:
|
text:
|
||||||
`crumbBlackjackResult:${game.userId}:${game.bet}`
|
`crumbBlackjackResult:${game.userId}:${game.originalBet ?? game.bet}`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ const {
|
|||||||
temporaryEphemeralReply
|
temporaryEphemeralReply
|
||||||
} = require("./cookieEconomy");
|
} = require("./cookieEconomy");
|
||||||
|
|
||||||
|
const {
|
||||||
|
trackProfileEvent
|
||||||
|
} = require("./profileSystem");
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ============================================================
|
* ============================================================
|
||||||
@@ -852,6 +856,63 @@ async function playRoulette(
|
|||||||
bet;
|
bet;
|
||||||
|
|
||||||
|
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
playerId,
|
||||||
|
{
|
||||||
|
type:
|
||||||
|
"game_result",
|
||||||
|
|
||||||
|
game:
|
||||||
|
"roulette",
|
||||||
|
|
||||||
|
result:
|
||||||
|
profit > 0
|
||||||
|
? "win"
|
||||||
|
: profit < 0
|
||||||
|
? "loss"
|
||||||
|
: "push"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
profit >
|
||||||
|
0
|
||||||
|
) {
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
playerId,
|
||||||
|
{
|
||||||
|
type:
|
||||||
|
"cookie_gain",
|
||||||
|
|
||||||
|
amount:
|
||||||
|
profit
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (
|
||||||
|
profit <
|
||||||
|
0
|
||||||
|
) {
|
||||||
|
await trackProfileEvent(
|
||||||
|
interaction,
|
||||||
|
playerId,
|
||||||
|
{
|
||||||
|
type:
|
||||||
|
"cookie_loss",
|
||||||
|
|
||||||
|
amount:
|
||||||
|
Math.abs(
|
||||||
|
profit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const resultEmbed =
|
const resultEmbed =
|
||||||
new EmbedBuilder()
|
new EmbedBuilder()
|
||||||
.setTitle(
|
.setTitle(
|
||||||
|
|||||||
+52
-1389
File diff suppressed because it is too large
Load Diff
+121
-12
@@ -21,6 +21,8 @@ const COLLECTIONS = {
|
|||||||
achievementConfig: "config_achievements"
|
achievementConfig: "config_achievements"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let profileSystemInitPromise = null;
|
||||||
|
|
||||||
const DEFAULT_ACHIEVEMENTS = [
|
const DEFAULT_ACHIEVEMENTS = [
|
||||||
{ achievementId: "stolen_1000", name: "Light Fingers", description: "Steal 1,000 Cookies from other players.", requirement: "Steal 1,000 Cookies total.", category: "stealing", stat: "economy.stolenTotal", target: 1000, emoji: "🧤" },
|
{ achievementId: "stolen_1000", name: "Light Fingers", description: "Steal 1,000 Cookies from other players.", requirement: "Steal 1,000 Cookies total.", category: "stealing", stat: "economy.stolenTotal", target: 1000, emoji: "🧤" },
|
||||||
{ achievementId: "stolen_10000", name: "Career Criminal", description: "Steal 10,000 Cookies from other players.", requirement: "Steal 10,000 Cookies total.", category: "stealing", stat: "economy.stolenTotal", target: 10000, emoji: "🕵️" },
|
{ achievementId: "stolen_10000", name: "Career Criminal", description: "Steal 10,000 Cookies from other players.", requirement: "Steal 10,000 Cookies total.", category: "stealing", stat: "economy.stolenTotal", target: 10000, emoji: "🕵️" },
|
||||||
@@ -41,7 +43,15 @@ const DEFAULT_ACHIEVEMENTS = [
|
|||||||
{ achievementId: "win_streak_3", name: "On a Roll", description: "Win 3 games in a row.", requirement: "Reach a 3-game winning streak.", category: "games", stat: "streaks.bestWin", target: 3, emoji: "🍀" },
|
{ achievementId: "win_streak_3", name: "On a Roll", description: "Win 3 games in a row.", requirement: "Reach a 3-game winning streak.", category: "games", stat: "streaks.bestWin", target: 3, emoji: "🍀" },
|
||||||
{ achievementId: "win_streak_10", name: "Can't Stop Won't Stop", description: "Win 10 games in a row.", requirement: "Reach a 10-game winning streak.", category: "games", stat: "streaks.bestWin", target: 10, emoji: "🔥" },
|
{ achievementId: "win_streak_10", name: "Can't Stop Won't Stop", description: "Win 10 games in a row.", requirement: "Reach a 10-game winning streak.", category: "games", stat: "streaks.bestWin", target: 10, emoji: "🔥" },
|
||||||
{ achievementId: "loss_streak_3", name: "Rough Night", description: "Lose 3 games in a row.", requirement: "Reach a 3-game losing streak.", category: "games", stat: "streaks.bestLoss", target: 3, emoji: "😬" },
|
{ achievementId: "loss_streak_3", name: "Rough Night", description: "Lose 3 games in a row.", requirement: "Reach a 3-game losing streak.", category: "games", stat: "streaks.bestLoss", target: 3, emoji: "😬" },
|
||||||
{ achievementId: "loss_streak_10", name: "Digging Deeper", description: "Lose 10 games in a row.", requirement: "Reach a 10-game losing streak.", category: "games", stat: "streaks.bestLoss", target: 10, emoji: "🕳️" }
|
{ achievementId: "loss_streak_10", name: "Digging Deeper", description: "Lose 10 games in a row.", requirement: "Reach a 10-game losing streak.", category: "games", stat: "streaks.bestLoss", target: 10, emoji: "🕳️" },
|
||||||
|
{ achievementId: "given_10000", name: "Sharing Is Caring", description: "Give 10,000 Cookies to other players.", requirement: "Give 10,000 Cookies to other players total.", category: "economy", stat: "economy.cookiesGivenTotal", target: 10000, emoji: "🎁" },
|
||||||
|
{ achievementId: "given_50000", name: "Community Baker", description: "Give 50,000 Cookies to other players.", requirement: "Give 50,000 Cookies to other players total.", category: "economy", stat: "economy.cookiesGivenTotal", target: 50000, emoji: "🤝" },
|
||||||
|
{ achievementId: "games_won_10", name: "House Regular", description: "Win 10 Crumb Saucer games.", requirement: "Win 10 games total.", category: "games", stat: "games.total.won", target: 10, emoji: "🎲" },
|
||||||
|
{ achievementId: "games_won_50", name: "House Favorite", description: "Win 50 Crumb Saucer games.", requirement: "Win 50 games total.", category: "games", stat: "games.total.won", target: 50, emoji: "🏆" },
|
||||||
|
{ achievementId: "slots_won_10", name: "Reel Deal", description: "Win 10 Slots spins.", requirement: "Win 10 Slots games.", category: "games", stat: "games.slots.won", target: 10, emoji: "🎰" },
|
||||||
|
{ achievementId: "blackjack_won_10", name: "Card Counter-ish", description: "Win 10 Blackjack hands.", requirement: "Win 10 Blackjack games.", category: "games", stat: "games.blackjack.won", target: 10, emoji: "🃏" },
|
||||||
|
{ achievementId: "roulette_won_10", name: "Wheel Watcher", description: "Win 10 Roulette bets.", requirement: "Win 10 Roulette games.", category: "games", stat: "games.roulette.won", target: 10, emoji: "🎡" },
|
||||||
|
{ achievementId: "heist_won_10", name: "Professional", description: "Complete 10 successful Vault Heists.", requirement: "Win 10 Vault Heists.", category: "games", stat: "games.heist.won", target: 10, emoji: "🏦" }
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEFAULT_BADGES = [
|
const DEFAULT_BADGES = [
|
||||||
@@ -63,6 +73,9 @@ function getProfileCollections() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function ensureProfileSystem() {
|
async function ensureProfileSystem() {
|
||||||
|
if (profileSystemInitPromise) return profileSystemInitPromise;
|
||||||
|
|
||||||
|
profileSystemInitPromise = (async () => {
|
||||||
const { stats, achievements, badges, achievementConfig } = getProfileCollections();
|
const { stats, achievements, badges, achievementConfig } = getProfileCollections();
|
||||||
await stats.createIndex({ userId: 1, guildId: 1 }, { unique: true });
|
await stats.createIndex({ userId: 1, guildId: 1 }, { unique: true });
|
||||||
await achievements.createIndex({ userId: 1, guildId: 1 }, { unique: true });
|
await achievements.createIndex({ userId: 1, guildId: 1 }, { unique: true });
|
||||||
@@ -76,9 +89,18 @@ async function ensureProfileSystem() {
|
|||||||
{ upsert: true }
|
{ upsert: true }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await profileSystemInitPromise;
|
||||||
|
} catch (error) {
|
||||||
|
profileSystemInitPromise = null;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureGuildBadges(guildId) {
|
async function ensureGuildBadges(guildId) {
|
||||||
|
await ensureProfileSystem();
|
||||||
const { badges } = getProfileCollections();
|
const { badges } = getProfileCollections();
|
||||||
for (const badge of DEFAULT_BADGES) {
|
for (const badge of DEFAULT_BADGES) {
|
||||||
await badges.updateOne(
|
await badges.updateOne(
|
||||||
@@ -145,6 +167,7 @@ async function getProfileStats(userId, guildId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function trackProfileEvent(interaction, userId, event) {
|
async function trackProfileEvent(interaction, userId, event) {
|
||||||
|
await ensureProfileSystem();
|
||||||
const guildId = interaction.guildId;
|
const guildId = interaction.guildId;
|
||||||
if (!guildId || !userId || !event?.type) return;
|
if (!guildId || !userId || !event?.type) return;
|
||||||
|
|
||||||
@@ -190,7 +213,7 @@ async function trackProfileEvent(interaction, userId, event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updatedStats = await getProfileStats(userId, guildId);
|
const updatedStats = await getProfileStats(userId, guildId);
|
||||||
await evaluateAchievements(interaction, userId, updatedStats);
|
await evaluateAchievements(interaction, userId, updatedStats, event.type);
|
||||||
await evaluateBadges(interaction, userId, updatedStats);
|
await evaluateBadges(interaction, userId, updatedStats);
|
||||||
return updatedStats;
|
return updatedStats;
|
||||||
}
|
}
|
||||||
@@ -244,12 +267,15 @@ async function trackGameResult(userId, guildId, event) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function evaluateAchievements(interaction, userId, statsDocument) {
|
async function evaluateAchievements(interaction, userId, statsDocument, eventType = null) {
|
||||||
const { achievements, achievementConfig } = getProfileCollections();
|
const { achievements, achievementConfig } = getProfileCollections();
|
||||||
const guildId = interaction.guildId;
|
const guildId = interaction.guildId;
|
||||||
await ensureAchievementDocument(userId, guildId);
|
await ensureAchievementDocument(userId, guildId);
|
||||||
|
|
||||||
const definitions = await achievementConfig.find({}).toArray();
|
const relevantStats = getRelevantAchievementStats(eventType);
|
||||||
|
const definitions = await achievementConfig.find(
|
||||||
|
relevantStats ? { stat: { $in: relevantStats } } : {}
|
||||||
|
).toArray();
|
||||||
const unlockedDoc = await achievements.findOne({ userId, guildId });
|
const unlockedDoc = await achievements.findOne({ userId, guildId });
|
||||||
const unlocked = unlockedDoc?.unlocked ?? {};
|
const unlocked = unlockedDoc?.unlocked ?? {};
|
||||||
|
|
||||||
@@ -265,7 +291,7 @@ async function evaluateAchievements(interaction, userId, statsDocument) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (result.modifiedCount === 1) {
|
if (result.modifiedCount === 1) {
|
||||||
await sendAchievementNotification(interaction, definition);
|
await sendAchievementNotification(interaction, userId, definition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -314,23 +340,38 @@ async function tryClaimBadge(interaction, userId, badgeId) {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendAchievementNotification(interaction, achievement) {
|
async function sendAchievementNotification(interaction, userId, achievement) {
|
||||||
try {
|
try {
|
||||||
await interaction.followUp({
|
await interaction.channel.send({
|
||||||
embeds: [
|
embeds: [
|
||||||
new EmbedBuilder()
|
new EmbedBuilder()
|
||||||
.setTitle("🏆 Achievement Unlocked!")
|
.setTitle("🏆 Achievement Unlocked!")
|
||||||
.setDescription([
|
.setDescription([
|
||||||
|
`<@${userId}>`,
|
||||||
|
"",
|
||||||
`${achievement.emoji} **${achievement.name}**`,
|
`${achievement.emoji} **${achievement.name}**`,
|
||||||
"",
|
"",
|
||||||
achievement.description
|
achievement.description
|
||||||
].join("\n"))
|
].join("\n"))
|
||||||
],
|
]
|
||||||
flags: MessageFlags.Ephemeral
|
|
||||||
});
|
});
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getRelevantAchievementStats(eventType) {
|
||||||
|
switch (eventType) {
|
||||||
|
case "cookie_gain": return ["economy.cookiesObtainedTotal"];
|
||||||
|
case "cookie_loss": return ["economy.cookiesLostTotal"];
|
||||||
|
case "cookies_given": return ["economy.cookiesGivenTotal"];
|
||||||
|
case "steal_success": return ["economy.stolenTotal", "unique.playersStolenFrom"];
|
||||||
|
case "stolen_from_me": return ["economy.stolenFromMeTotal", "unique.playersStolenBy"];
|
||||||
|
case "vault_steal": return ["economy.vaultStolenTotal"];
|
||||||
|
case "game_result": return ["streaks.bestWin", "streaks.bestLoss", "games.total.won", "games.slots.won", "games.blackjack.won", "games.roulette.won", "games.heist.won"];
|
||||||
|
default: return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function readStatValue(document, definition) {
|
function readStatValue(document, definition) {
|
||||||
const value = definition.stat
|
const value = definition.stat
|
||||||
.split(".")
|
.split(".")
|
||||||
@@ -344,6 +385,7 @@ function readStatValue(document, definition) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function buildProfileOverview(guildId, user) {
|
async function buildProfileOverview(guildId, user) {
|
||||||
|
await ensureProfileSystem();
|
||||||
const stats = await getProfileStats(user.id, guildId);
|
const stats = await getProfileStats(user.id, guildId);
|
||||||
const balance = await getCookieBalance(user.id);
|
const balance = await getCookieBalance(user.id);
|
||||||
const { achievements, badges, achievementConfig } = getProfileCollections();
|
const { achievements, badges, achievementConfig } = getProfileCollections();
|
||||||
@@ -386,6 +428,7 @@ async function buildProfileOverview(guildId, user) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function buildBadgesEmbed(guildId, user) {
|
async function buildBadgesEmbed(guildId, user) {
|
||||||
|
await ensureProfileSystem();
|
||||||
await ensureGuildBadges(guildId);
|
await ensureGuildBadges(guildId);
|
||||||
const { badges } = getProfileCollections();
|
const { badges } = getProfileCollections();
|
||||||
const documents = await badges.find({ guildId }).toArray();
|
const documents = await badges.find({ guildId }).toArray();
|
||||||
@@ -416,6 +459,7 @@ async function buildBadgesEmbed(guildId, user) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function buildAchievementsEmbed(guildId, user) {
|
async function buildAchievementsEmbed(guildId, user) {
|
||||||
|
await ensureProfileSystem();
|
||||||
const { achievements, achievementConfig } = getProfileCollections();
|
const { achievements, achievementConfig } = getProfileCollections();
|
||||||
|
|
||||||
const [unlockedDoc, definitions] = await Promise.all([
|
const [unlockedDoc, definitions] = await Promise.all([
|
||||||
@@ -424,9 +468,16 @@ async function buildAchievementsEmbed(guildId, user) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const unlocked = unlockedDoc?.unlocked ?? {};
|
const unlocked = unlockedDoc?.unlocked ?? {};
|
||||||
const lines = definitions.map(a =>
|
const stats = await getProfileStats(user.id, guildId);
|
||||||
`${unlocked[a.achievementId] ? "✅" : "⬜"} ${a.emoji} **${a.name}** — ${a.requirement}`
|
const lines = definitions.map(a => {
|
||||||
);
|
const complete = Boolean(unlocked[a.achievementId]);
|
||||||
|
const current = readStatValue(stats, a);
|
||||||
|
const progress = complete
|
||||||
|
? "Unlocked"
|
||||||
|
: `${Math.min(current, a.target).toLocaleString()} / ${a.target.toLocaleString()}`;
|
||||||
|
|
||||||
|
return `${complete ? "✅" : "⬜"} ${a.emoji} **${a.name}** — ${a.requirement}\n↳ ${progress}`;
|
||||||
|
});
|
||||||
|
|
||||||
return new EmbedBuilder()
|
return new EmbedBuilder()
|
||||||
.setTitle(`🏆 ${user.globalName ?? user.username}'s Achievements`)
|
.setTitle(`🏆 ${user.globalName ?? user.username}'s Achievements`)
|
||||||
@@ -434,10 +485,67 @@ async function buildAchievementsEmbed(guildId, user) {
|
|||||||
.setFooter({ text: `profile:${user.id}` });
|
.setFooter({ text: `profile:${user.id}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function buildStatsEmbed(guildId, user) {
|
||||||
|
await ensureProfileSystem();
|
||||||
|
const stats = await getProfileStats(user.id, guildId);
|
||||||
|
const e = stats.economy;
|
||||||
|
const g = stats.games;
|
||||||
|
|
||||||
|
return new EmbedBuilder()
|
||||||
|
.setTitle(`📊 ${user.globalName ?? user.username}'s Statistics`)
|
||||||
|
.setThumbnail(user.displayAvatarURL({ size: 256 }))
|
||||||
|
.addFields(
|
||||||
|
{
|
||||||
|
name: "🍪 Economy",
|
||||||
|
value: [
|
||||||
|
`Obtained: **${e.cookiesObtainedTotal.toLocaleString()}**`,
|
||||||
|
`Lost: **${e.cookiesLostTotal.toLocaleString()}**`,
|
||||||
|
`Given: **${e.cookiesGivenTotal.toLocaleString()}**`,
|
||||||
|
`Stolen: **${e.stolenTotal.toLocaleString()}**`,
|
||||||
|
`Stolen from you: **${e.stolenFromMeTotal.toLocaleString()}**`,
|
||||||
|
`Bot Vault stolen: **${e.vaultStolenTotal.toLocaleString()}**`
|
||||||
|
].join("\n")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "🥷 Theft",
|
||||||
|
value: [
|
||||||
|
`Unique victims: **${stats.unique.playersStolenFrom.length}**`,
|
||||||
|
`Unique thieves: **${stats.unique.playersStolenBy.length}**`
|
||||||
|
].join("\n"),
|
||||||
|
inline: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "🔥 Streaks",
|
||||||
|
value: [
|
||||||
|
`Current wins: **${stats.streaks.currentWin}**`,
|
||||||
|
`Best wins: **${stats.streaks.bestWin}**`,
|
||||||
|
`Current losses: **${stats.streaks.currentLoss}**`,
|
||||||
|
`Best losses: **${stats.streaks.bestLoss}**`
|
||||||
|
].join("\n"),
|
||||||
|
inline: true
|
||||||
|
},
|
||||||
|
{ name: "🎰 Slots", value: formatGameStats(g.slots), inline: true },
|
||||||
|
{ name: "🃏 Blackjack", value: formatGameStats(g.blackjack), inline: true },
|
||||||
|
{ name: "🎡 Roulette", value: formatGameStats(g.roulette), inline: true },
|
||||||
|
{ name: "🏦 Heist", value: formatGameStats(g.heist), inline: true }
|
||||||
|
)
|
||||||
|
.setFooter({ text: `profile:${user.id}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatGameStats(game) {
|
||||||
|
return [
|
||||||
|
`Played: **${game?.played ?? 0}**`,
|
||||||
|
`Won: **${game?.won ?? 0}**`,
|
||||||
|
`Lost: **${game?.lost ?? 0}**`,
|
||||||
|
`Pushes: **${game?.pushed ?? 0}**`
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
function buildProfileButtons() {
|
function buildProfileButtons() {
|
||||||
return [
|
return [
|
||||||
new ActionRowBuilder().addComponents(
|
new ActionRowBuilder().addComponents(
|
||||||
new ButtonBuilder().setCustomId("profileOverview").setLabel("Overview").setEmoji("👤").setStyle(ButtonStyle.Primary),
|
new ButtonBuilder().setCustomId("profileOverview").setLabel("Overview").setEmoji("👤").setStyle(ButtonStyle.Primary),
|
||||||
|
new ButtonBuilder().setCustomId("profileStats").setLabel("Stats").setEmoji("📊").setStyle(ButtonStyle.Secondary),
|
||||||
new ButtonBuilder().setCustomId("profileBadges").setLabel("Badges").setEmoji("🏅").setStyle(ButtonStyle.Secondary),
|
new ButtonBuilder().setCustomId("profileBadges").setLabel("Badges").setEmoji("🏅").setStyle(ButtonStyle.Secondary),
|
||||||
new ButtonBuilder().setCustomId("profileAchievements").setLabel("Achievements").setEmoji("🏆").setStyle(ButtonStyle.Secondary),
|
new ButtonBuilder().setCustomId("profileAchievements").setLabel("Achievements").setEmoji("🏆").setStyle(ButtonStyle.Secondary),
|
||||||
new ButtonBuilder().setCustomId("abort").setLabel("Close").setEmoji("✖️").setStyle(ButtonStyle.Secondary)
|
new ButtonBuilder().setCustomId("abort").setLabel("Close").setEmoji("✖️").setStyle(ButtonStyle.Secondary)
|
||||||
@@ -457,6 +565,7 @@ module.exports = {
|
|||||||
trackProfileEvent,
|
trackProfileEvent,
|
||||||
getProfileStats,
|
getProfileStats,
|
||||||
buildProfileOverview,
|
buildProfileOverview,
|
||||||
|
buildStatsEmbed,
|
||||||
buildBadgesEmbed,
|
buildBadgesEmbed,
|
||||||
buildAchievementsEmbed,
|
buildAchievementsEmbed,
|
||||||
buildProfileButtons,
|
buildProfileButtons,
|
||||||
|
|||||||
Reference in New Issue
Block a user