const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js"); const { getCookieBalance, transferCookies, temporaryEphemeralReply, randomItem } = require("./cookieEconomy"); const { trackProfileEvent } = require("./profileSystem"); const SLOT_BETS = [10, 50, 100, 500, 1000]; const SLOT_SYMBOLS = [ "🍒", "🍋", "🍪", "⭐", "💎" ]; const SLOT_OUTCOMES = [ { weight: 300, type: "loss", multiplier: 0 }, { weight: 390, type: "pair", multiplier: 0.5 }, { weight: 150, type: "triple", symbol: "🍋", multiplier: 1.5 }, { weight: 80, type: "triple", symbol: "🍒", multiplier: 2 }, { weight: 45, type: "triple", symbol: "🍪", multiplier: 3 }, { weight: 25, type: "triple", symbol: "⭐", multiplier: 5 }, { weight: 10, type: "triple", symbol: "💎", multiplier: 10 } ]; async function showSlots(interaction) { const ownerId = getSlotsSessionOwner(interaction); if (ownerId !== interaction.user.id) { return temporaryEphemeralReply(interaction, { content: "This isn't your Crumb Saucer session." }); } const balance = await getCookieBalance(interaction.user.id); const embed = new EmbedBuilder() .setTitle("🎰 Crumb Saucer Slots") .setDescription([ "Welcome to the Cookie reels.", "", "Choose your wager below.", "", "🍋 **Triple Lemon** — x1.5", "🍒 **Triple Cherry** — x2", "🍪 **Triple Cookie** — x3", "⭐ **Triple Star** — x5", "💎 **Jackpot** — x10" ].join("\n")) .addFields({ name: "Your Vault", value: `**${balance.toLocaleString()} 🍪**` }) .setFooter({ text: `crumbSaucerSlots:${interaction.user.id}` }); return interaction.update({ embeds: [embed], components: buildSlotBetButtons() }); } async function playSlots(interaction, bet) { const ownerId = getSlotsSessionOwner(interaction); if (ownerId !== interaction.user.id) { return temporaryEphemeralReply(interaction, { content: "These aren't your slots. Open your own Crumb Saucer session." }); } if (!SLOT_BETS.includes(bet)) { return temporaryEphemeralReply(interaction, { content: "That wager isn't available." }); } const playerId = interaction.user.id; const vaultId = interaction.client.user.id; const [playerBalance, vaultBalance] = await Promise.all([ getCookieBalance(playerId), getCookieBalance(vaultId) ]); if (playerBalance < bet) { return temporaryEphemeralReply(interaction, { content: `You need **${bet.toLocaleString()} Cookies** for that wager.` }); } const maxPayout = bet * 10; if (vaultBalance + bet < maxPayout) { return temporaryEphemeralReply(interaction, { content: "The Crumb Saucer vault doesn't currently have enough Cookies to cover that wager." }); } try { await transferCookies(playerId, vaultId, bet); } catch (error) { if (error.message === "INSUFFICIENT_COOKIES") { return temporaryEphemeralReply(interaction, { content: "Your Cookie balance changed before the spin could begin." }); } throw error; } await interaction.update({ embeds: [ buildSpinningEmbed( bet, ["❔", "❔", "❔"], "The reels are spinning..." ) ], components: [] }); await sleep(500); const firstPreview = randomItem(SLOT_SYMBOLS); await interaction.editReply({ embeds: [ buildSpinningEmbed( bet, [firstPreview, "❔", "❔"], "The first reel stops..." ) ], components: [] }); await sleep(500); const secondPreview = randomItem(SLOT_SYMBOLS); await interaction.editReply({ embeds: [ buildSpinningEmbed( bet, [firstPreview, secondPreview, "❔"], "One more..." ) ], components: [] }); await sleep(600); const outcome = rollSlotOutcome(); const result = generateOutcomeLine(outcome); const payout = Math.floor(bet * outcome.multiplier); if (payout > 0) { try { await transferCookies(vaultId, playerId, payout); } catch (error) { console.error("CRITICAL: Crumb Saucer Slots payout failed:", error); return interaction.editReply({ embeds: [ new EmbedBuilder() .setTitle("🎰 Crumb Saucer Error") .setDescription( "The reels stopped, but the payout system had a problem. Please contact an administrator." ) .setFooter({ text: `crumbSaucerSlotsResult:${playerId}:${bet}` }) ], components: buildResultButtons() }); } } const [playerAfter, vaultAfter] = await Promise.all([ getCookieBalance(playerId), getCookieBalance(vaultId) ]); const profit = payout - bet; await trackProfileEvent(interaction, playerId, { type: "game_result", game: "slots", 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 = new EmbedBuilder() .setTitle("🎰 Crumb Saucer Slots") .setDescription([ "## " + result.join(" "), "", `### ${getSlotResultName(outcome)}` ].join("\n")) .addFields( { name: "Bet", value: `${bet.toLocaleString()} 🍪`, inline: true }, { name: "Multiplier", value: `x${outcome.multiplier}`, inline: true }, { name: "Payout", value: `${payout.toLocaleString()} 🍪`, inline: true }, { name: "Profit", value: `${profit >= 0 ? "+" : ""}${profit.toLocaleString()} 🍪`, inline: true }, { name: "Your Vault", value: `${playerAfter.toLocaleString()} 🍪`, inline: true }, { name: "House Vault", value: `${vaultAfter.toLocaleString()} 🍪`, inline: true } ) .setFooter({ text: `crumbSaucerSlotsResult:${playerId}:${bet}` }); return interaction.editReply({ embeds: [resultEmbed], components: buildResultButtons() }); } async function runSlotsBack(interaction) { const footer = interaction.message?.embeds?.[0]?.footer?.text; const parts = footer?.split(":") ?? []; if (parts[0] !== "crumbSaucerSlotsResult") { return temporaryEphemeralReply(interaction, { content: "This Slots result can no longer be replayed." }); } const ownerId = parts[1]; const bet = Number(parts[2]); if (ownerId !== interaction.user.id) { return temporaryEphemeralReply(interaction, { content: "These aren't your slots." }); } if (!SLOT_BETS.includes(bet)) { return temporaryEphemeralReply(interaction, { content: "The previous wager is no longer available." }); } return playSlots(interaction, bet); } function buildSlotBetButtons() { return [ new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId("crumbSaucerSlots10").setLabel("10").setEmoji("🍪").setStyle(ButtonStyle.Secondary), new ButtonBuilder().setCustomId("crumbSaucerSlots50").setLabel("50").setEmoji("🍪").setStyle(ButtonStyle.Secondary), new ButtonBuilder().setCustomId("crumbSaucerSlots100").setLabel("100").setEmoji("🍪").setStyle(ButtonStyle.Primary), new ButtonBuilder().setCustomId("crumbSaucerSlots500").setLabel("500").setEmoji("🍪").setStyle(ButtonStyle.Primary), new ButtonBuilder().setCustomId("crumbSaucerSlots1000").setLabel("1,000").setEmoji("🍪").setStyle(ButtonStyle.Danger) ), new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId("crumbSaucerBack").setLabel("Back to Crumb Saucer").setEmoji("◀️").setStyle(ButtonStyle.Secondary) ) ]; } function buildResultButtons() { return [ new ActionRowBuilder().addComponents( new ButtonBuilder().setCustomId("crumbSaucerSlotsRunItBack").setLabel("Run It Back").setEmoji("🔁").setStyle(ButtonStyle.Success), new ButtonBuilder().setCustomId("crumbSaucerSlots").setLabel("Change Bet").setEmoji("🎰").setStyle(ButtonStyle.Primary), new ButtonBuilder().setCustomId("crumbSaucerBack").setLabel("Crumb Saucer").setEmoji("◀️").setStyle(ButtonStyle.Secondary) ) ]; } function rollSlotOutcome() { const totalWeight = SLOT_OUTCOMES.reduce( (total, outcome) => total + outcome.weight, 0 ); let roll = Math.random() * totalWeight; for (const outcome of SLOT_OUTCOMES) { roll -= outcome.weight; if (roll <= 0) return outcome; } return SLOT_OUTCOMES[0]; } function generateRandomLine() { return [ randomItem(SLOT_SYMBOLS), randomItem(SLOT_SYMBOLS), randomItem(SLOT_SYMBOLS) ]; } function generateOutcomeLine(outcome) { if (outcome.type === "triple") { return [ outcome.symbol, outcome.symbol, outcome.symbol ]; } if (outcome.type === "pair") { const symbol = randomItem(SLOT_SYMBOLS); let other; do { other = randomItem(SLOT_SYMBOLS); } while (other === symbol); const result = [symbol, symbol, symbol]; result[Math.floor(Math.random() * 3)] = other; return result; } let result; do { result = generateRandomLine(); } while (new Set(result).size < 3); return result; } function buildSpinningEmbed(bet, symbols, status) { return new EmbedBuilder() .setTitle("🎰 Crumb Saucer Slots") .setDescription([ "## " + symbols.join(" "), "", `**${status}**` ].join("\n")) .addFields({ name: "Bet", value: `**${bet.toLocaleString()} 🍪**` }); } function getSlotResultName(outcome) { if (outcome.type === "loss") return "No luck this time."; if (outcome.type === "pair") return "Pair!"; switch (outcome.symbol) { case "🍋": return "🍋 Triple Lemon!"; case "🍒": return "🍒 Triple Cherry!"; case "🍪": return "🍪 Triple Cookie!"; case "⭐": return "⭐ Triple Star!"; case "💎": return "💎 JACKPOT! 💎"; default: return "Winner!"; } } function getSlotsSessionOwner(interaction) { const footer = interaction.message?.embeds?.[0]?.footer?.text; const parts = footer?.split(":") ?? []; if (parts[0] === "crumbSaucer") return parts[1]; if (parts[0] === "crumbSaucerSlots") return parts[1]; if (parts[0] === "crumbSaucerSlotsResult") return parts[1]; return null; } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } module.exports = { showSlots, playSlots, runSlotsBack };