added runtiback and allin

This commit is contained in:
DeSqBlocki
2026-08-22 19:52:47 +02:00
parent 1fd73538fa
commit 81ef97370c
14 changed files with 1463 additions and 444 deletions
+349 -121
View File
@@ -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:<gameId>:<userId>
*/
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
};