added base profile update
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user