converted ShaiBot casino to Nut Economy
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+1013
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,777 @@
|
||||
// utils/nutHeist.js
|
||||
|
||||
const {
|
||||
EmbedBuilder,
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
MessageFlags
|
||||
} = require("discord.js");
|
||||
|
||||
const {
|
||||
getNutBalance,
|
||||
transferNuts,
|
||||
claimCooldown
|
||||
} = require("./nutEconomy");
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* CONFIG
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
const HEIST_COOLDOWN =
|
||||
60 * 60;
|
||||
|
||||
const HEIST_ACTIONS = {
|
||||
Sneak: {
|
||||
label:
|
||||
"Sneak",
|
||||
|
||||
emoji:
|
||||
"🥷",
|
||||
|
||||
successChance:
|
||||
0.55,
|
||||
|
||||
minReward:
|
||||
10,
|
||||
|
||||
maxReward:
|
||||
100,
|
||||
|
||||
minLoss:
|
||||
5,
|
||||
|
||||
maxLoss:
|
||||
40
|
||||
},
|
||||
|
||||
Hack: {
|
||||
label:
|
||||
"Hack",
|
||||
|
||||
emoji:
|
||||
"💻",
|
||||
|
||||
successChance:
|
||||
0.40,
|
||||
|
||||
minReward:
|
||||
50,
|
||||
|
||||
maxReward:
|
||||
250,
|
||||
|
||||
minLoss:
|
||||
20,
|
||||
|
||||
maxLoss:
|
||||
100
|
||||
},
|
||||
|
||||
Smash: {
|
||||
label:
|
||||
"Smash",
|
||||
|
||||
emoji:
|
||||
"💥",
|
||||
|
||||
successChance:
|
||||
0.25,
|
||||
|
||||
minReward:
|
||||
150,
|
||||
|
||||
maxReward:
|
||||
500,
|
||||
|
||||
minLoss:
|
||||
50,
|
||||
|
||||
maxLoss:
|
||||
200
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* HELPERS
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
function randomInteger(
|
||||
min,
|
||||
max
|
||||
) {
|
||||
return Math.floor(
|
||||
Math.random() *
|
||||
(
|
||||
max -
|
||||
min +
|
||||
1
|
||||
)
|
||||
) + min;
|
||||
}
|
||||
|
||||
|
||||
function getHeistOwner(
|
||||
interaction
|
||||
) {
|
||||
const footer =
|
||||
interaction.message
|
||||
?.embeds?.[0]
|
||||
?.footer
|
||||
?.text;
|
||||
|
||||
const parts =
|
||||
footer?.split(":")
|
||||
??
|
||||
[];
|
||||
|
||||
|
||||
/*
|
||||
* Coming from Nut Hub.
|
||||
*
|
||||
* nutHub:<userId>
|
||||
*/
|
||||
|
||||
if (
|
||||
parts[0] ===
|
||||
"nutHub"
|
||||
) {
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Coming from Heist.
|
||||
*
|
||||
* nutHeist:<userId>
|
||||
*/
|
||||
|
||||
if (
|
||||
parts[0] ===
|
||||
"nutHeist"
|
||||
) {
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
function buildHeistButtons() {
|
||||
return [
|
||||
new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"nutHeistSneak"
|
||||
)
|
||||
.setLabel(
|
||||
"Sneak"
|
||||
)
|
||||
.setEmoji(
|
||||
"🥷"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Success
|
||||
),
|
||||
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"nutHeistHack"
|
||||
)
|
||||
.setLabel(
|
||||
"Hack"
|
||||
)
|
||||
.setEmoji(
|
||||
"💻"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Primary
|
||||
),
|
||||
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"nutHeistSmash"
|
||||
)
|
||||
.setLabel(
|
||||
"Smash"
|
||||
)
|
||||
.setEmoji(
|
||||
"💥"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Danger
|
||||
)
|
||||
),
|
||||
|
||||
new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"nutHubBack"
|
||||
)
|
||||
.setLabel(
|
||||
"Nut Hub"
|
||||
)
|
||||
.setEmoji(
|
||||
"◀️"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Secondary
|
||||
)
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
function buildReturnButtons() {
|
||||
return [
|
||||
new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"nutHubHeist"
|
||||
)
|
||||
.setLabel(
|
||||
"Heist Menu"
|
||||
)
|
||||
.setEmoji(
|
||||
"🏦"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Primary
|
||||
),
|
||||
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"nutHubBack"
|
||||
)
|
||||
.setLabel(
|
||||
"Nut Hub"
|
||||
)
|
||||
.setEmoji(
|
||||
"◀️"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Secondary
|
||||
)
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* TEMPORARY EPHEMERAL MESSAGE
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
async function temporaryEphemeralReply(
|
||||
interaction,
|
||||
options
|
||||
) {
|
||||
const reply =
|
||||
await interaction.reply({
|
||||
...options,
|
||||
|
||||
flags:
|
||||
MessageFlags.Ephemeral
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Ephemeral messages cannot truly be given
|
||||
* a Discord-side expiration time.
|
||||
*
|
||||
* We attempt to delete the reply after 5 seconds.
|
||||
*/
|
||||
|
||||
setTimeout(
|
||||
async () => {
|
||||
try {
|
||||
await interaction.deleteReply();
|
||||
}
|
||||
|
||||
catch {
|
||||
// Ignore expired/deleted interaction.
|
||||
}
|
||||
},
|
||||
5000
|
||||
);
|
||||
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* SHOW HEIST MENU
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
async function showNutHeist(
|
||||
interaction
|
||||
) {
|
||||
const ownerId =
|
||||
getHeistOwner(
|
||||
interaction
|
||||
);
|
||||
|
||||
|
||||
if (
|
||||
ownerId !==
|
||||
interaction.user.id
|
||||
) {
|
||||
return temporaryEphemeralReply(
|
||||
interaction,
|
||||
{
|
||||
content:
|
||||
"Das ist nicht deine Nut-Hub-Session."
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
await interaction.deferUpdate();
|
||||
|
||||
|
||||
const balance =
|
||||
await getNutBalance(
|
||||
interaction.user.id
|
||||
);
|
||||
|
||||
|
||||
const embed =
|
||||
new EmbedBuilder()
|
||||
.setTitle(
|
||||
"🏦 Nut Vault Heist"
|
||||
)
|
||||
|
||||
.setDescription(
|
||||
[
|
||||
"Arthonor bewacht seinen Vorrat an Nüssen.",
|
||||
"",
|
||||
"Wähle deinen Ansatz.",
|
||||
"",
|
||||
"### 🥷 Sneak",
|
||||
"**55%** Erfolgschance",
|
||||
"Kleine Beute, geringes Risiko.",
|
||||
"",
|
||||
"### 💻 Hack",
|
||||
"**40%** Erfolgschance",
|
||||
"Mittlere Beute, mittleres Risiko.",
|
||||
"",
|
||||
"### 💥 Smash",
|
||||
"**25%** Erfolgschance",
|
||||
"Große Beute, hohes Risiko.",
|
||||
"",
|
||||
`Ein Heist hat **${Math.floor(HEIST_COOLDOWN / 60)} Minuten Cooldown**.`
|
||||
].join("\n")
|
||||
)
|
||||
|
||||
.addFields({
|
||||
name:
|
||||
"Deine Nüsse",
|
||||
|
||||
value:
|
||||
`**${balance.toLocaleString()} 🥜**`
|
||||
})
|
||||
|
||||
.setFooter({
|
||||
text:
|
||||
`nutHeist:${interaction.user.id}`
|
||||
});
|
||||
|
||||
|
||||
return interaction.editReply({
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
|
||||
components:
|
||||
buildHeistButtons()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* PERFORM HEIST
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
async function performNutHeist(
|
||||
interaction,
|
||||
actionName
|
||||
) {
|
||||
const ownerId =
|
||||
getHeistOwner(
|
||||
interaction
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
* Prevent another player from using
|
||||
* somebody else's Heist menu.
|
||||
*/
|
||||
|
||||
if (
|
||||
ownerId !==
|
||||
interaction.user.id
|
||||
) {
|
||||
return temporaryEphemeralReply(
|
||||
interaction,
|
||||
{
|
||||
content:
|
||||
"Das ist nicht dein Heist."
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const action =
|
||||
HEIST_ACTIONS[
|
||||
actionName
|
||||
];
|
||||
|
||||
|
||||
if (
|
||||
!action
|
||||
) {
|
||||
return temporaryEphemeralReply(
|
||||
interaction,
|
||||
{
|
||||
content:
|
||||
"Unbekannte Heist-Aktion."
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Acknowledge immediately.
|
||||
*/
|
||||
|
||||
await interaction.deferUpdate();
|
||||
|
||||
|
||||
/*
|
||||
* ========================================================
|
||||
* CLAIM SHARED HEIST COOLDOWN
|
||||
* ========================================================
|
||||
*
|
||||
* Sneak / Hack / Smash all use the same field:
|
||||
*
|
||||
* items_cooldowns
|
||||
*
|
||||
* {
|
||||
* userId: "...",
|
||||
* nut: ...,
|
||||
* heist: ...
|
||||
* }
|
||||
*/
|
||||
|
||||
const cooldown =
|
||||
await claimCooldown(
|
||||
interaction.user.id,
|
||||
"heist",
|
||||
HEIST_COOLDOWN
|
||||
);
|
||||
|
||||
|
||||
if (
|
||||
!cooldown.claimed
|
||||
) {
|
||||
const embed =
|
||||
new EmbedBuilder()
|
||||
.setTitle(
|
||||
"🏦 Vault Locked Down"
|
||||
)
|
||||
|
||||
.setDescription(
|
||||
[
|
||||
"Arthonor ist noch auf der Hut.",
|
||||
"",
|
||||
`Du kannst <t:${cooldown.next}:R> wieder einen Heist versuchen.`
|
||||
].join("\n")
|
||||
)
|
||||
|
||||
.setFooter({
|
||||
text:
|
||||
`nutHeist:${interaction.user.id}`
|
||||
});
|
||||
|
||||
|
||||
return interaction.editReply({
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
|
||||
components:
|
||||
buildReturnButtons()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ========================================================
|
||||
* ROLL RESULT
|
||||
* ========================================================
|
||||
*/
|
||||
|
||||
const success =
|
||||
Math.random() <
|
||||
action.successChance;
|
||||
|
||||
|
||||
const playerId =
|
||||
interaction.user.id;
|
||||
|
||||
|
||||
/*
|
||||
* Arthonor himself acts as the vault.
|
||||
*/
|
||||
|
||||
const vaultId =
|
||||
interaction.client.user.id;
|
||||
|
||||
|
||||
/*
|
||||
* ========================================================
|
||||
* SUCCESS
|
||||
* ========================================================
|
||||
*/
|
||||
|
||||
if (
|
||||
success
|
||||
) {
|
||||
let reward =
|
||||
randomInteger(
|
||||
action.minReward,
|
||||
action.maxReward
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
* Never steal more Nuts than
|
||||
* Arthonor currently owns.
|
||||
*/
|
||||
|
||||
const vaultBalance =
|
||||
await getNutBalance(
|
||||
vaultId
|
||||
);
|
||||
|
||||
|
||||
reward =
|
||||
Math.min(
|
||||
reward,
|
||||
vaultBalance
|
||||
);
|
||||
|
||||
|
||||
if (
|
||||
reward <=
|
||||
0
|
||||
) {
|
||||
const embed =
|
||||
new EmbedBuilder()
|
||||
.setTitle(
|
||||
"🏦 The Vault Is Empty"
|
||||
)
|
||||
|
||||
.setDescription(
|
||||
[
|
||||
`${action.emoji} **${action.label}** succeeded...`,
|
||||
"",
|
||||
"but Arthonor's Nut stash is empty.",
|
||||
"",
|
||||
"You escape with nothing."
|
||||
].join("\n")
|
||||
)
|
||||
|
||||
.setFooter({
|
||||
text:
|
||||
`nutHeist:${playerId}`
|
||||
});
|
||||
|
||||
|
||||
return interaction.editReply({
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
|
||||
components:
|
||||
buildReturnButtons()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
await transferNuts(
|
||||
vaultId,
|
||||
playerId,
|
||||
reward
|
||||
);
|
||||
|
||||
|
||||
const balance =
|
||||
await getNutBalance(
|
||||
playerId
|
||||
);
|
||||
|
||||
|
||||
const embed =
|
||||
new EmbedBuilder()
|
||||
.setTitle(
|
||||
"💰 Heist Successful!"
|
||||
)
|
||||
|
||||
.setDescription(
|
||||
[
|
||||
`${action.emoji} **${action.label}** worked!`,
|
||||
"",
|
||||
`You stole **${reward.toLocaleString()} 🥜** from Arthonor's stash.`
|
||||
].join("\n")
|
||||
)
|
||||
|
||||
.addFields({
|
||||
name:
|
||||
"Deine Nüsse",
|
||||
|
||||
value:
|
||||
`**${balance.toLocaleString()} 🥜**`
|
||||
})
|
||||
|
||||
.setFooter({
|
||||
text:
|
||||
`nutHeist:${playerId}`
|
||||
});
|
||||
|
||||
|
||||
return interaction.editReply({
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
|
||||
components:
|
||||
buildReturnButtons()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ========================================================
|
||||
* FAILURE
|
||||
* ========================================================
|
||||
*/
|
||||
|
||||
let loss =
|
||||
randomInteger(
|
||||
action.minLoss,
|
||||
action.maxLoss
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
* Don't take more than the player owns.
|
||||
*/
|
||||
|
||||
const playerBalance =
|
||||
await getNutBalance(
|
||||
playerId
|
||||
);
|
||||
|
||||
|
||||
loss =
|
||||
Math.min(
|
||||
loss,
|
||||
playerBalance
|
||||
);
|
||||
|
||||
|
||||
if (
|
||||
loss >
|
||||
0
|
||||
) {
|
||||
await transferNuts(
|
||||
playerId,
|
||||
vaultId,
|
||||
loss
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const balance =
|
||||
await getNutBalance(
|
||||
playerId
|
||||
);
|
||||
|
||||
|
||||
const embed =
|
||||
new EmbedBuilder()
|
||||
.setTitle(
|
||||
"🚨 Heist Failed!"
|
||||
)
|
||||
|
||||
.setDescription(
|
||||
loss > 0
|
||||
? [
|
||||
`${action.emoji} **${action.label}** went wrong.`,
|
||||
"",
|
||||
"Arthonor caught you.",
|
||||
"",
|
||||
`You lost **${loss.toLocaleString()} 🥜**.`
|
||||
].join("\n")
|
||||
|
||||
: [
|
||||
`${action.emoji} **${action.label}** went wrong.`,
|
||||
"",
|
||||
"Arthonor caught you...",
|
||||
"",
|
||||
"but you didn't have any Nuts for him to take."
|
||||
].join("\n")
|
||||
)
|
||||
|
||||
.addFields({
|
||||
name:
|
||||
"Deine Nüsse",
|
||||
|
||||
value:
|
||||
`**${balance.toLocaleString()} 🥜**`
|
||||
})
|
||||
|
||||
.setFooter({
|
||||
text:
|
||||
`nutHeist:${playerId}`
|
||||
});
|
||||
|
||||
|
||||
return interaction.editReply({
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
|
||||
components:
|
||||
buildReturnButtons()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* EXPORTS
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
showNutHeist,
|
||||
performNutHeist
|
||||
};
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
const {
|
||||
EmbedBuilder,
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
MessageFlags
|
||||
} = require("discord.js");
|
||||
|
||||
const {
|
||||
getNutBalance
|
||||
} = require("./nutEconomy");
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* SHOW HUB
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
async function showNutHub(
|
||||
interaction
|
||||
) {
|
||||
const balance =
|
||||
await getNutBalance(
|
||||
interaction.user.id
|
||||
);
|
||||
|
||||
|
||||
const embed =
|
||||
buildNutHubEmbed(
|
||||
interaction.user,
|
||||
balance
|
||||
);
|
||||
|
||||
|
||||
const components =
|
||||
buildNutHubButtons();
|
||||
|
||||
|
||||
/*
|
||||
* Slash command / fresh interaction.
|
||||
*/
|
||||
|
||||
if (
|
||||
!interaction.replied &&
|
||||
!interaction.deferred
|
||||
) {
|
||||
return interaction.reply({
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
|
||||
components
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Existing game/menu message.
|
||||
*/
|
||||
|
||||
return interaction.update({
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
|
||||
components
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* EMBED
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
function buildNutHubEmbed(
|
||||
user,
|
||||
balance
|
||||
) {
|
||||
return new EmbedBuilder()
|
||||
.setTitle(
|
||||
"🥜 Nut Hub"
|
||||
)
|
||||
|
||||
.setDescription(
|
||||
[
|
||||
`Willkommen, ${user}!`,
|
||||
"",
|
||||
"Hier werden Nüsse gewonnen, verloren und gelegentlich konfisziert.",
|
||||
"",
|
||||
"### 🎰 Nut Slots",
|
||||
"Dreh die Walzen und jag den Jackpot.",
|
||||
"",
|
||||
"### 🃏 Nut Blackjack",
|
||||
"Schlag den Dealer ohne über 21 zu kommen.",
|
||||
"",
|
||||
"### 🎡 Nut Roulette",
|
||||
"Setz auf Rot, Schwarz, Ungerade, Gerade oder Zero.",
|
||||
"",
|
||||
"### 🏦 Nut Heist",
|
||||
"Versuch dich direkt am Nut Vault des Bots.",
|
||||
"",
|
||||
"*Choose wisely. The house has very large nuts.*"
|
||||
].join("\n")
|
||||
)
|
||||
|
||||
.addFields({
|
||||
name:
|
||||
"Deine Nüsse",
|
||||
|
||||
value:
|
||||
`**${balance.toLocaleString()} 🥜**`
|
||||
})
|
||||
|
||||
.setThumbnail(
|
||||
user.displayAvatarURL({
|
||||
size:
|
||||
256
|
||||
})
|
||||
)
|
||||
|
||||
.setFooter({
|
||||
text:
|
||||
`nutHub:${user.id}`
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* BUTTONS
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
function buildNutHubButtons() {
|
||||
return [
|
||||
new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"nutHubSlots"
|
||||
)
|
||||
.setLabel(
|
||||
"Slots"
|
||||
)
|
||||
.setEmoji(
|
||||
"🎰"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Primary
|
||||
),
|
||||
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"nutHubBlackjack"
|
||||
)
|
||||
.setLabel(
|
||||
"Blackjack"
|
||||
)
|
||||
.setEmoji(
|
||||
"🃏"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Success
|
||||
),
|
||||
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"nutHubRoulette"
|
||||
)
|
||||
.setLabel(
|
||||
"Roulette"
|
||||
)
|
||||
.setEmoji(
|
||||
"🎡"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Primary
|
||||
),
|
||||
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"nutHubHeist"
|
||||
)
|
||||
.setLabel(
|
||||
"Vault Heist"
|
||||
)
|
||||
.setEmoji(
|
||||
"🏦"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Danger
|
||||
)
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* OWNERSHIP
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
function getNutHubOwner(
|
||||
interaction
|
||||
) {
|
||||
const footer =
|
||||
interaction.message
|
||||
?.embeds?.[0]
|
||||
?.footer
|
||||
?.text;
|
||||
|
||||
|
||||
const parts =
|
||||
footer?.split(":")
|
||||
??
|
||||
[];
|
||||
|
||||
|
||||
if (
|
||||
parts[0] !==
|
||||
"nutHub"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
showNutHub,
|
||||
buildNutHubEmbed,
|
||||
buildNutHubButtons,
|
||||
getNutHubOwner
|
||||
};
|
||||
@@ -0,0 +1,975 @@
|
||||
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
|
||||
};
|
||||
@@ -0,0 +1,795 @@
|
||||
const {
|
||||
EmbedBuilder,
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle
|
||||
} = require("discord.js");
|
||||
|
||||
const {
|
||||
getNutBalance,
|
||||
transferNuts,
|
||||
temporaryEphemeralReply,
|
||||
randomItem
|
||||
} = require("./nutEconomy");
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* CONFIG
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
const SLOT_BETS = [
|
||||
10,
|
||||
50,
|
||||
100,
|
||||
500,
|
||||
1000
|
||||
];
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* NUT SYMBOLS
|
||||
* ============================================================
|
||||
*
|
||||
* All symbols are nut-themed.
|
||||
*
|
||||
* 🌰 Chestnut
|
||||
* 🥜 Peanuts
|
||||
* 🥥 Coconut
|
||||
* 🍩 Doughnut
|
||||
*/
|
||||
|
||||
const SYMBOLS = [
|
||||
"🌰",
|
||||
"🥜",
|
||||
"🥥",
|
||||
"🍩"
|
||||
];
|
||||
|
||||
|
||||
/*
|
||||
* Weighted reel.
|
||||
*
|
||||
* Chestnuts are the common symbol.
|
||||
* Peanuts are less common.
|
||||
* Coconuts are rare.
|
||||
* Doughnuts are the jackpot symbol.
|
||||
*/
|
||||
|
||||
const REEL = [
|
||||
"🌰",
|
||||
"🌰",
|
||||
"🌰",
|
||||
"🌰",
|
||||
"🌰",
|
||||
|
||||
"🥜",
|
||||
"🥜",
|
||||
"🥜",
|
||||
"🥜",
|
||||
|
||||
"🥥",
|
||||
"🥥",
|
||||
|
||||
"🍩"
|
||||
];
|
||||
|
||||
|
||||
/*
|
||||
* Triple-symbol payouts.
|
||||
*
|
||||
* Payout includes the original wager.
|
||||
*/
|
||||
|
||||
const PAYOUTS = {
|
||||
"🌰":
|
||||
1.5,
|
||||
|
||||
"🥜":
|
||||
2,
|
||||
|
||||
"🥥":
|
||||
4,
|
||||
|
||||
"🍩":
|
||||
10
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* SHOW SLOTS
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
async function showNutSlots(
|
||||
interaction
|
||||
) {
|
||||
|
||||
const ownerId =
|
||||
getNutSlotsOwner(
|
||||
interaction
|
||||
);
|
||||
|
||||
|
||||
if (
|
||||
ownerId !==
|
||||
interaction.user.id
|
||||
) {
|
||||
return temporaryEphemeralReply(
|
||||
interaction,
|
||||
{
|
||||
content:
|
||||
"Das ist nicht deine Nut-Hub-Session."
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Acknowledge the button immediately.
|
||||
*/
|
||||
await interaction.deferUpdate();
|
||||
|
||||
|
||||
const balance =
|
||||
await getNutBalance(
|
||||
interaction.user.id
|
||||
);
|
||||
|
||||
|
||||
const embed =
|
||||
new EmbedBuilder()
|
||||
.setTitle(
|
||||
"🎰 Nut Slots"
|
||||
)
|
||||
|
||||
.setDescription(
|
||||
[
|
||||
"Dreh die Walzen und jag den **Nut Jackpot**.",
|
||||
"",
|
||||
"### 🥜 Payouts",
|
||||
"🌰 🌰 🌰 — **×1.5**",
|
||||
"🥜 🥜 🥜 — **×2**",
|
||||
"🥥 🥥 🥥 — **×4**",
|
||||
"🍩 🍩 🍩 — **×10**",
|
||||
"",
|
||||
"Choose your wager."
|
||||
].join("\n")
|
||||
)
|
||||
|
||||
.addFields({
|
||||
name:
|
||||
"Deine Nüsse",
|
||||
|
||||
value:
|
||||
`**${balance.toLocaleString()} 🥜**`
|
||||
})
|
||||
|
||||
.setFooter({
|
||||
text:
|
||||
`nutSlots:${interaction.user.id}`
|
||||
});
|
||||
|
||||
|
||||
return interaction.editReply({
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
|
||||
components:
|
||||
buildSlotBetButtons()
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* PLAY
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
async function playNutSlots(
|
||||
interaction,
|
||||
bet
|
||||
) {
|
||||
if (
|
||||
!SLOT_BETS.includes(
|
||||
bet
|
||||
)
|
||||
) {
|
||||
return temporaryEphemeralReply(
|
||||
interaction,
|
||||
{
|
||||
content:
|
||||
"Dieser Einsatz ist nicht verfügbar."
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const ownerId =
|
||||
getNutSlotsOwner(
|
||||
interaction
|
||||
);
|
||||
|
||||
|
||||
if (
|
||||
ownerId !==
|
||||
interaction.user.id
|
||||
) {
|
||||
return temporaryEphemeralReply(
|
||||
interaction,
|
||||
{
|
||||
content:
|
||||
"Das ist nicht deine Slots-Session."
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
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 brauchst **${bet.toLocaleString()} Nüsse** für diesen Einsatz.`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Maximum possible return = x10.
|
||||
*
|
||||
* After receiving the wager, the house must
|
||||
* still be able to cover the total payout.
|
||||
*/
|
||||
|
||||
const maxPayout =
|
||||
bet *
|
||||
10;
|
||||
|
||||
|
||||
if (
|
||||
houseBalance +
|
||||
bet <
|
||||
maxPayout
|
||||
) {
|
||||
return temporaryEphemeralReply(
|
||||
interaction,
|
||||
{
|
||||
content:
|
||||
"Der Nut Vault kann diesen Einsatz aktuell nicht absichern."
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await transferNuts(
|
||||
playerId,
|
||||
houseId,
|
||||
bet
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
if (
|
||||
error.message ===
|
||||
"INSUFFICIENT_NUTS"
|
||||
) {
|
||||
return temporaryEphemeralReply(
|
||||
interaction,
|
||||
{
|
||||
content:
|
||||
"Dein Nut-Bestand hat sich verändert."
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initial animation state.
|
||||
*/
|
||||
|
||||
await interaction.update({
|
||||
embeds: [
|
||||
buildSpinEmbed(
|
||||
bet,
|
||||
[
|
||||
"❔",
|
||||
"❔",
|
||||
"❔"
|
||||
],
|
||||
"Die Walzen drehen..."
|
||||
)
|
||||
],
|
||||
|
||||
components: []
|
||||
});
|
||||
|
||||
|
||||
await delay(
|
||||
500
|
||||
);
|
||||
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
buildSpinEmbed(
|
||||
bet,
|
||||
[
|
||||
randomReelSymbol(),
|
||||
"❔",
|
||||
"❔"
|
||||
],
|
||||
"Die erste Walze stoppt..."
|
||||
)
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
await delay(
|
||||
500
|
||||
);
|
||||
|
||||
|
||||
const first =
|
||||
randomReelSymbol();
|
||||
|
||||
|
||||
const second =
|
||||
randomReelSymbol();
|
||||
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
buildSpinEmbed(
|
||||
bet,
|
||||
[
|
||||
first,
|
||||
second,
|
||||
"❔"
|
||||
],
|
||||
"Noch eine..."
|
||||
)
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
await delay(
|
||||
600
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
* Final actual result.
|
||||
*
|
||||
* Generate all three here so animation values
|
||||
* don't alter game odds.
|
||||
*/
|
||||
|
||||
const result = [
|
||||
randomReelSymbol(),
|
||||
randomReelSymbol(),
|
||||
randomReelSymbol()
|
||||
];
|
||||
|
||||
|
||||
const multiplier =
|
||||
getSlotMultiplier(
|
||||
result
|
||||
);
|
||||
|
||||
|
||||
const payout =
|
||||
Math.floor(
|
||||
bet *
|
||||
multiplier
|
||||
);
|
||||
|
||||
|
||||
if (
|
||||
payout >
|
||||
0
|
||||
) {
|
||||
await transferNuts(
|
||||
houseId,
|
||||
playerId,
|
||||
payout
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const balanceAfter =
|
||||
await getNutBalance(
|
||||
playerId
|
||||
);
|
||||
|
||||
|
||||
const profit =
|
||||
payout -
|
||||
bet;
|
||||
|
||||
|
||||
const embed =
|
||||
new EmbedBuilder()
|
||||
.setTitle(
|
||||
multiplier >
|
||||
0
|
||||
? "🎰 Nut Slots — WIN!"
|
||||
: "🎰 Nut Slots"
|
||||
)
|
||||
|
||||
.setDescription(
|
||||
[
|
||||
"## " +
|
||||
result.join(
|
||||
" "
|
||||
),
|
||||
"",
|
||||
multiplier >
|
||||
0
|
||||
? `🎉 **x${multiplier}**`
|
||||
: "Die Nüsse bleiben beim Haus."
|
||||
].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:
|
||||
`${balanceAfter.toLocaleString()} 🥜`
|
||||
}
|
||||
)
|
||||
|
||||
.setFooter({
|
||||
text:
|
||||
`nutSlots:${interaction.user.id}`
|
||||
});
|
||||
|
||||
|
||||
return interaction.editReply({
|
||||
embeds: [
|
||||
embed
|
||||
],
|
||||
|
||||
components:
|
||||
buildSlotResultButtons()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* PAYOUT
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
function getSlotMultiplier(
|
||||
result
|
||||
) {
|
||||
const [
|
||||
first,
|
||||
second,
|
||||
third
|
||||
] = result;
|
||||
|
||||
|
||||
/*
|
||||
* Triple.
|
||||
*/
|
||||
|
||||
if (
|
||||
first === second &&
|
||||
second === third
|
||||
) {
|
||||
return PAYOUTS[first] ?? 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Two matching symbols.
|
||||
*
|
||||
* Half the wager is returned.
|
||||
*/
|
||||
|
||||
if (
|
||||
first === second ||
|
||||
first === third ||
|
||||
second === third
|
||||
) {
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Nut Salad:
|
||||
*
|
||||
* Chestnut + Peanut + Coconut
|
||||
* in any order.
|
||||
*
|
||||
* Returns the wager.
|
||||
*/
|
||||
|
||||
const nutSalad =
|
||||
new Set(
|
||||
result
|
||||
);
|
||||
|
||||
|
||||
if (
|
||||
nutSalad.size === 3 &&
|
||||
nutSalad.has("🌰") &&
|
||||
nutSalad.has("🥜") &&
|
||||
nutSalad.has("🥥")
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* DISPLAY
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
function buildSpinEmbed(
|
||||
bet,
|
||||
symbols,
|
||||
status
|
||||
) {
|
||||
return new EmbedBuilder()
|
||||
.setTitle(
|
||||
"🎰 Nut Slots"
|
||||
)
|
||||
|
||||
.setDescription(
|
||||
[
|
||||
"## " +
|
||||
symbols.join(
|
||||
" "
|
||||
),
|
||||
"",
|
||||
`**${status}**`
|
||||
].join("\n")
|
||||
)
|
||||
|
||||
.addFields({
|
||||
name:
|
||||
"Einsatz",
|
||||
|
||||
value:
|
||||
`${bet.toLocaleString()} 🥜`
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* BUTTONS
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
function buildSlotBetButtons() {
|
||||
return [
|
||||
new ActionRowBuilder()
|
||||
.addComponents(
|
||||
...SLOT_BETS.map(
|
||||
bet =>
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
`nutSlotsBet${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 buildSlotResultButtons() {
|
||||
return [
|
||||
new ActionRowBuilder()
|
||||
.addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"nutHubSlots"
|
||||
)
|
||||
.setLabel(
|
||||
"Play Again"
|
||||
)
|
||||
.setEmoji(
|
||||
"🎰"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Primary
|
||||
),
|
||||
|
||||
new ButtonBuilder()
|
||||
.setCustomId(
|
||||
"nutHubBack"
|
||||
)
|
||||
.setLabel(
|
||||
"Nut Hub"
|
||||
)
|
||||
.setEmoji(
|
||||
"◀️"
|
||||
)
|
||||
.setStyle(
|
||||
ButtonStyle.Secondary
|
||||
)
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* SESSION OWNER
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
function getNutSlotsOwner(
|
||||
interaction
|
||||
) {
|
||||
const footer =
|
||||
interaction.message
|
||||
?.embeds?.[0]
|
||||
?.footer
|
||||
?.text;
|
||||
|
||||
|
||||
const parts =
|
||||
footer?.split(":")
|
||||
??
|
||||
[];
|
||||
|
||||
|
||||
if (
|
||||
parts[0] ===
|
||||
"nutHub"
|
||||
) {
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
parts[0] ===
|
||||
"nutSlots"
|
||||
) {
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================================================
|
||||
* RANDOM
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
function randomReelSymbol() {
|
||||
return randomItem(
|
||||
REEL
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function delay(
|
||||
ms
|
||||
) {
|
||||
return new Promise(
|
||||
resolve =>
|
||||
setTimeout(
|
||||
resolve,
|
||||
ms
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
showNutSlots,
|
||||
playNutSlots
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
module.exports =
|
||||
function ping(interface, args, type){
|
||||
if (type = 'legacy'){
|
||||
// type = legacy
|
||||
interface.reply('pang!')
|
||||
} else {
|
||||
interface.reply('pong!')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user