const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js"); const { getCookieBalance, transferCookies, claimCooldown, temporaryEphemeralReply, randomFloat, formatPercentage } = require("./cookieEconomy"); const { trackProfileEvent } = require("./profileSystem"); const HEIST_COOLDOWN = 60 * 60; const HEIST_METHODS = { Sneak: { baseChance: 0.65, minStealPercent: 0.01, maxStealPercent: 0.03, maxStealAmount: 1500, minPenaltyPercent: 0.01, maxPenaltyPercent: 0.04, maxPenaltyAmount: 750 }, Hack: { baseChance: 0.45, minStealPercent: 0.03, maxStealPercent: 0.06, maxStealAmount: 3500, minPenaltyPercent: 0.03, maxPenaltyPercent: 0.07, maxPenaltyAmount: 2000 }, Smash: { baseChance: 0.25, minStealPercent: 0.06, maxStealPercent: 0.12, maxStealAmount: 7500, minPenaltyPercent: 0.06, maxPenaltyPercent: 0.12, maxPenaltyAmount: 5000 } }; async function resolveCookieHeist( interaction, methodName ) { const footer = interaction.message ?.embeds?.[0] ?.footer ?.text; const [ type, ownerId ] = footer?.split(":") ?? []; if ( type !== "cookieHeist" || ownerId !== interaction.user.id ) { return temporaryEphemeralReply( interaction, { content: "This isn't your heist. Open your own from `/crumbsaucer`." } ); } const method = HEIST_METHODS[ methodName ]; if (!method) { return temporaryEphemeralReply( interaction, { content: "That heist approach doesn't exist." } ); } const cooldown = await claimCooldown( interaction.user.id, interaction.guildId, "heist", HEIST_COOLDOWN ); if (!cooldown.claimed) { return temporaryEphemeralReply( interaction, { content: `🏦 The vault is still on high alert.\nYou can attempt another heist .` }, 6000 ); } await interaction.update({ embeds: [ new EmbedBuilder() .setTitle( "🏦 Vault Heist in Progress..." ) .setDescription( getHeistProgressText( methodName ) ) .setFooter({ text: `cookieHeist:${interaction.user.id}` }) ], components: [] }); const playerId = interaction.user.id; const vaultId = interaction.client.user.id; const [ playerCookies, vaultCookies ] = await Promise.all([ getCookieBalance(playerId), getCookieBalance(vaultId) ]); if ( playerCookies < 1 || vaultCookies < 1 ) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setTitle( "🏦 Heist Aborted" ) .setDescription( "There weren't enough Cookies available to resolve the heist." ) .addFields({ name: "Next Heist", value: `` }) .setFooter({ text: `cookieHeist:${playerId}` }) ], components: buildReturnButton() }); } await sleep(1200); const securityPenalty = getVaultSecurityPenalty( vaultCookies ); const successChance = Math.max( 0.10, method.baseChance - securityPenalty ); const success = Math.random() < successChance; if (success) { const rolledPercent = randomFloat( method.minStealPercent, method.maxStealPercent ); const amount = Math.max( 1, Math.min( Math.floor( vaultCookies * rolledPercent ), method.maxStealAmount, vaultCookies ) ); try { await transferCookies( vaultId, playerId, amount ); } catch (error) { if ( error.message === "INSUFFICIENT_COOKIES" ) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setTitle( "🏦 Heist Interrupted" ) .setDescription( "The ShaiBot Vault changed before you could complete the robbery." ) .addFields({ name: "Next Heist", value: `` }) .setFooter({ text: `cookieHeist:${playerId}` }) ], components: buildReturnButton() }); } throw error; } await trackProfileEvent( interaction, playerId, { type: "game_result", game: "heist", result: "win", profit: amount } ); await trackProfileEvent( interaction, playerId, { type: "vault_steal", amount } ); await trackProfileEvent( interaction, playerId, { type: "cookie_gain", amount } ); const [ playerAfter, vaultAfter ] = await Promise.all([ getCookieBalance(playerId), getCookieBalance(vaultId) ]); const actualPercentage = ( amount / vaultCookies ) * 100; const embed = new EmbedBuilder() .setTitle( "💥 Vault Breached!" ) .setDescription( getSuccessText( methodName, interaction.user, amount ) ) .addFields( { name: "Approach", value: methodName, inline: true }, { name: "Success Chance", value: `**${formatPercentage(successChance)}**`, inline: true }, { name: "Vault Taken", value: `**${actualPercentage.toFixed(2)}%**`, inline: true }, { name: "Cookies Stolen", value: `**${amount.toLocaleString()} 🍪**`, inline: true }, { name: "Your Vault", value: `**${playerAfter.toLocaleString()} 🍪**`, inline: true }, { name: "House Vault", value: `**${vaultAfter.toLocaleString()} 🍪**`, inline: true }, { name: "Next Heist", value: ``, inline: false } ) .setFooter({ text: `cookieHeist:${playerId}` }); return interaction.editReply({ embeds: [embed], components: buildReturnButton() }); } const penaltyPercent = randomFloat( method.minPenaltyPercent, method.maxPenaltyPercent ); const penalty = Math.max( 1, Math.min( Math.floor( playerCookies * penaltyPercent ), method.maxPenaltyAmount, playerCookies ) ); try { await transferCookies( playerId, vaultId, penalty ); } catch (error) { if ( error.message === "INSUFFICIENT_COOKIES" ) { return interaction.editReply({ embeds: [ new EmbedBuilder() .setTitle( "🚨 Heist Failed" ) .setDescription( "Your Cookie balance changed before the fine could be collected." ) .addFields({ name: "Next Heist", value: `` }) .setFooter({ text: `cookieHeist:${playerId}` }) ], components: buildReturnButton() }); } throw error; } await trackProfileEvent( interaction, playerId, { type: "game_result", game: "heist", result: "loss", profit: -penalty } ); await trackProfileEvent( interaction, playerId, { type: "cookie_loss", amount: penalty } ); const [ playerAfter, vaultAfter ] = await Promise.all([ getCookieBalance(playerId), getCookieBalance(vaultId) ]); const actualPenaltyPercentage = ( penalty / playerCookies ) * 100; const embed = new EmbedBuilder() .setTitle( "🚨 HEIST FAILED" ) .setDescription( getFailureText( methodName, interaction.user, penalty ) ) .addFields( { name: "Approach", value: methodName, inline: true }, { name: "Success Chance", value: `**${formatPercentage(successChance)}**`, inline: true }, { name: "Fine", value: `**${penalty.toLocaleString()} 🍪**`, inline: true }, { name: "Vault Lost", value: `**${actualPenaltyPercentage.toFixed(1)}%**`, inline: true }, { name: "Your Vault", value: `**${playerAfter.toLocaleString()} 🍪**`, inline: true }, { name: "House Vault", value: `**${vaultAfter.toLocaleString()} 🍪**`, inline: true }, { name: "Next Heist", value: ``, inline: false } ) .setFooter({ text: `cookieHeist:${playerId}` }); return interaction.editReply({ embeds: [embed], components: buildReturnButton() }); } function buildReturnButton() { return [ new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId( "crumbSaucerBack" ) .setLabel( "Back to Crumb Saucer" ) .setEmoji("◀️") .setStyle( ButtonStyle.Secondary ) ) ]; } function getVaultSecurityPenalty( vaultCookies ) { return Math.min( Math.log10( vaultCookies + 1 ) * 0.025, 0.15 ); } function getHeistProgressText( methodName ) { switch (methodName) { case "Sneak": return [ "🥷 You slip through the service entrance...", "", "The Cookie Vault guards haven't noticed you yet." ].join("\n"); case "Hack": return [ "💻 You connect to the vault terminal...", "", "Several extremely suspicious progress bars appear." ].join("\n"); case "Smash": return [ "💥 You charge directly at the vault...", "", "Subtlety has been abandoned." ].join("\n"); default: return "The heist is underway..."; } } function getSuccessText( methodName, user, amount ) { const cookies = `**${amount.toLocaleString()} Cookies**`; switch (methodName) { case "Sneak": return ( `🥷 ${user} slipped through security and quietly escaped with ${cookies}.` ); case "Hack": return ( `💻 ${user} convinced the vault terminal that ${cookies} belonged to them.` ); case "Smash": return ( `💥 ${user} smashed through the Cookie Vault and escaped with ${cookies}!` ); default: return ( `${user} escaped with ${cookies}.` ); } } function getFailureText( methodName, user, penalty ) { const cookies = `**${penalty.toLocaleString()} Cookies**`; switch (methodName) { case "Sneak": return [ `🚨 ${user} stepped directly on the world's loudest Cookie wrapper.`, "", `The ShaiBot Vault collected a ${cookies} fine.` ].join("\n"); case "Hack": return [ `🚨 ${user} entered the password incorrectly one too many times.`, "", `The ShaiBot Vault collected a ${cookies} security fee.` ].join("\n"); case "Smash": return [ `🚨 ${user} discovered that the vault door is significantly stronger than expected.`, "", `Repairs cost ${cookies}. Naturally, you're paying.` ].join("\n"); default: return ( `The heist failed and cost ${user} ${cookies}.` ); } } function sleep(ms) { return new Promise( resolve => setTimeout( resolve, ms ) ); } module.exports = { resolveCookieHeist, HEIST_COOLDOWN };