Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions botconfig/emojis.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"loading": "<:required:893938878380122122>",
"King_Coin": "<:KC_20260417_123851_0000:1494754428572667944>",
"blackjack": "<:blackjack_v2:1499564011404923063>",
"reload": "<:autoplay1:893938933891756073>",
"casino_slots": "🎰",
"casino_cherry": "🍒",
"casino_lemon": "🍋",
Expand Down
56 changes: 28 additions & 28 deletions commands/Cassino/blackjack.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ const { MessageActionRow, MessageButton } = require("discord.js");
const {
emojis,
ee,
normalizeText,
buildCasinoEmbed,
getCasinoResultColor,
attachReplayHandler,
parseBet,
getUserData,
applyGameResult,
Expand Down Expand Up @@ -31,19 +32,18 @@ const BLACKJACK_THUMBNAIL = (() => {
return emojiId ? `https://cdn.discordapp.com/emojis/${emojiId}.png` : null;
})();

const DECK_VALUES = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"];

function createBlackjackEmbed(user, color = ee.color) {
const embed = buildCasinoEmbed(user, color);
if (BLACKJACK_THUMBNAIL) embed.setThumbnail(BLACKJACK_THUMBNAIL);
return embed;
}

const SUITS = ["♠️", "♥️", "♦️", "♣️"];
const DECK_VALUES = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"];

function drawCard() {
const value = DECK_VALUES[Math.floor(Math.random() * DECK_VALUES.length)];
const suit = SUITS[Math.floor(Math.random() * SUITS.length)];
return { value, suit };
const suitIndex = Math.floor(Math.random() * 4);
return { value, suitIndex };
}

function handValue(hand) {
Expand All @@ -70,25 +70,23 @@ function handValue(hand) {
}

function getCardEmoji(card) {
if (naipes[card.value]) {
const suitIndex = SUITS.indexOf(card.suit);
if (suitIndex >= 0 && naipes[card.value][suitIndex]) {
return naipes[card.value][suitIndex];
}
if (naipes[card.value]?.[card.suitIndex]) {
return naipes[card.value][card.suitIndex];
}
return `${card.value}${card.suit}`;

return `${card.value}${naipes.naipes?.[card.suitIndex] || ""}`;
}

function formatHand(hand, hidden = false) {
if (!hidden) return hand.map(card => getCardEmoji(card)).join(" ");
if (!hidden) return hand.map((card) => getCardEmoji(card)).join(" ");
return [getCardEmoji(hand[0]), "<:none:1500968452087742585>"].join(" ");
}

function buildControls(customPrefix, disabled = false) {
return [
new MessageActionRow().addComponents(
new MessageButton().setCustomId(`${customPrefix}:hit`).setLabel("Comprar").setStyle("PRIMARY").setDisabled(disabled),
new MessageButton().setCustomId(`${customPrefix}:stand`).setLabel("Parar").setStyle("SUCCESS").setDisabled(disabled)
new MessageButton().setCustomId(`${customPrefix}:hit`).setLabel("<:buy:1499564014777401434> Comprar").setStyle("SUCCESS").setDisabled(disabled),
new MessageButton().setCustomId(`${customPrefix}:stand`).setLabel("<:stop:1499564013346881596> Parar").setStyle("DANGER").setDisabled(disabled)
)
];
}
Expand All @@ -98,9 +96,9 @@ function createEmbed(user, state, revealDealer = false, finalText = null) {
const dealerTotal = revealDealer ? handValue(state.dealerHand) : handValue([state.dealerHand[0]]);

return createBlackjackEmbed(user, state.color || ee.color)
.setTitle("🃏 Blackjack")
.setDescription(finalText || "Controle sua mão usando os botões abaixo.")
.addField(`Sua mão (${playerTotal})`, formatHand(state.playerHand), true)
.setTitle(`${emojis.blackjack} Blackjack`)
.setDescription(finalText || "Controle sua mao usando os botoes abaixo.")
.addField(`Sua mao (${playerTotal})`, formatHand(state.playerHand), true)
.addField(`Banca (${dealerTotal}${revealDealer ? "" : "+"})`, formatHand(state.dealerHand, !revealDealer), true)
.addField("Aposta atual", formatAmount(state.bet), true);
}
Expand All @@ -126,12 +124,12 @@ function settleGame(state) {
outcome = "win";
reason = "Blackjack natural. Pagamento 3:2.";
} else if (playerTotal > 21) {
reason = "Você estourou 21.";
reason = "Voce estourou 21.";
} else if (dealerTotal > 21 || playerTotal > dealerTotal) {
payout = state.bet * 2;
netChange = payout - state.bet;
outcome = "win";
reason = dealerTotal > 21 ? "A banca estourou." : "Sua mão venceu a banca.";
reason = dealerTotal > 21 ? "A banca estourou." : "Sua mao venceu a banca.";
} else if (playerTotal === dealerTotal) {
payout = state.bet;
netChange = 0;
Expand All @@ -153,20 +151,20 @@ async function resolveBet(message, args, settings, userData) {
{
embeds: [
createBlackjackEmbed(message.author)
.setTitle("🃏 Blackjack")
.setTitle(`${emojis.blackjack} Blackjack`)
.setDescription(
[
"Qual será o valor da aposta?",
`Aposta mínima: ${formatAmount(settings.casinoMinBet)}`,
`Aposta máxima: ${formatAmount(settings.casinoMaxBet)}`,
"Qual sera o valor da aposta?",
`Aposta minima: ${formatAmount(settings.casinoMinBet)}`,
`Aposta maxima: ${formatAmount(settings.casinoMaxBet)}`,
`Seu saldo: ${formatAmount(userData.coins)}`
].join("\n")
)
]
},
(content) => {
const amount = parseBet(content, userData.coins);
if (!amount) return { ok: false, message: "Envie uma aposta válida usando número inteiro ou `all`." };
if (!amount) return { ok: false, message: "Envie uma aposta valida usando numero inteiro ou `all`." };
return { ok: true, value: amount };
}
).then((result) => result.ok ? { ok: true, amount: result.value } : result);
Expand Down Expand Up @@ -253,19 +251,21 @@ module.exports = {
finished = true;
const result = settleGame(state);
const newBalance = await applyGameResult(message.author.id, state.balance, result.netChange);
state.color = result.netChange >= 0 ? ee.color : ee.wrongcolor;
state.color = getCasinoResultColor(result.outcome);

await gameMessage.edit({
embeds: [
createEmbed(
message.author,
state,
true,
`${result.reason}\nPrêmio: ${result.payout > 0 ? formatAmount(result.payout) : `**0** ${emojis.King_Coin}`}\nSaldo atual: ${formatAmount(newBalance)}`
`${result.reason}\nPremio: ${result.payout > 0 ? formatAmount(result.payout) : `**0** ${emojis.King_Coin}`}\nSaldo atual: ${formatAmount(newBalance)}`
)
],
components: buildControls(customPrefix, true)
}).catch(() => null);
endCasinoSession(message.author.id);
attachReplayHandler(client, message, gameMessage, "blackjack", []);

await logCasinoEvent(client, message, {
userId: message.author.id,
Expand Down Expand Up @@ -314,7 +314,7 @@ module.exports = {
finished = true;
await gameMessage.edit({
embeds: [
createEmbed(message.author, state, false, "Tempo esgotado. A mesa foi encerrada sem resultado.").setColor(ee.wrongcolor)
createEmbed(message.author, state, false, "Tempo esgotado. A mesa foi encerrada sem resultado.").setColor(ee.color)
],
components: buildControls(customPrefix, true)
}).catch(() => null);
Expand Down
6 changes: 3 additions & 3 deletions commands/Cassino/cassino.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function buildMenuRows(customPrefix, disabled = false) {
return [
new MessageActionRow().addComponents(
new MessageButton().setCustomId(`${customPrefix}:slots`).setLabel("Slots").setStyle("PRIMARY").setEmoji(emojis.casino_slots).setDisabled(disabled),
new MessageButton().setCustomId(`${customPrefix}:blackjack`).setLabel("Blackjack").setStyle("SUCCESS").setEmoji("🃏").setDisabled(disabled),
new MessageButton().setCustomId(`${customPrefix}:blackjack`).setLabel("Blackjack").setStyle("SUCCESS").setEmoji(emojis.blackjack).setDisabled(disabled),
new MessageButton().setCustomId(`${customPrefix}:minas`).setLabel("Minas").setStyle("DANGER").setEmoji("💣").setDisabled(disabled),
new MessageButton().setCustomId(`${customPrefix}:roleta`).setLabel("Roleta").setStyle("SECONDARY").setEmoji("🎡").setDisabled(disabled)
)
Expand All @@ -37,8 +37,8 @@ module.exports = {
`\`${prefix}slots [aposta]\``,
"Se nao enviar aposta, o bot pergunta o valor e gira os slots.",
"",
`\`${prefix}blackjack [aposta] [seguro|padrao|agressivo]\``,
"A mesa pode perguntar aposta e estilo antes de abrir os botoes da rodada.",
`\`${prefix}blackjack [aposta]\``,
"A mesa pode perguntar a aposta antes de abrir os botoes da rodada.",
"",
`\`${prefix}minas [aposta] [minas]\``,
"Escolha aposta, numero de minas e jogue num tabuleiro 5x5 de botoes.",
Expand Down
7 changes: 7 additions & 0 deletions commands/Cassino/minas.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const {
emojis,
ee,
buildCasinoEmbed,
getCasinoResultColor,
attachReplayHandler,
parseBet,
getUserData,
applyGameResult,
Expand Down Expand Up @@ -312,6 +314,8 @@ module.exports = {
tile,
false
);
endCasinoSession(message.author.id);
attachReplayHandler(client, message, controlMessage, "minas", []);

await logCasinoEvent(client, message, {
userId: message.author.id,
Expand Down Expand Up @@ -385,6 +389,7 @@ module.exports = {
const payout = Math.floor(state.bet * multiplier);
const netChange = payout - state.bet;
const newBalance = await applyGameResult(message.author.id, state.balance, netChange);
state.color = getCasinoResultColor("win");
endCollectors("finished");
await interaction.deferUpdate().catch(() => null);
await closeGame(
Expand All @@ -397,6 +402,8 @@ module.exports = {
null,
false
);
endCasinoSession(message.author.id);
attachReplayHandler(client, message, controlMessage, "minas", []);

await logCasinoEvent(client, message, {
userId: message.author.id,
Expand Down
12 changes: 9 additions & 3 deletions commands/Cassino/roleta.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const {
ee,
normalizeText,
buildCasinoEmbed,
getCasinoResultColor,
attachReplayHandler,
parseBet,
getUserData,
applyGameResult,
Expand Down Expand Up @@ -293,7 +295,8 @@ module.exports = {

const newBalance = await applyGameResult(message.author.id, userData.coins, netChange);

const embed = buildCasinoEmbed(message.author, netChange >= 0 ? ee.color : ee.wrongcolor)
const outcome = netChange > 0 ? "win" : netChange === 0 ? "push" : "loss";
const embed = buildCasinoEmbed(message.author, getCasinoResultColor(outcome))
.setTitle("🎡 Roleta")
.addField("Sua aposta", `\`${preview.label}\``, true)
.addField("Pagamento previsto", preview.payoutText, false)
Expand All @@ -309,7 +312,7 @@ module.exports = {
bet: amount,
payout,
netChange,
outcome: netChange > 0 ? "win" : netChange === 0 ? "push" : "loss",
outcome,
reason: status,
metadata: {
bet,
Expand All @@ -318,7 +321,10 @@ module.exports = {
}
});

return message.reply({ embeds: [embed] });
const resultMessage = await message.reply({ embeds: [embed], fetchReply: true });
endCasinoSession(message.author.id);
attachReplayHandler(client, message, resultMessage, "roleta", []);
return resultMessage;
} finally {
endCasinoSession(message.author.id);
}
Expand Down
11 changes: 8 additions & 3 deletions commands/Cassino/slots.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const {
emojis,
ee,
buildCasinoEmbed,
getCasinoResultColor,
attachReplayHandler,
parseBet,
getUserData,
applyGameResult,
Expand Down Expand Up @@ -193,28 +195,31 @@ module.exports = {
const result = evaluateSlots(roll, amount);
const newBalance = await applyGameResult(message.author.id, userData.coins, result.netChange);

const embed = buildCasinoEmbed(message.author, result.netChange >= 0 ? ee.color : ee.wrongcolor)
const outcome = result.netChange > 0 ? "win" : result.netChange === 0 ? "push" : "loss";
const embed = buildCasinoEmbed(message.author, getCasinoResultColor(outcome))
.setTitle(`${emojis.casino_slots} Slots`)
.setDescription(roll.join(" | "))
.addField("Aposta", formatAmount(amount), true)
.addField("Premio", result.payout > 0 ? formatAmount(result.payout) : `**0** ${emojis.King_Coin}`, true)
.addField("Saldo atual", formatAmount(newBalance), true)
.addField("Resumo", result.netChange >= 0 ? `Voce ganhou **${result.multiplier}x**. ${result.reason}` : result.reason, false);
.addField("Resumo", outcome === "win" ? `Voce ganhou **${result.multiplier}x**. ${result.reason}` : result.reason, false);

await logCasinoEvent(client, message, {
userId: message.author.id,
game: "slots",
bet: amount,
payout: result.payout,
netChange: result.netChange,
outcome: result.netChange > 0 ? "win" : result.netChange === 0 ? "push" : "loss",
outcome,
reason: result.reason,
metadata: {
roll
}
});

await animationMessage.edit({ embeds: [embed] }).catch(() => null);
endCasinoSession(message.author.id);
attachReplayHandler(client, message, animationMessage, "slots", []);
return animationMessage;
} finally {
endCasinoSession(message.author.id);
Expand Down
62 changes: 62 additions & 0 deletions handlers/casinoUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const DEFAULT_CASINO_SETTINGS = {

const sessionCooldowns = new Map();
const activeCasinoSessions = new Map();
const CASINO_WIN_COLOR = "#00FF00";

function normalizeText(value) {
return String(value || "")
Expand All @@ -36,6 +37,63 @@ function buildCasinoEmbed(user, color = ee.color, thumbnail = null) {
return embed;
}

function getCasinoResultColor(result) {
if (typeof result === "number") {
if (result > 0) return CASINO_WIN_COLOR;
if (result < 0) return ee.wrongcolor;
return ee.color;
}

if (result === "win") return CASINO_WIN_COLOR;
if (result === "loss") return ee.wrongcolor;
return ee.color;
}

function buildReplayRow(customPrefix, disabled = false) {
return new Discord.MessageActionRow().addComponents(
new Discord.MessageButton()
.setCustomId(`${customPrefix}:replay`)
.setLabel("Jogar novamente")
.setStyle("PRIMARY")
.setEmoji(emojis.reload)
.setDisabled(disabled)
);
}

function attachReplayHandler(client, message, gameMessage, game, args = [], timeoutMs = 120000) {
if (!gameMessage?.createMessageComponentCollector) return;

const user = message.author || message.user || message.interaction?.user;
if (!user?.id) return;

const customPrefix = `casino-replay:${game}:${user.id}:${Date.now()}`;
const row = buildReplayRow(customPrefix, false);

gameMessage.edit({ components: [row] }).catch(() => null);

const collector = gameMessage.createMessageComponentCollector({
filter: (interaction) => interaction.customId === `${customPrefix}:replay`,
time: timeoutMs,
max: 1
});

collector.on("collect", async (interaction) => {
if (interaction.user.id !== user.id) {
return interaction.reply({ content: `${emojis.x} Apenas quem iniciou pode jogar novamente.`, flags: 64 }).catch(() => null);
}

await gameMessage.edit({ components: [buildReplayRow(customPrefix, true)] }).catch(() => null);
await interaction.deferUpdate().catch(() => null);
const { runSlashCommand } = require("./slashCommandUtils");
return runSlashCommand(client, interaction, game, args).catch(() => null);
});

collector.on("end", async (_collected, reason) => {
if (reason === "limit") return;
await gameMessage.edit({ components: [buildReplayRow(customPrefix, true)] }).catch(() => null);
});
}

function ensureCasinoSettings(client, guildId) {
if (!client?.settings || !guildId) return { ...DEFAULT_CASINO_SETTINGS };

Expand Down Expand Up @@ -282,10 +340,14 @@ function createSessionBusyEmbed(user, session) {
module.exports = {
emojis,
ee,
CASINO_WIN_COLOR,
DEFAULT_CASINO_SETTINGS,
normalizeText,
formatAmount,
buildCasinoEmbed,
getCasinoResultColor,
buildReplayRow,
attachReplayHandler,
ensureCasinoSettings,
parseBet,
getUserData,
Expand Down
Loading