converted ShaiBot casino to Nut Economy

This commit is contained in:
2026-08-19 14:10:38 +02:00
parent a126de21d4
commit 1957991182
55 changed files with 8905 additions and 751 deletions
+777
View File
@@ -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
};