Files
ShaiBot/commands/applications/cookies.js
T
2026-08-23 00:21:52 +02:00

1376 lines
31 KiB
JavaScript

const {
SlashCommandBuilder,
EmbedBuilder,
MessageFlags
} = require("discord.js");
const {
getCollections,
getCookieBalance,
transferCookies,
claimCooldown,
randomFloat,
randomItem,
formatPercentage
} = require("../../utils/cookieEconomy");
const {
trackProfileEvent
} = require("../../utils/profileSystem");
const {
collectCookies
} = require("../../utils/crumbCollect");
/*
* ============================================================
* CONFIG
* ============================================================
*/
const STEAL_COOLDOWN = 15 * 60; // 15 minutes
/*
* ============================================================
* COMMAND
* ============================================================
*/
module.exports = {
data:
new SlashCommandBuilder()
.setName("cookies")
.setDescription("The ShaiBot cookie economy.")
.addSubcommand(
subcommand =>
subcommand
.setName("collect")
.setDescription(
"Collect a fresh batch of cookies."
)
)
.addSubcommand(
subcommand =>
subcommand
.setName("steal")
.setDescription(
"Attempt to steal another user's cookies."
)
.addUserOption(
option =>
option
.setName("target")
.setDescription(
"The unfortunate victim."
)
.setRequired(true)
)
)
.addSubcommand(
subcommand =>
subcommand
.setName("check")
.setDescription(
"Check a cookie balance."
)
.addUserOption(
option =>
option
.setName("target")
.setDescription(
"The user whose cookies you want to check."
)
)
)
.addSubcommand(
subcommand =>
subcommand
.setName("give")
.setDescription(
"Give some of your cookies to another user."
)
.addUserOption(
option =>
option
.setName("target")
.setDescription(
"The recipient."
)
.setRequired(true)
)
.addIntegerOption(
option =>
option
.setName("amount")
.setDescription(
"How many cookies to give."
)
.setRequired(true)
.setMinValue(1)
)
)
.addSubcommand(
subcommand =>
subcommand
.setName("leaderboard")
.setDescription(
"Show the richest cookie collectors."
)
),
async execute(interaction) {
switch (
interaction.options.getSubcommand()
) {
case "collect":
return collect(interaction);
case "steal":
return steal(interaction);
case "check":
return check(interaction);
case "give":
return give(interaction);
case "leaderboard":
return leaderboard(interaction);
default:
return interaction.reply({
content:
"Unknown subcommand.",
flags:
MessageFlags.Ephemeral
});
}
}
};
/*
* ============================================================
* /cookies collect
* ============================================================
*/
async function collect(interaction) {
const userId =
interaction.user.id;
/*
* Shared collection logic from crumbCollect.js.
*
* This now owns:
*
* - Mongo cooldown
* - collect amount
* - balance mutation
* - profile tracking
*/
const result =
await collectCookies(
interaction,
userId
);
/*
* ========================================================
* COOLDOWN
* ========================================================
*
* Keep the existing /cookies behavior:
* cooldown errors are private/ephemeral.
*/
if (!result.collected) {
return interaction.reply({
content:
`The batch is still cooking. Come back <t:${result.cooldown.next}:R> 🍪`,
flags:
MessageFlags.Ephemeral
});
}
/*
* ========================================================
* SUCCESS EMBED
* ========================================================
*
* Preserve the existing cookies.js embed and random
* response style.
*/
const embed =
new EmbedBuilder()
.setTitle(
"🍪 Fresh Cookies!"
)
.setDescription(
randomCollectResponse(
result.amount,
interaction.user
)
)
.addFields(
{
name:
"Collected",
value:
`**+${result.amount.toLocaleString()} 🍪**`,
inline:
true
},
{
name:
"Your Vault",
value:
`**${result.balance.toLocaleString()} 🍪**`,
inline:
true
},
{
name:
"Next Batch",
value:
`<t:${result.cooldown.next}:R>`,
inline:
false
}
)
.setThumbnail(
interaction.user.displayAvatarURL({
size: 256
})
)
.setTimestamp();
return interaction.reply({
embeds: [embed]
});
}
/*
* ============================================================
* /cookies steal
* ============================================================
*/
async function steal(interaction) {
const user =
interaction.user;
const target =
interaction.options.getUser(
"target",
true
);
/*
* ========================================================
* VALIDATION
* ========================================================
*/
if (
target.id ===
user.id
) {
return interaction.reply({
content:
"C'mon, don't embarrass yourself. You can't steal from yourself.",
flags:
MessageFlags.Ephemeral
});
}
if (
target.bot
) {
return interaction.reply({
content:
"Bots have highly sophisticated Cookie security systems.",
flags:
MessageFlags.Ephemeral
});
}
/*
* ========================================================
* BALANCES
* ========================================================
*/
const [
yourCookies,
theirCookies
] =
await Promise.all([
getCookieBalance(
user.id
),
getCookieBalance(
target.id
)
]);
if (
yourCookies <
1
) {
return interaction.reply({
content:
"You need at least **1 Cookie** to attempt a robbery.",
flags:
MessageFlags.Ephemeral
});
}
if (
theirCookies <
1
) {
return interaction.reply({
content:
`Shame on you! ${target} doesn't have any Cookies. D:`,
flags:
MessageFlags.Ephemeral
});
}
/*
* ========================================================
* COOLDOWN
* ========================================================
*/
const cooldown =
await claimCooldown(
user.id,
interaction.guildId,
"steal",
STEAL_COOLDOWN
);
if (
!cooldown.claimed
) {
return interaction.reply({
content:
`Lay low for a while. You can steal again <t:${cooldown.next}:R>.`,
flags:
MessageFlags.Ephemeral
});
}
/*
* ========================================================
* DYNAMIC SUCCESS CHANCE
* ========================================================
*/
const successChance =
getStealChance(
yourCookies,
theirCookies
);
const success =
Math.random() <
successChance;
/*
* ========================================================
* SUCCESS
* ========================================================
*/
if (success) {
const {
amount
} =
getStealAmount(
theirCookies
);
try {
await transferCookies(
target.id,
user.id,
amount
);
}
catch (error) {
if (
error.message ===
"INSUFFICIENT_COOKIES"
) {
return interaction.reply({
content:
`${target}'s Cookie balance changed before you could finish the robbery.`,
flags:
MessageFlags.Ephemeral
});
}
throw error;
}
/*
* Thief stats.
*/
await trackProfileEvent(
interaction,
user.id,
{
type:
"steal_success",
amount,
targetUserId:
target.id
}
);
await trackProfileEvent(
interaction,
user.id,
{
type:
"cookie_gain",
amount
}
);
/*
* Victim stats.
*/
await trackProfileEvent(
interaction,
target.id,
{
type:
"stolen_from_me",
amount,
thiefUserId:
user.id
}
);
await trackProfileEvent(
interaction,
target.id,
{
type:
"cookie_loss",
amount
}
);
const [
yourBalanceAfter,
targetBalanceAfter
] =
await Promise.all([
getCookieBalance(
user.id
),
getCookieBalance(
target.id
)
]);
const actualPercentage =
(
amount /
theirCookies
)
*
100;
const embed =
new EmbedBuilder()
.setTitle(
"🥷 Robbery Successful!"
)
.setDescription(
randomStealSuccessResponse(
amount,
target
)
)
.addFields(
{
name:
"Cookies Stolen",
value:
`**${amount.toLocaleString()} 🍪**`,
inline:
true
},
{
name:
"Vault Taken",
value:
`**${actualPercentage.toFixed(1)}%**`,
inline:
true
},
{
name:
"Success Chance",
value:
`**${formatPercentage(successChance)}**`,
inline:
true
},
{
name:
"Your Vault",
value:
`**${yourBalanceAfter.toLocaleString()} 🍪**`,
inline:
true
},
{
name:
`${target.username}'s Vault`,
value:
`**${targetBalanceAfter.toLocaleString()} 🍪**`,
inline:
true
},
{
name:
"Next Robbery",
value:
`<t:${cooldown.next}:R>`,
inline:
false
}
)
.setThumbnail(
target.displayAvatarURL({
size: 256
})
)
.setTimestamp();
return interaction.reply({
embeds: [embed]
});
}
/*
* ========================================================
* FAILURE
* ========================================================
*/
const penaltyPercentage =
randomFloat(
0.01,
0.08
);
const penalty =
Math.max(
1,
Math.min(
yourCookies,
Math.floor(
yourCookies *
penaltyPercentage
)
)
);
try {
await transferCookies(
user.id,
target.id,
penalty
);
}
catch (error) {
if (
error.message ===
"INSUFFICIENT_COOKIES"
) {
return interaction.reply({
content:
"Your Cookie balance changed before the robbery could finish.",
flags:
MessageFlags.Ephemeral
});
}
throw error;
}
/*
* Profile tracking.
*/
await trackProfileEvent(
interaction,
user.id,
{
type:
"cookie_loss",
amount:
penalty
}
);
await trackProfileEvent(
interaction,
target.id,
{
type:
"cookie_gain",
amount:
penalty
}
);
const [
yourBalanceAfter,
targetBalanceAfter
] =
await Promise.all([
getCookieBalance(
user.id
),
getCookieBalance(
target.id
)
]);
const actualPenaltyPercentage =
(
penalty /
yourCookies
)
*
100;
const embed =
new EmbedBuilder()
.setTitle(
"🚨 Robbery Failed!"
)
.setDescription(
randomStealFailResponse(
penalty,
target
)
)
.addFields(
{
name:
"Fine",
value:
`**${penalty.toLocaleString()} 🍪**`,
inline:
true
},
{
name:
"Vault Lost",
value:
`**${actualPenaltyPercentage.toFixed(1)}%**`,
inline:
true
},
{
name:
"Success Chance",
value:
`**${formatPercentage(successChance)}**`,
inline:
true
},
{
name:
"Your Vault",
value:
`**${yourBalanceAfter.toLocaleString()} 🍪**`,
inline:
true
},
{
name:
`${target.username}'s Vault`,
value:
`**${targetBalanceAfter.toLocaleString()} 🍪**`,
inline:
true
},
{
name:
"Next Robbery",
value:
`<t:${cooldown.next}:R>`,
inline:
false
}
)
.setThumbnail(
target.displayAvatarURL({
size: 256
})
)
.setTimestamp();
return interaction.reply({
embeds: [embed]
});
}
/*
* ============================================================
* /cookies check
* ============================================================
*/
async function check(interaction) {
const target =
interaction.options.getUser(
"target"
)
??
interaction.user;
const balance =
await getCookieBalance(
target.id
);
const embed =
new EmbedBuilder()
.setTitle(
"🍪 Cookie Vault"
)
.setDescription(
`${target} has **${balance.toLocaleString()} Cookie${balance === 1 ? "" : "s"}**.`
)
.addFields({
name:
"Vault Balance",
value:
`**${balance.toLocaleString()} 🍪**`
})
.setThumbnail(
target.displayAvatarURL({
size: 256
})
)
.setTimestamp();
return interaction.reply({
embeds: [embed]
});
}
/*
* ============================================================
* /cookies give
* ============================================================
*/
async function give(interaction) {
const user =
interaction.user;
const target =
interaction.options.getUser(
"target",
true
);
const amount =
interaction.options.getInteger(
"amount",
true
);
if (
target.id ===
user.id
) {
return interaction.reply({
content:
"You cannot give Cookies to yourself.",
flags:
MessageFlags.Ephemeral
});
}
if (
target.bot
) {
return interaction.reply({
content:
"Bots don't need Cookies. Allegedly.",
flags:
MessageFlags.Ephemeral
});
}
try {
await transferCookies(
user.id,
target.id,
amount
);
}
catch (error) {
if (
error.message ===
"INSUFFICIENT_COOKIES"
) {
return interaction.reply({
content:
"We don't run a charity here. You don't have enough Cookies!",
flags:
MessageFlags.Ephemeral
});
}
throw error;
}
/*
* Sender lifetime giving.
*/
await trackProfileEvent(
interaction,
user.id,
{
type:
"cookies_given",
amount
}
);
/*
* Recipient lifetime gain.
*/
await trackProfileEvent(
interaction,
target.id,
{
type:
"cookie_gain",
amount
}
);
const [
senderBalance,
recipientBalance
] =
await Promise.all([
getCookieBalance(
user.id
),
getCookieBalance(
target.id
)
]);
const embed =
new EmbedBuilder()
.setTitle(
"🍪 Cookies Shared!"
)
.setDescription(
`${user} shared **${amount.toLocaleString()} Cookie${amount === 1 ? "" : "s"}** with ${target}.`
)
.addFields(
{
name:
"Transferred",
value:
`**${amount.toLocaleString()} 🍪**`,
inline:
true
},
{
name:
"Your Vault",
value:
`**${senderBalance.toLocaleString()} 🍪**`,
inline:
true
},
{
name:
`${target.username}'s Vault`,
value:
`**${recipientBalance.toLocaleString()} 🍪**`,
inline:
true
}
)
.setThumbnail(
target.displayAvatarURL({
size: 256
})
)
.setTimestamp();
return interaction.reply({
embeds: [embed]
});
}
/*
* ============================================================
* /cookies leaderboard
* ============================================================
*/
async function leaderboard(interaction) {
const {
cookies
} =
getCollections();
const results =
await cookies
.find({
cookies: {
$gt: 0
},
userId: {
$ne:
interaction.client.user.id
}
})
.sort({
cookies: -1
})
.limit(10)
.toArray();
const description =
results.length
? results
.map(
(
record,
index
) => {
const medal =
getLeaderboardMedal(
index
);
return (
`${medal} <@${record.userId}> — ` +
`**${record.cookies.toLocaleString()}** 🍪`
);
}
)
.join("\n")
: "Nobody has collected any Cookies yet.";
const embed =
new EmbedBuilder()
.setTitle(
"🍪 Cookie Leaderboard"
)
.setDescription(
description
)
.setTimestamp();
if (
interaction.guild
?.iconURL()
) {
embed.setThumbnail(
interaction.guild.iconURL({
size: 256
})
);
}
return interaction.reply({
embeds: [embed]
});
}
/*
* ============================================================
* COLLECT RESPONSES
* ============================================================
*/
function randomCollectResponse(
amount,
user
) {
const cookies =
`**${amount.toLocaleString()} Cookie${amount === 1 ? "" : "s"}**`;
const responses = [
`${user}, I made you ${cookies} 🍪`,
`${user}, here's your ${cookies} 🍪 I ate the rest, sorry >.<`,
`Only ${cookies} 🍪 for you today ${user}... Abra stole the rest :c`,
`${user}, would you like a glass of milk with your ${cookies}? 🥛`,
`${cookies} 🍪 Careful, ${user}! They're still warm :3`,
`${cookies} 🍪 Best batch yet!`,
`${user}, these ${cookies} I bequeath unto thine own self upon this day.`,
`${user}, your ${cookies} are fresh out of the oven. Try not to burn your tongue!`,
`${user}, I bribed the Cookie Monster to spare these ${cookies} just for you.`,
`${user}, Santa came early! You got ${cookies}. Milk not included.`,
`${user}, your ${cookies} are artisanal, gluten-free, and slightly cursed.`,
`A wild baker appeared! They hand ${user} ${cookies} 🍪`,
`${user}, Abra said these ${cookies} are "totally not stolen"... 🤔`,
`${user}, here's your ${cookies} — watch out, they may be booby-trapped.`
];
return randomItem(
responses
);
}
/*
* ============================================================
* STEAL SUCCESS RESPONSES
* ============================================================
*/
function randomStealSuccessResponse(
amount,
target
) {
const cookies =
`**${amount.toLocaleString()} Cookie${amount === 1 ? "" : "s"}**`;
const responses = [
`You've successfully stolen ${cookies} from ${target}!`,
`Ninja mode activated. You snatched ${cookies} from ${target} without leaving a crumb behind.`,
`Abra taught you well — ${cookies} from ${target} are now yours!`,
`Like a phantom in the night, you took ${cookies} from ${target}.`,
`You just pulled the sweetest heist: ${cookies} from ${target}!`,
`Mission complete. ${target} is ${cookies} poorer.`,
`You distracted ${target} with a dance and swiped ${cookies}.`,
`Clean getaway! ${target} won't notice the missing ${cookies} until it's too late.`,
`The Cookie Vault has been breached. ${cookies} taken from ${target}.`,
`You emerged from the shadows carrying ${cookies} from ${target}.`
];
return randomItem(
responses
);
}
/*
* ============================================================
* STEAL FAILURE RESPONSES
* ============================================================
*/
function randomStealFailResponse(
amount,
target
) {
const cookies =
`**${amount.toLocaleString()} Cookie${amount === 1 ? "" : "s"}**`;
const responses = [
`You were caught red-handed by ${target} and lost ${cookies} as a penalty!`,
`Fail! ${target} caught you mid-bite — you dropped ${cookies}.`,
`Not your best moment... You lost ${cookies} to ${target} in the struggle.`,
`Stealth level: 0. You handed ${cookies} to ${target} as an apology.`,
`The Cookie jar was booby-trapped! You gave ${cookies} to ${target} while running away.`,
`Ouch. The guard dog barked, and now ${target} has your ${cookies}.`,
`You tripped over your own shoelace — ${target} picked up ${cookies}.`,
`Caught by the Cookie Police! ${cookies} confiscated and given to ${target}.`,
`Your pockets had holes... ${target} found the ${cookies} you dropped.`
];
return randomItem(
responses
);
}
/*
* ============================================================
* STEAL CHANCE
* ============================================================
*/
function getStealChance(
attackerCookies,
targetCookies
) {
const MIN_CHANCE = 0.15;
const MAX_CHANCE = 0.70;
const BASE_CHANCE = 0.50;
const targetPenalty =
Math.log10(
targetCookies +
1
)
*
0.06;
const wealthRatio =
attackerCookies /
Math.max(
targetCookies,
1
);
const povertyBonus =
wealthRatio <
1
? (
1 -
wealthRatio
)
*
0.18
: 0;
const bullyPenalty =
wealthRatio >
1
? Math.min(
Math.log10(
wealthRatio +
1
)
*
0.12,
0.20
)
: 0;
const chance =
BASE_CHANCE
-
targetPenalty
+
povertyBonus
-
bullyPenalty;
return Math.max(
MIN_CHANCE,
Math.min(
MAX_CHANCE,
chance
)
);
}
/*
* ============================================================
* STEAL AMOUNT
* ============================================================
*/
function getStealAmount(
targetCookies
) {
const MAX_STEAL_AMOUNT =
5000;
const percentage =
getStealPercentage();
const calculatedAmount =
Math.floor(
targetCookies *
percentage
);
const amount =
Math.max(
1,
Math.min(
calculatedAmount,
targetCookies,
MAX_STEAL_AMOUNT
)
);
return {
amount,
percentage
};
}
/*
* ============================================================
* STEAL PERCENTAGE
* ============================================================
*/
function getStealPercentage() {
const roll =
Math.random();
if (
roll <
0.50
) {
return randomFloat(
0.01,
0.03
);
}
if (
roll <
0.80
) {
return randomFloat(
0.03,
0.05
);
}
if (
roll <
0.95
) {
return randomFloat(
0.05,
0.08
);
}
return randomFloat(
0.08,
0.10
);
}
/*
* ============================================================
* LEADERBOARD MEDALS
* ============================================================
*/
function getLeaderboardMedal(
index
) {
switch (index) {
case 0:
return "🥇";
case 1:
return "🥈";
case 2:
return "🥉";
default:
return `**${index + 1}.**`;
}
}