const { SlashCommandBuilder, EmbedBuilder, MessageFlags } = require("discord.js"); const { getCollections, getCookieBalance, changeCookies, transferCookies, getCooldown, setCooldown, getUnixTime, randomInteger, randomFloat, randomItem, formatPercentage } = require("../../utils/cookieEconomy"); const { trackProfileEvent } = require("../../utils/profileSystem"); const COLLECT_COOLDOWN = 60 * 60; const STEAL_COOLDOWN = 15 * 60; const ABRA_ID = "378135175059603457"; const ABRA_MULTIPLIER = 1.5; 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 Cookies command.", flags: MessageFlags.Ephemeral }); } } }; async function collect(interaction) { const userId = interaction.user.id; const guildId = interaction.guildId; const now = getUnixTime(); const cooldown = await getCooldown( userId, guildId, "collect" ); if ( cooldown > now ) { return interaction.reply({ content: `The batch is still cooking. Come back 🍪`, flags: MessageFlags.Ephemeral }); } let amount = randomInteger( 1, 1000 ); if ( userId === ABRA_ID ) { amount = Math.ceil( amount * ABRA_MULTIPLIER ); } await changeCookies( userId, amount ); await trackProfileEvent( interaction, userId, { type: "cookie_gain", amount } ); const nextCollect = await setCooldown( userId, guildId, "collect", COLLECT_COOLDOWN ); const balance = await getCookieBalance( userId ); const embed = new EmbedBuilder() .setTitle( "🍪 Fresh Batch" ) .setDescription( randomCollectResponse( amount, interaction.user ) ) .addFields( { name: "Collected", value: `**${amount.toLocaleString()} Cookie${amount === 1 ? "" : "s"}**`, inline: true }, { name: "Your Vault", value: `**${balance.toLocaleString()} 🍪**`, inline: true }, { name: "Next Batch", value: ``, inline: false } ) .setThumbnail( interaction.user.displayAvatarURL({ size: 256 }) ); return interaction.reply({ embeds: [ embed ] }); } async function steal(interaction) { const user = interaction.user; const target = interaction.options.getUser( "target", true ); if ( target.id === user.id ) { return interaction.reply({ content: "You can't steal from yourself.", flags: MessageFlags.Ephemeral }); } if ( target.id === interaction.client.user.id ) { return interaction.reply({ content: "ShaiBot's vault requires a proper Vault Heist. Visit `/crumbsaucer`.", flags: MessageFlags.Ephemeral }); } if (target.bot) { return interaction.reply({ content: "Other bots are protected by Cookie Law.", flags: MessageFlags.Ephemeral }); } const now = getUnixTime(); const cooldown = await getCooldown( user.id, interaction.guildId, "steal" ); if ( cooldown > now ) { return interaction.reply({ content: `Lay low for a while. You can steal again .`, flags: MessageFlags.Ephemeral }); } 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: `${target} doesn't have any Cookies.`, flags: MessageFlags.Ephemeral }); } const successChance = getStealChance( yourCookies, theirCookies ); const success = Math.random() < successChance; let embed; 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; } 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 [ yourBalanceAfter, targetBalanceAfter ] = await Promise.all([ getCookieBalance( user.id ), getCookieBalance( target.id ) ]); const actualPercentage = ( amount / theirCookies ) * 100; embed = new EmbedBuilder() .setTitle( "🥷 Cookie 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.displayName}'s Vault`, value: `**${targetBalanceAfter.toLocaleString()} 🍪**`, inline: true } ) .setThumbnail( target.displayAvatarURL({ size: 256 }) ); } else { 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; } 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; embed = new EmbedBuilder() .setTitle( "🚨 Cookie Robbery Failed" ) .setDescription( randomStealFailResponse( penalty, target ) ) .addFields( { name: "Penalty", 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.displayName}'s Vault`, value: `**${targetBalanceAfter.toLocaleString()} 🍪**`, inline: true } ) .setThumbnail( target.displayAvatarURL({ size: 256 }) ); } const nextSteal = await setCooldown( user.id, interaction.guildId, "steal", STEAL_COOLDOWN ); embed.addFields({ name: "Next Robbery", value: ``, inline: false }); return interaction.reply({ embeds: [ embed ] }); } async function check(interaction) { const target = interaction.options.getUser( "target" ) ?? interaction.user; const balance = await getCookieBalance( target.id ); const isHouse = target.id === interaction.client.user.id; const embed = new EmbedBuilder() .setTitle( isHouse ? "🏦 ShaiBot Cookie Vault" : "🍪 Cookie Vault" ) .setDescription( isHouse ? `The house currently holds **${balance.toLocaleString()} Cookies**.` : `${target} currently has **${balance.toLocaleString()} Cookie${balance === 1 ? "" : "s"}**.` ) .setThumbnail( target.displayAvatarURL({ size: 256 }) ); return interaction.reply({ embeds: [ embed ] }); } 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 accept direct Cookie transfers. Visit `/crumbsaucer` if you'd like to donate to the house involuntarily.", flags: MessageFlags.Ephemeral }); } try { await transferCookies( user.id, target.id, amount ); } catch (error) { if ( error.message === "INSUFFICIENT_COOKIES" ) { return interaction.reply({ content: "You don't have enough Cookies.", flags: MessageFlags.Ephemeral }); } throw error; } await trackProfileEvent( interaction, user.id, { type: "cookies_given", amount } ); await trackProfileEvent( interaction, target.id, { type: "cookie_gain", amount } ); const [ yourBalance, theirBalance ] = await Promise.all([ getCookieBalance( user.id ), getCookieBalance( target.id ) ]); const embed = new EmbedBuilder() .setTitle( "🍪 Cookies Shared" ) .setDescription( `${user} gave ${target} **${amount.toLocaleString()} Cookie${amount === 1 ? "" : "s"}**.` ) .addFields( { name: "Your Vault", value: `**${yourBalance.toLocaleString()} 🍪**`, inline: true }, { name: `${target.displayName}'s Vault`, value: `**${theirBalance.toLocaleString()} 🍪**`, inline: true } ) .setThumbnail( target.displayAvatarURL({ size: 256 }) ); return interaction.reply({ embeds: [ embed ] }); } async function leaderboard(interaction) { const { cookies } = getCollections(); const results = await cookies .find({ userId: { $regex: /^\d+$/ }, cookies: { $gt: 0 } }) .sort({ cookies: -1 }) .limit(25) .toArray(); const playerResults = results .filter( record => record.userId !== interaction.client.user.id ) .slice( 0, 10 ); const description = playerResults.length ? playerResults .map( ( record, index ) => ( `${getLeaderboardMedal(index)} ` + `<@${record.userId}> — ` + `**${record.cookies.toLocaleString()}** 🍪` ) ) .join("\n") : "Nobody has collected any Cookies yet."; const houseBalance = await getCookieBalance( interaction.client.user.id ); const embed = new EmbedBuilder() .setTitle( "🍪 Cookie Leaderboard" ) .setDescription( description ) .addFields({ name: "🏦 ShaiBot House Vault", value: `**${houseBalance.toLocaleString()} 🍪**` }) .setTimestamp(); if ( interaction.guild?.iconURL() ) { embed.setThumbnail( interaction.guild.iconURL({ size: 256 }) ); } return interaction.reply({ embeds: [ embed ] }); } 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 ) ); } 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 }; } 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 ); } function randomCollectResponse( amount, user ) { const cookies = `**${amount.toLocaleString()} Cookie${amount === 1 ? "" : "s"}**`; return randomItem([ `${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.`, `${user}, I bribed the Cookie Monster to spare these ${cookies}.`, `A wild baker appeared! They hand ${user} ${cookies} 🍪`, `${user}, Abra said these ${cookies} are "totally not stolen"... 🤔` ]); } function randomStealSuccessResponse( amount, target ) { const cookies = `**${amount.toLocaleString()} Cookie${amount === 1 ? "" : "s"}**`; return randomItem([ `You've successfully stolen ${cookies} from ${target}!`, `Ninja mode activated. You snatched ${cookies} from ${target}.`, `Abra taught you well — ${cookies} from ${target} are now yours!`, `Like a phantom in the night, you took ${cookies} from ${target}.`, `Clean getaway! ${target} won't notice the missing ${cookies} until it's too late.` ]); } function randomStealFailResponse( amount, target ) { const cookies = `**${amount.toLocaleString()} Cookie${amount === 1 ? "" : "s"}**`; return randomItem([ `You were caught red-handed by ${target} and lost ${cookies}!`, `${target} caught you mid-bite — you dropped ${cookies}.`, `You lost ${cookies} to ${target} in the struggle.`, `Stealth level: 0. You handed ${cookies} to ${target} as an apology.`, `Caught by the Cookie Police! ${cookies} were given to ${target}.` ]); } function getLeaderboardMedal( index ) { switch (index) { case 0: return "🥇"; case 1: return "🥈"; case 2: return "🥉"; default: return `**${index + 1}.**`; } }