const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require("discord.js"); const { getNutBalance, transferNuts, temporaryEphemeralReply } = require("./nutEconomy"); const ROULETTE_BETS = [ 10, 50, 100, 500, 1000 ]; const CHOICES = { Red: { name: "Red", emoji: "🔴", multiplier: 2 }, Black: { name: "Black", emoji: "⚫", multiplier: 2 }, Odd: { name: "Odd", emoji: "1️⃣", multiplier: 2 }, Even: { name: "Even", emoji: "2️⃣", multiplier: 2 }, Zero: { name: "Zero", emoji: "🟢", multiplier: 36 } }; const RED_NUMBERS = new Set([ 1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36 ]); /* * ============================================================ * MENU * ============================================================ */ async function showNutRoulette( interaction ) { const ownerId = getRouletteOwner( interaction ); if ( ownerId !== interaction.user.id ) { return temporaryEphemeralReply( interaction, { content: "Das ist nicht deine Nut-Hub-Session." } ); } const balance = await getNutBalance( interaction.user.id ); const embed = new EmbedBuilder() .setTitle( "🎡 Nut Roulette" ) .setDescription( [ "European Roulette — **0 bis 36**.", "", "🔴 **Red** — x2", "⚫ **Black** — x2", "1️⃣ **Odd** — x2", "2️⃣ **Even** — x2", "🟢 **Zero** — x36", "", "Choose your wager." ].join("\n") ) .addFields({ name: "Deine Nüsse", value: `${balance.toLocaleString()} 🥜` }) .setFooter({ text: `nutRouletteMenu:${interaction.user.id}` }); return interaction.update({ embeds: [ embed ], components: buildBetButtons() }); } /* * ============================================================ * WAGER * ============================================================ */ async function chooseNutRouletteBet( interaction, bet ) { const ownerId = getRouletteOwner( interaction ); if ( ownerId !== interaction.user.id ) { return temporaryEphemeralReply( interaction, { content: "Das ist nicht dein Roulette-Tisch." } ); } if ( !ROULETTE_BETS.includes( bet ) ) { return temporaryEphemeralReply( interaction, { content: "Dieser Einsatz ist ungültig." } ); } const balance = await getNutBalance( interaction.user.id ); if ( balance < bet ) { return temporaryEphemeralReply( interaction, { content: `Du brauchst **${bet.toLocaleString()} Nüsse**.` } ); } const embed = new EmbedBuilder() .setTitle( "🎡 Nut Roulette" ) .setDescription( [ `Einsatz: **${bet.toLocaleString()} 🥜**`, "", "Wo sollen deine Nüsse landen?", "", "🔴 Red", "⚫ Black", "1️⃣ Odd", "2️⃣ Even", "🟢 Zero" ].join("\n") ) .setFooter({ text: `nutRouletteBet:${interaction.user.id}:${bet}` }); return interaction.update({ embeds: [ embed ], components: buildChoiceButtons() }); } /* * ============================================================ * PLAY * ============================================================ */ async function playNutRoulette( interaction, choiceKey ) { const choice = CHOICES[ choiceKey ]; if (!choice) { return temporaryEphemeralReply( interaction, { content: "Diese Roulette-Wette existiert nicht." } ); } const footer = interaction.message ?.embeds?.[0] ?.footer ?.text; const parts = footer?.split(":") ?? []; if ( parts[0] !== "nutRouletteBet" ) { return temporaryEphemeralReply( interaction, { content: "Diese Roulette-Wette ist nicht mehr gültig." } ); } const ownerId = parts[1]; const bet = Number( parts[2] ); if ( ownerId !== interaction.user.id ) { return temporaryEphemeralReply( interaction, { content: "Das ist nicht dein Roulette-Tisch." } ); } const playerId = interaction.user.id; const houseId = interaction.client.user.id; const [ playerBalance, houseBalance ] = await Promise.all([ getNutBalance( playerId ), getNutBalance( houseId ) ]); if ( playerBalance < bet ) { return temporaryEphemeralReply( interaction, { content: "Du hast nicht mehr genug Nüsse." } ); } const maxPayout = bet * choice.multiplier; if ( houseBalance + bet < maxPayout ) { return temporaryEphemeralReply( interaction, { content: "Der Nut Vault kann diesen Einsatz nicht absichern." } ); } await transferNuts( playerId, houseId, bet ); /* * Pseudo animation. */ await interaction.update({ embeds: [ buildSpinEmbed( choice, bet, randomRouletteNumber(), "Das Rad beginnt sich zu drehen..." ) ], components: [] }); await delay( 650 ); await interaction.editReply({ embeds: [ buildSpinEmbed( choice, bet, randomRouletteNumber(), "Round and round..." ) ] }); await delay( 650 ); await interaction.editReply({ embeds: [ buildSpinEmbed( choice, bet, randomRouletteNumber(), "Die Kugel wird langsamer..." ) ] }); await delay( 750 ); const result = randomRouletteNumber(); const won = didBetWin( choiceKey, result ); const payout = won ? bet * choice.multiplier : 0; if ( payout > 0 ) { await transferNuts( houseId, playerId, payout ); } const balance = await getNutBalance( playerId ); const profit = payout - bet; const embed = new EmbedBuilder() .setTitle( won ? "🎉 Nut Roulette — WIN!" : "🎡 Nut Roulette" ) .setDescription( [ "## Die Kugel landet auf...", "", `# ${formatRouletteNumber(result)}`, "", `${choice.emoji} Deine Wette: **${choice.name}**`, "", won ? `🎉 **${payout.toLocaleString()} Nüsse gewonnen!**` : "Das Haus gewinnt." ].join("\n") ) .addFields( { name: "Einsatz", value: `${bet.toLocaleString()} 🥜`, inline: true }, { name: "Payout", value: `${payout.toLocaleString()} 🥜`, inline: true }, { name: "Profit", value: `${profit >= 0 ? "+" : ""}${profit.toLocaleString()} 🥜`, inline: true }, { name: "Deine Nüsse", value: `${balance.toLocaleString()} 🥜` } ) .setFooter({ text: `nutRouletteResult:${playerId}` }); return interaction.editReply({ embeds: [ embed ], components: buildResultButtons() }); } /* * ============================================================ * RULES * ============================================================ */ function didBetWin( choice, number ) { if ( number === 0 ) { return choice === "Zero"; } switch (choice) { case "Red": return RED_NUMBERS.has( number ); case "Black": return !RED_NUMBERS.has( number ); case "Odd": return number % 2 === 1; case "Even": return number % 2 === 0; default: return false; } } function formatRouletteNumber( number ) { if ( number === 0 ) { return "🟢 0"; } if ( RED_NUMBERS.has( number ) ) { return `🔴 ${number}`; } return `⚫ ${number}`; } /* * ============================================================ * DISPLAY * ============================================================ */ function buildSpinEmbed( choice, bet, number, text ) { return new EmbedBuilder() .setTitle( "🎡 Nut Roulette" ) .setDescription( [ "## 🎡 Spinning...", "", `### ${formatRouletteNumber(number)}`, "", `**${text}**` ].join("\n") ) .addFields( { name: "Wette", value: `${choice.emoji} ${choice.name}`, inline: true }, { name: "Einsatz", value: `${bet.toLocaleString()} 🥜`, inline: true } ); } /* * ============================================================ * BUTTONS * ============================================================ */ function buildBetButtons() { return [ new ActionRowBuilder() .addComponents( ...ROULETTE_BETS.map( bet => new ButtonBuilder() .setCustomId( `nutRouletteBet${bet}` ) .setLabel( bet.toLocaleString() ) .setEmoji( "🥜" ) .setStyle( bet >= 500 ? ButtonStyle.Primary : ButtonStyle.Secondary ) ) ), new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId( "nutHubBack" ) .setLabel( "Nut Hub" ) .setEmoji( "◀️" ) .setStyle( ButtonStyle.Secondary ) ) ]; } function buildChoiceButtons() { return [ new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId( "nutRouletteRed" ) .setLabel( "Red" ) .setEmoji( "🔴" ) .setStyle( ButtonStyle.Danger ), new ButtonBuilder() .setCustomId( "nutRouletteBlack" ) .setLabel( "Black" ) .setEmoji( "⚫" ) .setStyle( ButtonStyle.Secondary ), new ButtonBuilder() .setCustomId( "nutRouletteOdd" ) .setLabel( "Odd" ) .setEmoji( "1️⃣" ) .setStyle( ButtonStyle.Primary ), new ButtonBuilder() .setCustomId( "nutRouletteEven" ) .setLabel( "Even" ) .setEmoji( "2️⃣" ) .setStyle( ButtonStyle.Primary ), new ButtonBuilder() .setCustomId( "nutRouletteZero" ) .setLabel( "Zero" ) .setEmoji( "🟢" ) .setStyle( ButtonStyle.Success ) ) ]; } function buildResultButtons() { return [ new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId( "nutHubRoulette" ) .setLabel( "Play Again" ) .setEmoji( "🎡" ) .setStyle( ButtonStyle.Primary ), new ButtonBuilder() .setCustomId( "nutHubBack" ) .setLabel( "Nut Hub" ) .setEmoji( "◀️" ) .setStyle( ButtonStyle.Secondary ) ) ]; } /* * ============================================================ * OWNERSHIP * ============================================================ */ function getRouletteOwner( interaction ) { const footer = interaction.message ?.embeds?.[0] ?.footer ?.text; const parts = footer?.split(":") ?? []; if ( parts[0] === "nutHub" ) { return parts[1]; } if ( parts[0] === "nutRouletteMenu" || parts[0] === "nutRouletteResult" ) { return parts[1]; } if ( parts[0] === "nutRouletteBet" ) { return parts[1]; } return null; } function randomRouletteNumber() { return Math.floor( Math.random() * 37 ); } function delay( ms ) { return new Promise( resolve => setTimeout( resolve, ms ) ); } module.exports = { showNutRoulette, chooseNutRouletteBet, playNutRoulette };