From 81ef97370ca54908bf956e34cb0c9a26762bf740 Mon Sep 17 00:00:00 2001 From: DeSqBlocki Date: Sat, 22 Aug 2026 19:52:47 +0200 Subject: [PATCH] added runtiback and allin --- buttons/crumbSaucer/blackjack.js | 4 +- buttons/crumbSaucer/blackjackAllIn.js | 19 + buttons/crumbSaucer/blackjackDouble.js | 4 +- buttons/crumbSaucer/blackjackHit.js | 4 +- buttons/crumbSaucer/blackjackRunItBack.js | 19 + buttons/crumbSaucer/blackjackStand.js | 4 +- buttons/crumbSaucer/rouletteAllIn.js | 19 + buttons/crumbSaucer/rouletteRunItBack.js | 19 + buttons/crumbSaucer/slotsRunItBack.js | 0 commands/applications/cookies.js | 2 +- utils/cookieHeist.js | 77 ++- utils/crumbBlackjack.js | 795 ++++++++++++++++------ utils/crumbRoulette.js | 471 +++++++++++-- utils/crumbSaucer.js | 470 +++++++++---- 14 files changed, 1463 insertions(+), 444 deletions(-) create mode 100644 buttons/crumbSaucer/blackjackAllIn.js create mode 100644 buttons/crumbSaucer/blackjackRunItBack.js create mode 100644 buttons/crumbSaucer/rouletteAllIn.js create mode 100644 buttons/crumbSaucer/rouletteRunItBack.js create mode 100644 buttons/crumbSaucer/slotsRunItBack.js diff --git a/buttons/crumbSaucer/blackjack.js b/buttons/crumbSaucer/blackjack.js index f49532c..0d7d28e 100644 --- a/buttons/crumbSaucer/blackjack.js +++ b/buttons/crumbSaucer/blackjack.js @@ -9,7 +9,9 @@ module.exports = { name: "crumbSaucerBlackjack", - async execute(interaction) { + async execute( + interaction + ) { return showBlackjack( interaction ); diff --git a/buttons/crumbSaucer/blackjackAllIn.js b/buttons/crumbSaucer/blackjackAllIn.js new file mode 100644 index 0000000..f53c714 --- /dev/null +++ b/buttons/crumbSaucer/blackjackAllIn.js @@ -0,0 +1,19 @@ +const { + startBlackjackAllIn +} = require( + "../../utils/crumbBlackjack" +); + + +module.exports = { + name: + "crumbBlackjackAllIn", + + async execute( + interaction + ) { + return startBlackjackAllIn( + interaction + ); + } +}; \ No newline at end of file diff --git a/buttons/crumbSaucer/blackjackDouble.js b/buttons/crumbSaucer/blackjackDouble.js index 7e978b0..ade8135 100644 --- a/buttons/crumbSaucer/blackjackDouble.js +++ b/buttons/crumbSaucer/blackjackDouble.js @@ -9,7 +9,9 @@ module.exports = { name: "crumbBlackjackDouble", - async execute(interaction) { + async execute( + interaction + ) { return doubleBlackjack( interaction ); diff --git a/buttons/crumbSaucer/blackjackHit.js b/buttons/crumbSaucer/blackjackHit.js index 0cb58d3..d43e937 100644 --- a/buttons/crumbSaucer/blackjackHit.js +++ b/buttons/crumbSaucer/blackjackHit.js @@ -9,7 +9,9 @@ module.exports = { name: "crumbBlackjackHit", - async execute(interaction) { + async execute( + interaction + ) { return hitBlackjack( interaction ); diff --git a/buttons/crumbSaucer/blackjackRunItBack.js b/buttons/crumbSaucer/blackjackRunItBack.js new file mode 100644 index 0000000..2b05c8d --- /dev/null +++ b/buttons/crumbSaucer/blackjackRunItBack.js @@ -0,0 +1,19 @@ +const { + runBlackjackBack +} = require( + "../../utils/crumbBlackjack" +); + + +module.exports = { + name: + "crumbBlackjackRunItBack", + + async execute( + interaction + ) { + return runBlackjackBack( + interaction + ); + } +}; \ No newline at end of file diff --git a/buttons/crumbSaucer/blackjackStand.js b/buttons/crumbSaucer/blackjackStand.js index 80c6eb3..a189fd1 100644 --- a/buttons/crumbSaucer/blackjackStand.js +++ b/buttons/crumbSaucer/blackjackStand.js @@ -9,7 +9,9 @@ module.exports = { name: "crumbBlackjackStand", - async execute(interaction) { + async execute( + interaction + ) { return standBlackjack( interaction ); diff --git a/buttons/crumbSaucer/rouletteAllIn.js b/buttons/crumbSaucer/rouletteAllIn.js new file mode 100644 index 0000000..dcb2125 --- /dev/null +++ b/buttons/crumbSaucer/rouletteAllIn.js @@ -0,0 +1,19 @@ +const { + startRouletteAllIn +} = require( + "../../utils/crumbRoulette" +); + + +module.exports = { + name: + "crumbRouletteAllIn", + + async execute( + interaction + ) { + return startRouletteAllIn( + interaction + ); + } +}; \ No newline at end of file diff --git a/buttons/crumbSaucer/rouletteRunItBack.js b/buttons/crumbSaucer/rouletteRunItBack.js new file mode 100644 index 0000000..b25fc03 --- /dev/null +++ b/buttons/crumbSaucer/rouletteRunItBack.js @@ -0,0 +1,19 @@ +const { + runRouletteBack +} = require( + "../../utils/crumbRoulette" +); + + +module.exports = { + name: + "crumbRouletteRunItBack", + + async execute( + interaction + ) { + return runRouletteBack( + interaction + ); + } +}; \ No newline at end of file diff --git a/buttons/crumbSaucer/slotsRunItBack.js b/buttons/crumbSaucer/slotsRunItBack.js new file mode 100644 index 0000000..e69de29 diff --git a/commands/applications/cookies.js b/commands/applications/cookies.js index 19ee780..267db82 100644 --- a/commands/applications/cookies.js +++ b/commands/applications/cookies.js @@ -193,7 +193,7 @@ async function collect(interaction) { let amount = randomInteger( 1, - 20 + 200 ); diff --git a/utils/cookieHeist.js b/utils/cookieHeist.js index f533849..3873b6a 100644 --- a/utils/cookieHeist.js +++ b/utils/cookieHeist.js @@ -91,7 +91,7 @@ async function resolveCookieHeist( const footer = interaction.message - .embeds[0] + ?.embeds?.[0] ?.footer ?.text; @@ -101,12 +101,16 @@ async function resolveCookieHeist( ownerId ] = footer?.split(":") - ?? []; + ?? + []; if ( - type !== "cookieHeist" || - ownerId !== interaction.user.id + type !== + "cookieHeist" + || + ownerId !== + interaction.user.id ) { return temporaryEphemeralReply( interaction, @@ -143,16 +147,6 @@ async function resolveCookieHeist( * ======================================================== * SHARED HEIST COOLDOWN * ======================================================== - * - * IMPORTANT: - * - * This is intentionally always "heist". - * - * It does NOT use: - * - * heistSneak - * heistHack - * heistSmash */ const cooldown = @@ -164,7 +158,9 @@ async function resolveCookieHeist( ); - if (!cooldown.claimed) { + if ( + !cooldown.claimed + ) { return temporaryEphemeralReply( interaction, { @@ -178,9 +174,6 @@ async function resolveCookieHeist( /* * Lock this Heist interaction immediately. - * - * Prevents selecting another approach from - * the same message. */ await interaction.update({ @@ -194,6 +187,10 @@ async function resolveCookieHeist( methodName ) ) + .setFooter({ + text: + `cookieHeist:${interaction.user.id}` + }) ], components: [] @@ -203,6 +200,7 @@ async function resolveCookieHeist( const playerId = interaction.user.id; + const vaultId = interaction.client.user.id; @@ -228,8 +226,11 @@ async function resolveCookieHeist( if ( - playerCookies < 1 || - vaultCookies < 1 + playerCookies < + 1 + || + vaultCookies < + 1 ) { return interaction.editReply({ embeds: [ @@ -247,6 +248,10 @@ async function resolveCookieHeist( value: `` }) + .setFooter({ + text: + `cookieHeist:${playerId}` + }) ], components: @@ -349,6 +354,10 @@ async function resolveCookieHeist( value: `` }) + .setFooter({ + text: + `cookieHeist:${playerId}` + }) ], components: @@ -476,7 +485,12 @@ async function resolveCookieHeist( inline: false } - ); + ) + + .setFooter({ + text: + `cookieHeist:${playerId}` + }); return interaction.editReply({ @@ -551,6 +565,10 @@ async function resolveCookieHeist( value: `` }) + .setFooter({ + text: + `cookieHeist:${playerId}` + }) ], components: @@ -678,7 +696,12 @@ async function resolveCookieHeist( inline: false } - ); + ) + + .setFooter({ + text: + `cookieHeist:${playerId}` + }); return interaction.editReply({ @@ -894,7 +917,9 @@ function getFailureText( * ============================================================ */ -function sleep(ms) { +function sleep( + ms +) { return new Promise( resolve => setTimeout( @@ -905,6 +930,12 @@ function sleep(ms) { } +/* + * ============================================================ + * EXPORTS + * ============================================================ + */ + module.exports = { resolveCookieHeist, HEIST_COOLDOWN diff --git a/utils/crumbBlackjack.js b/utils/crumbBlackjack.js index 42ab305..97a30e6 100644 --- a/utils/crumbBlackjack.js +++ b/utils/crumbBlackjack.js @@ -18,6 +18,12 @@ const { } = require("./cookieEconomy"); +/* + * ============================================================ + * CONFIG + * ============================================================ + */ + const GAME_COLLECTION = "items_games"; @@ -60,11 +66,9 @@ async function showBlackjack( } - const balance = - await getCookieBalance( - interaction.user.id - ); - + /* + * Resume an unfinished game. + */ const active = await getActiveBlackjack( @@ -89,6 +93,12 @@ async function showBlackjack( } + const balance = + await getCookieBalance( + interaction.user.id + ); + + const embed = new EmbedBuilder() .setTitle( @@ -101,7 +111,7 @@ async function showBlackjack( "", "**Dealer stands on 17.**", "**Blackjack pays 3:2.**", - "**Push returns your wager.**", + "**Push refunds your wager.**", "", "Choose your wager below." ].join("\n") @@ -140,9 +150,21 @@ async function showBlackjack( async function startBlackjack( interaction, - bet + bet, + allowCustomBet = + false ) { + /* + * Normal wager buttons must use one of our + * predefined values. + * + * All In and Run It Back are allowed to use + * custom wager amounts. + */ + if ( + !allowCustomBet + && !BLACKJACK_BETS.includes( bet ) @@ -157,14 +179,37 @@ async function startBlackjack( } - const owner = - getBlackjackMenuOwner( + /* + * Every wager, predefined or custom, must + * still be a positive safe integer. + */ + + if ( + !Number.isSafeInteger( + bet + ) + || + bet <= + 0 + ) { + return temporaryEphemeralReply( + interaction, + { + content: + "That wager isn't valid." + } + ); + } + + + const ownerId = + getBlackjackSessionOwner( interaction ); if ( - owner !== + ownerId !== interaction.user.id ) { return temporaryEphemeralReply( @@ -189,6 +234,10 @@ async function startBlackjack( ); + /* + * Prevent multiple active hands. + */ + const existing = await getActiveBlackjack( interaction.user.id, @@ -207,14 +256,31 @@ async function startBlackjack( } - const balance = - await getCookieBalance( - interaction.user.id - ); + const playerId = + interaction.user.id; + + + const houseId = + interaction.client.user.id; + + + const [ + playerBalance, + houseBalance + ] = + await Promise.all([ + getCookieBalance( + playerId + ), + + getCookieBalance( + houseId + ) + ]); if ( - balance < + playerBalance < bet ) { return temporaryEphemeralReply( @@ -228,22 +294,17 @@ async function startBlackjack( /* - * The house must be able to cover a natural Blackjack. + * Natural Blackjack returns: * - * Total Blackjack payout is 2.5x stake after - * the player's stake has already moved to the house. + * wager + 3:2 winnings + * + * = 2.5x total payout. + * + * The wager will move into the house before + * resolution, so the house currently needs + * enough to cover the additional 1.5x. */ - const houseId = - interaction.client.user.id; - - - const houseBalance = - await getCookieBalance( - houseId - ); - - if ( houseBalance < Math.ceil( @@ -261,9 +322,13 @@ async function startBlackjack( } + /* + * Take wager. + */ + try { await transferCookies( - interaction.user.id, + playerId, houseId, bet ); @@ -287,6 +352,12 @@ async function startBlackjack( } + /* + * ======================================================== + * REAL 52-CARD DECK + * ======================================================== + */ + const deck = createDeck(); @@ -316,7 +387,7 @@ async function startBlackjack( randomUUID(), userId: - interaction.user.id, + playerId, guildId: interaction.guildId, @@ -352,7 +423,9 @@ async function startBlackjack( /* - * Natural Blackjack checks. + * ======================================================== + * NATURAL BLACKJACK CHECK + * ======================================================== */ const playerBlackjack = @@ -371,11 +444,35 @@ async function startBlackjack( playerBlackjack || dealerBlackjack ) { - return resolveInitialBlackjack( + /* + * Both natural Blackjack = Push. + */ + + if ( + playerBlackjack && + dealerBlackjack + ) { + return finishBlackjack( + interaction, + game, + "Push" + ); + } + + + if (playerBlackjack) { + return finishBlackjack( + interaction, + game, + "Blackjack" + ); + } + + + return finishBlackjack( interaction, game, - playerBlackjack, - dealerBlackjack + "DealerBlackjack" ); } @@ -395,6 +492,175 @@ async function startBlackjack( } +/* + * ============================================================ + * ALL IN + * ============================================================ + */ + +async function startBlackjackAllIn( + interaction +) { + const ownerId = + getBlackjackSessionOwner( + interaction + ); + + + if ( + ownerId !== + interaction.user.id + ) { + return temporaryEphemeralReply( + interaction, + { + content: + "This isn't your Blackjack table." + } + ); + } + + + /* + * Resolve balance at click time. + */ + + const balance = + await getCookieBalance( + interaction.user.id + ); + + + if ( + balance <= + 0 + ) { + return temporaryEphemeralReply( + interaction, + { + content: + "You don't have any Cookies to go All In with." + } + ); + } + + + /* + * true allows the balance to be used even though + * it likely isn't one of BLACKJACK_BETS. + */ + + return startBlackjack( + interaction, + balance, + true + ); +} + + +/* + * ============================================================ + * RUN IT BACK + * ============================================================ + * + * Reuses only the previous BET. + * + * A completely new shuffled deck and hand are created. + */ + +async function runBlackjackBack( + interaction +) { + const footer = + interaction.message + ?.embeds?.[0] + ?.footer + ?.text; + + + const parts = + footer?.split(":") + ?? + []; + + + /* + * Expected: + * + * crumbBlackjackResult:: + */ + + if ( + parts[0] !== + "crumbBlackjackResult" + ) { + return temporaryEphemeralReply( + interaction, + { + content: + "This Blackjack result can no longer be replayed." + } + ); + } + + + const ownerId = + parts[1]; + + + const bet = + Number( + parts[2] + ); + + + if ( + ownerId !== + interaction.user.id + ) { + return temporaryEphemeralReply( + interaction, + { + content: + "This isn't your Blackjack session." + } + ); + } + + + if ( + !Number.isSafeInteger( + bet + ) + || + bet <= + 0 + ) { + return temporaryEphemeralReply( + interaction, + { + content: + "The previous wager is no longer valid." + } + ); + } + + + /* + * Reuse the exact previous wager. + * + * true is required because a previous All In bet + * may not be part of BLACKJACK_BETS. + */ + + return startBlackjack( + interaction, + bet, + true + ); +} + + /* * ============================================================ * HIT @@ -450,11 +716,15 @@ async function hitBlackjack( const deck = - [...game.deck]; + [ + ...game.deck + ]; const playerCards = - [...game.playerCards]; + [ + ...game.playerCards + ]; playerCards.push( @@ -497,44 +767,44 @@ async function hitBlackjack( ); + const updated = { + ...game, + deck, + playerCards + }; + + + /* + * Bust. + */ + if ( score > 21 ) { return finishBlackjack( interaction, - { - ...game, - deck, - playerCards - }, + updated, "Bust" ); } + /* + * Automatically stand on exactly 21. + */ + if ( score === 21 ) { return standBlackjack( interaction, - { - ...game, - deck, - playerCards - } + updated ); } - const updated = { - ...game, - deck, - playerCards - }; - - return interaction.update({ embeds: [ buildGameEmbed( @@ -558,7 +828,8 @@ async function hitBlackjack( async function standBlackjack( interaction, - suppliedGame = null + suppliedGame = + null ) { let game = suppliedGame; @@ -598,13 +869,21 @@ async function standBlackjack( const deck = - [...game.deck]; + [ + ...game.deck + ]; const dealerCards = - [...game.dealerCards]; + [ + ...game.dealerCards + ]; + /* + * Dealer stands on 17. + */ + while ( getHandValue( dealerCards @@ -648,6 +927,7 @@ async function standBlackjack( "DealerBust"; } + else if ( playerScore > dealerScore @@ -656,6 +936,7 @@ async function standBlackjack( "Win"; } + else if ( playerScore < dealerScore @@ -664,6 +945,11 @@ async function standBlackjack( "Loss"; } + + /* + * Same value = Push. + */ + else { result = "Push"; @@ -719,12 +1005,14 @@ async function doubleBlackjack( /* - * Double is only allowed on the initial 2-card hand. + * Double only on initial two cards. */ if ( game.playerCards.length !== 2 + || + game.doubled ) { return temporaryEphemeralReply( interaction, @@ -736,14 +1024,14 @@ async function doubleBlackjack( } - const balance = + const playerBalance = await getCookieBalance( interaction.user.id ); if ( - balance < + playerBalance < game.originalBet ) { return temporaryEphemeralReply( @@ -783,13 +1071,21 @@ async function doubleBlackjack( const deck = - [...game.deck]; + [ + ...game.deck + ]; const playerCards = - [...game.playerCards]; + [ + ...game.playerCards + ]; + /* + * Double receives exactly one additional card. + */ + playerCards.push( deck.pop() ); @@ -859,7 +1155,7 @@ async function doubleBlackjack( /* - * Double always automatically stands. + * Double automatically stands. */ return standBlackjack( @@ -869,47 +1165,6 @@ async function doubleBlackjack( } -/* - * ============================================================ - * NATURAL BLACKJACK - * ============================================================ - */ - -async function resolveInitialBlackjack( - interaction, - game, - playerBlackjack, - dealerBlackjack -) { - if ( - playerBlackjack && - dealerBlackjack - ) { - return finishBlackjack( - interaction, - game, - "Push" - ); - } - - - if (playerBlackjack) { - return finishBlackjack( - interaction, - game, - "Blackjack" - ); - } - - - return finishBlackjack( - interaction, - game, - "DealerBlackjack" - ); -} - - /* * ============================================================ * FINISH GAME @@ -931,55 +1186,65 @@ async function finishBlackjack( switch (result) { /* - * Regular win returns stake + matching winnings. + * Normal win. */ case "Win": case "DealerBust": + payout = game.bet * 2; + break; /* - * Push simply returns the player's stake. + * Push: + * + * Return exactly the wager. */ case "Push": + payout = game.bet; + break; /* * Natural Blackjack: * - * original stake + 3:2 winnings - * - * = 2.5x total returned + * stake + 3:2 winnings. */ case "Blackjack": + payout = Math.floor( game.bet * 2.5 ); + break; /* - * Loss / Bust / Dealer Blackjack: - * house keeps the stake. + * Bust / Loss / Dealer Blackjack. */ default: + payout = 0; } + /* + * Pay player. + */ + if ( payout > 0 @@ -1005,13 +1270,19 @@ async function finishBlackjack( .setTitle( "🃏 Blackjack Error" ) + .setDescription( "The Crumb Saucer vault couldn't cover the payout. Please contact an administrator." ) + + .setFooter({ + text: + `crumbBlackjackResult:${game.userId}:${game.bet}` + }) ], components: - buildReturnButtons() + buildResultButtons() }); } @@ -1024,6 +1295,10 @@ async function finishBlackjack( } + /* + * Mark game finished. + */ + const { db } = @@ -1075,77 +1350,97 @@ async function finishBlackjack( game.bet; -const embed = - new EmbedBuilder() - .setTitle( - getResultTitle( - result + const description = [ + "**Dealer**", + formatCards( + game.dealerCards + ), + `Score: **${getHandValue(game.dealerCards)}**`, + "", + "**You**", + formatCards( + game.playerCards + ), + `Score: **${getHandValue(game.playerCards)}**` + ]; + + + if ( + result === + "Push" + ) { + description.push( + "", + "🤝 **Push — your wager has been refunded.**" + ); + } + + + const embed = + new EmbedBuilder() + .setTitle( + getResultTitle( + result + ) ) - ) - .setDescription( - [ - "**Dealer**", - formatCards( - game.dealerCards - ), - `Score: **${getHandValue(game.dealerCards)}**`, - "", - "**You**", - formatCards( - game.playerCards - ), - `Score: **${getHandValue(game.playerCards)}**` - ].join("\n") - ) + .setDescription( + description.join( + "\n" + ) + ) - .addFields( - { - name: - "Bet", + .addFields( + { + name: + "Bet", - value: - `${game.bet.toLocaleString()} 🍪`, + value: + `${game.bet.toLocaleString()} 🍪`, - inline: - true - }, + inline: + true + }, - { - name: - "Payout", + { + name: + "Payout", - value: - `${payout.toLocaleString()} 🍪`, + value: + `${payout.toLocaleString()} 🍪`, - inline: - true - }, + inline: + true + }, - { - name: - "Profit", + { + name: + "Profit", - value: - `${profit >= 0 ? "+" : ""}${profit.toLocaleString()} 🍪`, + value: + `${profit >= 0 ? "+" : ""}${profit.toLocaleString()} 🍪`, - inline: - true - }, + inline: + true + }, - { - name: - "Your Vault", + { + name: + "Your Vault", - value: - `${playerBalance.toLocaleString()} 🍪` - } - ) + value: + `${playerBalance.toLocaleString()} 🍪` + } + ) - .setFooter({ - text: - `crumbSaucerBlackjack:${game.userId}` - }); + /* + * Owner + previous exact wager. + */ + + .setFooter({ + text: + `crumbBlackjackResult:${game.userId}:${game.bet}` + }); return interaction.update({ @@ -1154,7 +1449,7 @@ const embed = ], components: - buildReturnButtons() + buildResultButtons() }); } @@ -1229,7 +1524,7 @@ function buildGameEmbed( /* * ============================================================ - * BUTTONS + * BET BUTTONS * ============================================================ */ @@ -1243,14 +1538,18 @@ function buildBetButtons() { .setCustomId( `crumbBlackjackBet${bet}` ) + .setLabel( bet.toLocaleString() ) + .setEmoji( "🍪" ) + .setStyle( - bet >= 500 + bet >= + 500 ? ButtonStyle.Primary : ButtonStyle.Secondary ) @@ -1259,6 +1558,20 @@ function buildBetButtons() { new ActionRowBuilder() .addComponents( + new ButtonBuilder() + .setCustomId( + "crumbBlackjackAllIn" + ) + .setLabel( + "All In" + ) + .setEmoji( + "🔥" + ) + .setStyle( + ButtonStyle.Danger + ), + new ButtonBuilder() .setCustomId( "crumbSaucerBack" @@ -1277,6 +1590,12 @@ function buildBetButtons() { } +/* + * ============================================================ + * ACTIVE GAME BUTTONS + * ============================================================ + */ + function buildGameButtons( game ) { @@ -1339,16 +1658,36 @@ function buildGameButtons( } -function buildReturnButtons() { +/* + * ============================================================ + * RESULT BUTTONS + * ============================================================ + */ + +function buildResultButtons() { return [ new ActionRowBuilder() .addComponents( + new ButtonBuilder() + .setCustomId( + "crumbBlackjackRunItBack" + ) + .setLabel( + "Run It Back" + ) + .setEmoji( + "🔁" + ) + .setStyle( + ButtonStyle.Success + ), + new ButtonBuilder() .setCustomId( "crumbSaucerBlackjack" ) .setLabel( - "Play Again" + "Change Bet" ) .setEmoji( "🃏" @@ -1435,7 +1774,8 @@ function shuffleDeck( ) { for ( let i = - deck.length - 1; + deck.length - + 1; i > 0; i-- @@ -1453,11 +1793,10 @@ function shuffleDeck( [ deck[i], deck[j] - ] = - [ - deck[j], - deck[i] - ]; + ] = [ + deck[j], + deck[i] + ]; } } @@ -1468,6 +1807,7 @@ function getHandValue( let total = 0; + let aces = 0; @@ -1480,22 +1820,27 @@ function getHandValue( card.rank ) { case "A": + total += 11; aces++; + break; case "K": case "Q": case "J": + total += 10; + break; default: + total += Number( card.rank @@ -1540,9 +1885,7 @@ function isBlackjack( function formatCard( card ) { - return ( - `**${card.rank}${card.suit}**` - ); + return `**${card.rank}${card.suit}**`; } @@ -1561,7 +1904,7 @@ function formatCards( /* * ============================================================ - * LOOKUPS + * DATABASE LOOKUPS * ============================================================ */ @@ -1598,7 +1941,7 @@ async function getGameFromInteraction( ) { const footer = interaction.message - .embeds[0] + ?.embeds?.[0] ?.footer ?.text; @@ -1635,14 +1978,17 @@ async function getGameFromInteraction( eventType: "Blackjack", - gameId + gameId, + + state: + "Playing" }); } /* * ============================================================ - * MENU OWNERS + * SESSION OWNER * ============================================================ */ @@ -1651,7 +1997,7 @@ function getBlackjackSessionOwner( ) { const footer = interaction.message - .embeds[0] + ?.embeds?.[0] ?.footer ?.text; @@ -1662,11 +2008,6 @@ function getBlackjackSessionOwner( []; - /* - * Main Crumb Saucer - * - * crumbSaucer: - */ if ( parts[0] === "crumbSaucer" @@ -1675,11 +2016,6 @@ function getBlackjackSessionOwner( } - /* - * Blackjack wager / result menu - * - * crumbSaucerBlackjack: - */ if ( parts[0] === "crumbSaucerBlackjack" @@ -1689,10 +2025,11 @@ function getBlackjackSessionOwner( /* - * Active Blackjack hand + * Active hand: * * crumbBlackjack:: */ + if ( parts[0] === "crumbBlackjack" @@ -1701,37 +2038,21 @@ function getBlackjackSessionOwner( } - return null; -} - -function getBlackjackMenuOwner( - interaction -) { - const footer = - interaction.message - .embeds[0] - ?.footer - ?.text; - - - const [ - type, - userId - ] = - footer?.split(":") - ?? - []; - + /* + * Finished result: + * + * crumbBlackjackResult:: + */ if ( - type !== - "crumbSaucerBlackjack" + parts[0] === + "crumbBlackjackResult" ) { - return null; + return parts[1]; } - return userId; + return null; } @@ -1748,33 +2069,57 @@ function getResultTitle( result ) { case "Blackjack": + return "🃏 BLACKJACK!"; + case "Win": + return "🎉 You Win!"; + case "DealerBust": + return "💥 Dealer Bust!"; + case "Push": - return "🤝 Push"; + + return "🤝 Push — Wager Refunded"; + case "Bust": + return "💥 Bust!"; + case "DealerBlackjack": + return "🃏 Dealer Blackjack"; + default: + return "🃏 Dealer Wins"; } } +/* + * ============================================================ + * EXPORTS + * ============================================================ + */ + module.exports = { showBlackjack, + startBlackjack, + startBlackjackAllIn, + hitBlackjack, standBlackjack, - doubleBlackjack + doubleBlackjack, + + runBlackjackBack }; \ No newline at end of file diff --git a/utils/crumbRoulette.js b/utils/crumbRoulette.js index aaec667..327b37c 100644 --- a/utils/crumbRoulette.js +++ b/utils/crumbRoulette.js @@ -29,62 +29,33 @@ const ROULETTE_BETS = [ const ROULETTE_CHOICES = { Red: { - name: - "Red", - - emoji: - "🔴", - - multiplier: - 2 + name: "Red", + emoji: "🔴", + multiplier: 2 }, - Black: { - name: - "Black", - - emoji: - "⚫", - - multiplier: - 2 + name: "Black", + emoji: "⚫", + multiplier: 2 }, - Odd: { - name: - "Odd", - - emoji: - "🔢", - - multiplier: - 2 + name: "Odd", + emoji: "🔢", + multiplier: 2 }, - Even: { - name: - "Even", - - emoji: - "2️⃣", - - multiplier: - 2 + name: "Even", + emoji: "2️⃣", + multiplier: 2 }, - Zero: { - name: - "Zero", - - emoji: - "🟢", - - multiplier: - 36 + name: "Zero", + emoji: "🟢", + multiplier: 36 } }; @@ -156,19 +127,21 @@ async function showRoulette( .setTitle( "🎡 Crumb Saucer Roulette" ) + .setDescription( [ "European Roulette — **0 to 36**.", "", "Choose your wager.", "", - "🔴 **Red** — ×2", - "⚫ **Black** — ×2", - "🔢 **Odd** — ×2", - "2️⃣ **Even** — ×2", - "🟢 **Zero** — ×36" + "🔴 **Red** — x2", + "⚫ **Black** — x2", + "🔢 **Odd** — x2", + "2️⃣ **Even** — x2", + "🟢 **Zero** — x36" ].join("\n") ) + .addFields({ name: "Your Vault", @@ -176,6 +149,7 @@ async function showRoulette( value: `**${balance.toLocaleString()} 🍪**` }) + .setFooter({ text: `crumbRouletteMenu:${interaction.user.id}` @@ -195,7 +169,7 @@ async function showRoulette( /* * ============================================================ - * CHOOSE WAGER + * CHOOSE NORMAL WAGER * ============================================================ */ @@ -238,6 +212,114 @@ async function chooseRouletteBet( } + return showRouletteChoices( + interaction, + bet + ); +} + + +/* + * ============================================================ + * ALL IN + * ============================================================ + */ + +async function startRouletteAllIn( + interaction +) { + const ownerId = + getRouletteSessionOwner( + interaction + ); + + + if ( + ownerId !== + interaction.user.id + ) { + return temporaryEphemeralReply( + interaction, + { + content: + "This isn't your Roulette table." + } + ); + } + + + /* + * Resolve the balance at click time. + */ + + const balance = + await getCookieBalance( + interaction.user.id + ); + + + if ( + balance <= + 0 + ) { + return temporaryEphemeralReply( + interaction, + { + content: + "You don't have any Cookies to go All In with." + } + ); + } + + + /* + * All In only selects the wager. + * + * Nothing is transferred until the player + * chooses Red / Black / Odd / Even / Zero. + */ + + return showRouletteChoices( + interaction, + balance + ); +} + + +/* + * ============================================================ + * SHOW CHOICE MENU + * ============================================================ + * + * Shared by: + * + * - normal wager buttons + * - All In + * - Run It Back + */ + +async function showRouletteChoices( + interaction, + bet +) { + if ( + !Number.isSafeInteger( + bet + ) + || + bet <= + 0 + ) { + return temporaryEphemeralReply( + interaction, + { + content: + "That Roulette wager isn't valid." + } + ); + } + + const balance = await getCookieBalance( interaction.user.id @@ -263,19 +345,29 @@ async function chooseRouletteBet( .setTitle( "🎡 Crumb Saucer Roulette" ) + .setDescription( [ `Wager: **${bet.toLocaleString()} 🍪**`, "", "Where do you want to place your Cookies?", "", - "🔴 **Red** — ×2", - "⚫ **Black** — ×2", - "🔢 **Odd** — ×2", - "2️⃣ **Even** — ×2", - "🟢 **Zero** — ×36" + "🔴 **Red** — x2", + "⚫ **Black** — x2", + "🔢 **Odd** — x2", + "2️⃣ **Even** — x2", + "🟢 **Zero** — x36" ].join("\n") ) + + .addFields({ + name: + "Your Vault", + + value: + `**${balance.toLocaleString()} 🍪**` + }) + .setFooter({ text: `crumbRouletteBet:${interaction.user.id}:${bet}` @@ -293,6 +385,105 @@ async function chooseRouletteBet( } +/* + * ============================================================ + * RUN IT BACK + * ============================================================ + * + * Remembers ONLY the previous wager. + * + * It does NOT remember Red / Black / Odd / Even / Zero. + */ + +async function runRouletteBack( + interaction +) { + const footer = + interaction.message + ?.embeds?.[0] + ?.footer + ?.text; + + + const parts = + footer?.split(":") + ?? + []; + + + /* + * Expected: + * + * crumbRouletteResult:: + */ + + if ( + parts[0] !== + "crumbRouletteResult" + ) { + return temporaryEphemeralReply( + interaction, + { + content: + "This Roulette result can no longer be replayed." + } + ); + } + + + const ownerId = + parts[1]; + + + const bet = + Number( + parts[2] + ); + + + if ( + ownerId !== + interaction.user.id + ) { + return temporaryEphemeralReply( + interaction, + { + content: + "This isn't your Roulette session." + } + ); + } + + + if ( + !Number.isSafeInteger( + bet + ) + || + bet <= + 0 + ) { + return temporaryEphemeralReply( + interaction, + { + content: + "The previous wager is no longer valid." + } + ); + } + + + /* + * Same wager, fresh choice. + */ + + return showRouletteChoices( + interaction, + bet + ); +} + + /* * ============================================================ * PLAY ROULETTE @@ -322,7 +513,7 @@ async function playRoulette( const footer = interaction.message - .embeds[0] + ?.embeds?.[0] ?.footer ?.text; @@ -371,10 +562,22 @@ async function playRoulette( } + /* + * IMPORTANT: + * + * Do NOT require ROULETTE_BETS.includes(bet) here. + * + * All In and Run It Back may legitimately use + * amounts outside the predefined wager buttons. + */ + if ( - !ROULETTE_BETS.includes( + !Number.isSafeInteger( bet ) + || + bet <= + 0 ) { return temporaryEphemeralReply( interaction, @@ -409,6 +612,11 @@ async function playRoulette( ]); + /* + * Balance may have changed while the player + * was choosing their bet type. + */ + if ( playerBalance < bet @@ -424,11 +632,17 @@ async function playRoulette( /* - * Worst payout is Zero at x36. + * Worst payout depends on the selected wager. * - * Once the player's wager is transferred to the house, - * the house must still be able to cover the remaining - * potential payout. + * Red / Black / Odd / Even: + * x2 + * + * Zero: + * x36 + * + * The player's wager is transferred to the house + * before payout, so houseBalance + bet is available + * after accepting the wager. */ const maximumPayout = @@ -452,7 +666,9 @@ async function playRoulette( /* - * Take wager. + * ======================================================== + * TAKE WAGER + * ======================================================== */ try { @@ -545,8 +761,9 @@ async function playRoulette( /* - * The real result is only generated after - * the animation completes. + * ======================================================== + * ACTUAL RESULT + * ======================================================== */ const result = @@ -567,6 +784,12 @@ async function playRoulette( : 0; + /* + * ======================================================== + * PAYOUT + * ======================================================== + */ + if ( payout > 0 @@ -591,9 +814,15 @@ async function playRoulette( .setTitle( "🎡 Roulette Error" ) + .setDescription( "The wheel resolved, but the payout failed. Please contact an administrator." ) + + .setFooter({ + text: + `crumbRouletteResult:${playerId}:${bet}` + }) ], components: @@ -630,6 +859,7 @@ async function playRoulette( ? "🎉 Roulette Winner!" : "🎡 Roulette" ) + .setDescription( [ "## The ball lands on...", @@ -643,6 +873,7 @@ async function playRoulette( : "The house takes this one." ].join("\n") ) + .addFields( { name: @@ -699,9 +930,17 @@ async function playRoulette( true } ) + + /* + * Remember only: + * + * owner + * wager + */ + .setFooter({ text: - `crumbRouletteResult:${interaction.user.id}` + `crumbRouletteResult:${playerId}:${bet}` }); @@ -728,6 +967,7 @@ function didRouletteBetWin( ) { switch (choice) { case "Red": + return ( number !== 0 @@ -739,6 +979,7 @@ function didRouletteBetWin( case "Black": + return ( number !== 0 @@ -750,6 +991,7 @@ function didRouletteBetWin( case "Odd": + return ( number !== 0 @@ -761,6 +1003,7 @@ function didRouletteBetWin( case "Even": + return ( number !== 0 @@ -772,6 +1015,7 @@ function didRouletteBetWin( case "Zero": + return ( number === 0 @@ -779,6 +1023,7 @@ function didRouletteBetWin( default: + return false; } } @@ -800,6 +1045,7 @@ function buildSpinEmbed( .setTitle( "🎡 Crumb Saucer Roulette" ) + .setDescription( [ "## 🎡 Spinning...", @@ -809,6 +1055,7 @@ function buildSpinEmbed( `**${status}**` ].join("\n") ) + .addFields( { name: @@ -861,7 +1108,7 @@ function formatRouletteNumber( /* * ============================================================ - * BUTTON BUILDERS + * WAGER BUTTONS * ============================================================ */ @@ -875,12 +1122,15 @@ function buildBetButtons() { .setCustomId( `crumbRouletteBet${bet}` ) + .setLabel( bet.toLocaleString() ) + .setEmoji( "🍪" ) + .setStyle( bet >= 500 @@ -892,6 +1142,20 @@ function buildBetButtons() { new ActionRowBuilder() .addComponents( + new ButtonBuilder() + .setCustomId( + "crumbRouletteAllIn" + ) + .setLabel( + "All In" + ) + .setEmoji( + "🔥" + ) + .setStyle( + ButtonStyle.Danger + ), + new ButtonBuilder() .setCustomId( "crumbSaucerBack" @@ -910,6 +1174,12 @@ function buildBetButtons() { } +/* + * ============================================================ + * CHOICE BUTTONS + * ============================================================ + */ + function buildChoiceButtons() { return [ new ActionRowBuilder() @@ -999,16 +1269,46 @@ function buildChoiceButtons() { } +/* + * ============================================================ + * RESULT BUTTONS + * ============================================================ + */ + function buildResultButtons() { return [ new ActionRowBuilder() .addComponents( + /* + * Same wager. + * + * Player chooses Red/Black/etc again. + */ + + new ButtonBuilder() + .setCustomId( + "crumbRouletteRunItBack" + ) + .setLabel( + "Run It Back" + ) + .setEmoji( + "🔁" + ) + .setStyle( + ButtonStyle.Success + ), + + /* + * Return to wager selection. + */ + new ButtonBuilder() .setCustomId( "crumbSaucerRoulette" ) .setLabel( - "Play Again" + "Change Bet" ) .setEmoji( "🎡" @@ -1017,6 +1317,10 @@ function buildResultButtons() { ButtonStyle.Primary ), + /* + * Main Crumb Saucer. + */ + new ButtonBuilder() .setCustomId( "crumbSaucerBack" @@ -1046,7 +1350,7 @@ function getRouletteSessionOwner( ) { const footer = interaction.message - .embeds[0] + ?.embeds?.[0] ?.footer ?.text; @@ -1060,6 +1364,7 @@ function getRouletteSessionOwner( /* * Main Saucer */ + if ( parts[0] === "crumbSaucer" @@ -1071,6 +1376,7 @@ function getRouletteSessionOwner( /* * Roulette wager menu */ + if ( parts[0] === "crumbRouletteMenu" @@ -1080,8 +1386,21 @@ function getRouletteSessionOwner( /* - * Roulette result + * Roulette choice menu. */ + + if ( + parts[0] === + "crumbRouletteBet" + ) { + return parts[1]; + } + + + /* + * Roulette result. + */ + if ( parts[0] === "crumbRouletteResult" @@ -1121,8 +1440,20 @@ function sleep( } +/* + * ============================================================ + * EXPORTS + * ============================================================ + */ + module.exports = { showRoulette, + chooseRouletteBet, - playRoulette + startRouletteAllIn, + showRouletteChoices, + + playRoulette, + + runRouletteBack }; \ No newline at end of file diff --git a/utils/crumbSaucer.js b/utils/crumbSaucer.js index f4e2e3d..226925e 100644 --- a/utils/crumbSaucer.js +++ b/utils/crumbSaucer.js @@ -14,7 +14,9 @@ const { /* + * ============================================================ * SLOT CONFIG + * ============================================================ */ const SLOT_BETS = [ @@ -98,6 +100,40 @@ const SLOT_OUTCOMES = [ async function showCrumbSaucer( interaction ) { + /* + * If this is being called from a button, + * verify that the player owns the current + * Crumb Saucer / game session. + * + * This prevents another user from pressing + * "Crumb Saucer" / "Back" on somebody else's + * game message and taking over their session. + */ + + if ( + interaction.isButton() + ) { + const ownerId = + getCrumbSaucerSessionOwner( + interaction + ); + + + if ( + ownerId !== + interaction.user.id + ) { + return temporaryEphemeralReply( + interaction, + { + content: + "This isn't your Crumb Saucer session." + } + ); + } + } + + const balance = await getCookieBalance( interaction.user.id @@ -116,11 +152,8 @@ async function showCrumbSaucer( /* - * Slash command: - * reply normally. - * * Button: - * update existing menu message. + * update existing menu/game message. */ if ( @@ -136,6 +169,11 @@ async function showCrumbSaucer( } + /* + * Slash command: + * create a new Crumb Saucer. + */ + return interaction.reply({ embeds: [ embed @@ -145,51 +183,13 @@ async function showCrumbSaucer( }); } -function getSlotsSessionOwner( - interaction -) { - const footer = - interaction.message - .embeds[0] - ?.footer - ?.text; - - const parts = - footer?.split(":") - ?? - []; - - - /* - * Main Crumb Saucer - */ - if ( - parts[0] === - "crumbSaucer" - ) { - return parts[1]; - } - - - /* - * Slots menu/result - */ - if ( - parts[0] === - "crumbSaucerSlots" - ) { - return parts[1]; - } - - - return null; -} /* * ============================================================ * SLOT MENU * ============================================================ */ + async function showSlots( interaction ) { @@ -224,19 +224,21 @@ async function showSlots( .setTitle( "🎰 Crumb Saucer Slots" ) + .setDescription( [ "Welcome to the Cookie reels.", "", "Choose your wager below.", "", - "🍋 **Triple Lemon** — ×1.5", - "🍒 **Triple Cherry** — ×2", - "🍪 **Triple Cookie** — ×3", - "⭐ **Triple Star** — ×5", - "💎 **Jackpot** — ×10" + "🍋 **Triple Lemon** — x1.5", + "🍒 **Triple Cherry** — x2", + "🍪 **Triple Cookie** — x3", + "⭐ **Triple Star** — x5", + "💎 **Jackpot** — x10" ].join("\n") ) + .addFields({ name: "Your Vault", @@ -244,6 +246,7 @@ async function showSlots( value: `**${balance.toLocaleString()} 🍪**` }) + .setFooter({ text: `crumbSaucerSlots:${interaction.user.id}` @@ -260,6 +263,7 @@ async function showSlots( }); } + /* * ============================================================ * PLAY SLOTS @@ -271,7 +275,7 @@ async function playSlots( bet ) { const ownerId = - getSlotsOwner( + getSlotsSessionOwner( interaction ); @@ -308,6 +312,7 @@ async function playSlots( const playerId = interaction.user.id; + const vaultId = interaction.client.user.id; @@ -388,6 +393,7 @@ async function playSlots( ); } + throw error; } @@ -409,11 +415,6 @@ async function playSlots( }); - /* - * A few slow edits give a slot-machine feel - * without hammering the Discord API. - */ - await sleep( 700 ); @@ -478,13 +479,11 @@ async function playSlots( /* * House pays winnings. - * - * The original stake was already transferred - * to the house, so x2 means a total payout of 2x. */ if ( - payout > 0 + payout > + 0 ) { try { await transferCookies( @@ -509,6 +508,10 @@ async function playSlots( .setDescription( "The reels stopped, but the payout system had a problem. Please contact an administrator." ) + .setFooter({ + text: + `crumbSaucerSlotsResult:${playerId}:${bet}` + }) ], components: @@ -543,6 +546,7 @@ async function playSlots( .setTitle( "🎰 Crumb Saucer Slots" ) + .setDescription( [ buildSlotDisplay( @@ -552,6 +556,7 @@ async function playSlots( `### ${getSlotResultName(outcome)}` ].join("\n") ) + .addFields( { name: @@ -569,7 +574,7 @@ async function playSlots( "Multiplier", value: - `×${outcome.multiplier}`, + `x${outcome.multiplier}`, inline: true @@ -619,9 +624,17 @@ async function playSlots( true } ) + + /* + * Store owner + previous wager. + * + * Run It Back can now replay the exact + * same wager without opening the bet menu. + */ + .setFooter({ text: - `crumbSaucerSlots:${interaction.user.id}` + `crumbSaucerSlotsResult:${playerId}:${bet}` }); @@ -636,6 +649,88 @@ async function playSlots( } +/* + * ============================================================ + * RUN IT BACK + * ============================================================ + */ + +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 + ); +} + + /* * ============================================================ * MAIN MENU EMBED @@ -650,6 +745,7 @@ function buildMainMenuEmbed( .setTitle( "🎰 The Crumb Saucer" ) + .setDescription( [ `Welcome, ${user}!`, @@ -672,6 +768,7 @@ function buildMainMenuEmbed( "*More attractions coming soon... make a lala offering or buy me a white monster*" ].join("\n") ) + .addFields({ name: "Your Vault", @@ -679,6 +776,7 @@ function buildMainMenuEmbed( value: `**${balance.toLocaleString()} 🍪**` }) + .setFooter({ text: `crumbSaucer:${user.id}` @@ -688,7 +786,7 @@ function buildMainMenuEmbed( /* * ============================================================ - * BUTTONS + * MAIN MENU BUTTONS * ============================================================ */ @@ -755,6 +853,13 @@ function buildMainMenuButtons() { ]; } + +/* + * ============================================================ + * SLOT BET BUTTONS + * ============================================================ + */ + function buildSlotBetButtons() { return [ new ActionRowBuilder() @@ -850,16 +955,44 @@ function buildSlotBetButtons() { } +/* + * ============================================================ + * SLOT RESULT BUTTONS + * ============================================================ + */ + function buildResultButtons() { return [ new ActionRowBuilder() .addComponents( + /* + * Exact same wager immediately. + */ + + new ButtonBuilder() + .setCustomId( + "crumbSaucerSlotsRunItBack" + ) + .setLabel( + "Run It Back" + ) + .setEmoji( + "🔁" + ) + .setStyle( + ButtonStyle.Success + ), + + /* + * Return to wager selection. + */ + new ButtonBuilder() .setCustomId( "crumbSaucerSlots" ) .setLabel( - "Play Again" + "Change Bet" ) .setEmoji( "🎰" @@ -868,6 +1001,10 @@ function buildResultButtons() { ButtonStyle.Primary ), + /* + * Main Saucer. + */ + new ButtonBuilder() .setCustomId( "crumbSaucerBack" @@ -920,7 +1057,8 @@ function rollSlotOutcome() { if ( - roll <= 0 + roll <= + 0 ) { return outcome; } @@ -959,10 +1097,6 @@ function generateOutcomeGrid( generateRandomGrid(); - /* - * Winning middle row. - */ - if ( outcome.type === "triple" @@ -978,10 +1112,6 @@ function generateOutcomeGrid( } - /* - * Pair. - */ - if ( outcome.type === "pair" @@ -1002,14 +1132,11 @@ function generateOutcomeGrid( ); } while ( - other === symbol + other === + symbol ); - /* - * Randomize the location of the odd symbol. - */ - const oddPosition = Math.floor( Math.random() * @@ -1034,11 +1161,6 @@ function generateOutcomeGrid( } - /* - * Loss: - * ensure all three center symbols differ. - */ - let line; @@ -1095,6 +1217,7 @@ function buildSpinningEmbed( .setTitle( "🎰 Crumb Saucer Slots" ) + .setDescription( [ buildSlotDisplay( @@ -1104,6 +1227,7 @@ function buildSpinningEmbed( `**${status}**` ].join("\n") ) + .addFields({ name: "Bet", @@ -1134,23 +1258,28 @@ function getSlotResultName( switch ( - outcome.symbol + outcome.symbol ) { case "🍋": return "🍋 Triple Lemon!"; + case "🍒": return "🍒 Triple Cherry!"; + case "🍪": return "🍪 Triple Cookie!"; + case "⭐": return "⭐ Triple Star!"; + case "💎": return "💎 JACKPOT! 💎"; + default: return "Winner!"; } @@ -1159,75 +1288,166 @@ function getSlotResultName( /* * ============================================================ - * OWNER RESOLUTION + * SESSION OWNER RESOLUTION + * ============================================================ + * + * This intentionally understands every Crumb Saucer + * screen that can navigate back to the main menu. + * + * That prevents another player from pressing the + * Back / Crumb Saucer button on somebody else's + * public interaction. + */ + +function getCrumbSaucerSessionOwner( + interaction +) { + const footer = + interaction.message + ?.embeds?.[0] + ?.footer + ?.text; + + + const parts = + footer?.split(":") + ?? + []; + + + switch ( + parts[0] + ) { + /* + * Main menu + */ + case "crumbSaucer": + + /* + * Slots wager menu + */ + case "crumbSaucerSlots": + + /* + * Slots completed result + */ + case "crumbSaucerSlotsResult": + + /* + * Blackjack wager/result menu + */ + case "crumbSaucerBlackjack": + + case "crumbSaucerBlackjackResult": + + /* + * Roulette + */ + case "crumbRouletteMenu": + + case "crumbRouletteBet": + + case "crumbRouletteResult": + + /* + * Heist + */ + case "crumbSaucerHeist": + + case "cookieHeist": + + return parts[1]; + + + /* + * Active Blackjack footer: + * + * crumbBlackjack:: + */ + + case "crumbBlackjack": + + return parts[2]; + + + default: + + return null; + } +} + + +/* + * ============================================================ + * SLOT OWNER RESOLUTION * ============================================================ */ -function getMenuOwner( +function getSlotsSessionOwner( interaction ) { const footer = interaction.message - .embeds[0] + ?.embeds?.[0] ?.footer ?.text; - const [ - type, - userId - ] = - footer - ?.split(":") + const parts = + footer?.split(":") ?? []; + /* + * Main Crumb Saucer. + */ + if ( - type !== + parts[0] === "crumbSaucer" ) { - return null; + return parts[1]; } - return userId; -} - - -function getSlotsOwner( - interaction -) { - const footer = - interaction.message - .embeds[0] - ?.footer - ?.text; - - - const [ - type, - userId - ] = - footer - ?.split(":") - ?? - []; - + /* + * Slots wager menu. + */ if ( - type !== + parts[0] === "crumbSaucerSlots" ) { - return null; + return parts[1]; } - return userId; + /* + * Completed Slots result. + */ + + if ( + parts[0] === + "crumbSaucerSlotsResult" + ) { + return parts[1]; + } + + + return null; } -function sleep(ms) { +/* + * ============================================================ + * SLEEP + * ============================================================ + */ + +function sleep( + ms +) { return new Promise( resolve => setTimeout( @@ -1238,8 +1458,16 @@ function sleep(ms) { } +/* + * ============================================================ + * EXPORTS + * ============================================================ + */ + module.exports = { showCrumbSaucer, + showSlots, - playSlots + playSlots, + runSlotsBack }; \ No newline at end of file