Compare commits

..

3 Commits

Author SHA1 Message Date
admin_rb 1957991182 converted ShaiBot casino to Nut Economy 2026-08-19 14:10:38 +02:00
admin_rb a126de21d4 changed collection name 2026-08-16 13:04:54 +02:00
admin_rb 71e53c8014 no longer reassigns the same user 2026-08-16 13:01:32 +02:00
57 changed files with 8972 additions and 772 deletions
+14
View File
@@ -0,0 +1,14 @@
const {
showNutHub
} = require("../../utils/nutHub");
module.exports = {
name: "nutHubBack",
async execute(interaction) {
return showNutHub(
interaction
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
showNutBlackjack
} = require(
"../../utils/nutBlackjack"
);
module.exports = {
name:
"nutHubBlackjack",
async execute(
interaction
) {
return showNutBlackjack(
interaction
);
}
};
+20
View File
@@ -0,0 +1,20 @@
const {
startNutBlackjack
} = require(
"../../utils/nutBlackjack"
);
module.exports = {
name:
"nutBlackjackBet10",
async execute(
interaction
) {
return startNutBlackjack(
interaction,
10
);
}
};
+20
View File
@@ -0,0 +1,20 @@
const {
startNutBlackjack
} = require(
"../../utils/nutBlackjack"
);
module.exports = {
name:
"nutBlackjackBet100",
async execute(
interaction
) {
return startNutBlackjack(
interaction,
100
);
}
};
+20
View File
@@ -0,0 +1,20 @@
const {
startNutBlackjack
} = require(
"../../utils/nutBlackjack"
);
module.exports = {
name:
"nutBlackjackBet1000",
async execute(
interaction
) {
return startNutBlackjack(
interaction,
1000
);
}
};
+20
View File
@@ -0,0 +1,20 @@
const {
startNutBlackjack
} = require(
"../../utils/nutBlackjack"
);
module.exports = {
name:
"nutBlackjackBet50",
async execute(
interaction
) {
return startNutBlackjack(
interaction,
50
);
}
};
+20
View File
@@ -0,0 +1,20 @@
const {
startNutBlackjack
} = require(
"../../utils/nutBlackjack"
);
module.exports = {
name:
"nutBlackjackBet500",
async execute(
interaction
) {
return startNutBlackjack(
interaction,
500
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
doubleNutBlackjack
} = require(
"../../utils/nutBlackjack"
);
module.exports = {
name:
"nutBlackjackDouble",
async execute(
interaction
) {
return doubleNutBlackjack(
interaction
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
hitNutBlackjack
} = require(
"../../utils/nutBlackjack"
);
module.exports = {
name:
"nutBlackjackHit",
async execute(
interaction
) {
return hitNutBlackjack(
interaction
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
standNutBlackjack
} = require(
"../../utils/nutBlackjack"
);
module.exports = {
name:
"nutBlackjackStand",
async execute(
interaction
) {
return standNutBlackjack(
interaction
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
showNutHeist
} = require(
"../../utils/nutHeist"
);
module.exports = {
name:
"nutHubHeist",
async execute(
interaction
) {
return showNutHeist(
interaction
);
}
};
+22
View File
@@ -0,0 +1,22 @@
// buttons/nutHub/heistHack.js
const {
performNutHeist
} = require(
"../../utils/nutHeist"
);
module.exports = {
name:
"nutHeistHack",
async execute(
interaction
) {
return performNutHeist(
interaction,
"Hack"
);
}
};
+22
View File
@@ -0,0 +1,22 @@
// buttons/nutHub/heistSmash.js
const {
performNutHeist
} = require(
"../../utils/nutHeist"
);
module.exports = {
name:
"nutHeistSmash",
async execute(
interaction
) {
return performNutHeist(
interaction,
"Smash"
);
}
};
+22
View File
@@ -0,0 +1,22 @@
// buttons/nutHub/heistSneak.js
const {
performNutHeist
} = require(
"../../utils/nutHeist"
);
module.exports = {
name:
"nutHeistSneak",
async execute(
interaction
) {
return performNutHeist(
interaction,
"Sneak"
);
}
};
+21
View File
@@ -0,0 +1,21 @@
// buttons/nutHub/roulette.js
const {
showNutRoulette
} = require(
"../../utils/nutRoulette"
);
module.exports = {
name:
"nutHubRoulette",
async execute(
interaction
) {
return showNutRoulette(
interaction
);
}
};
+15
View File
@@ -0,0 +1,15 @@
// buttons/nutHub/rouletteBet10.js
const {
chooseNutRouletteBet
} = require("../../utils/nutRoulette");
module.exports = {
name: "nutRouletteBet10",
async execute(interaction) {
return chooseNutRouletteBet(
interaction,
10
);
}
};
+15
View File
@@ -0,0 +1,15 @@
// buttons/nutHub/rouletteBet100.js
const {
chooseNutRouletteBet
} = require("../../utils/nutRoulette");
module.exports = {
name: "nutRouletteBet100",
async execute(interaction) {
return chooseNutRouletteBet(
interaction,
100
);
}
};
+15
View File
@@ -0,0 +1,15 @@
// buttons/nutHub/rouletteBet1000.js
const {
chooseNutRouletteBet
} = require("../../utils/nutRoulette");
module.exports = {
name: "nutRouletteBet1000",
async execute(interaction) {
return chooseNutRouletteBet(
interaction,
1000
);
}
};
+15
View File
@@ -0,0 +1,15 @@
// buttons/nutHub/rouletteBet50.js
const {
chooseNutRouletteBet
} = require("../../utils/nutRoulette");
module.exports = {
name: "nutRouletteBet50",
async execute(interaction) {
return chooseNutRouletteBet(
interaction,
50
);
}
};
+15
View File
@@ -0,0 +1,15 @@
// buttons/nutHub/rouletteBet500.js
const {
chooseNutRouletteBet
} = require("../../utils/nutRoulette");
module.exports = {
name: "nutRouletteBet500",
async execute(interaction) {
return chooseNutRouletteBet(
interaction,
500
);
}
};
+15
View File
@@ -0,0 +1,15 @@
// buttons/nutHub/rouletteBlack.js
const {
playNutRoulette
} = require("../../utils/nutRoulette");
module.exports = {
name: "nutRouletteBlack",
async execute(interaction) {
return playNutRoulette(
interaction,
"Black"
);
}
};
+15
View File
@@ -0,0 +1,15 @@
// buttons/nutHub/rouletteEven.js
const {
playNutRoulette
} = require("../../utils/nutRoulette");
module.exports = {
name: "nutRouletteEven",
async execute(interaction) {
return playNutRoulette(
interaction,
"Even"
);
}
};
+15
View File
@@ -0,0 +1,15 @@
// buttons/nutHub/rouletteOdd.js
const {
playNutRoulette
} = require("../../utils/nutRoulette");
module.exports = {
name: "nutRouletteOdd",
async execute(interaction) {
return playNutRoulette(
interaction,
"Odd"
);
}
};
+15
View File
@@ -0,0 +1,15 @@
// buttons/nutHub/rouletteRed.js
const {
playNutRoulette
} = require("../../utils/nutRoulette");
module.exports = {
name: "nutRouletteRed",
async execute(interaction) {
return playNutRoulette(
interaction,
"Red"
);
}
};
+15
View File
@@ -0,0 +1,15 @@
// buttons/nutHub/rouletteZero.js
const {
playNutRoulette
} = require("../../utils/nutRoulette");
module.exports = {
name: "nutRouletteZero",
async execute(interaction) {
return playNutRoulette(
interaction,
"Zero"
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
playNutSlots
} = require(
"../../utils/nutSlots"
);
module.exports = {
name:
"nutSlotsBet10",
async execute(
interaction
) {
return playNutSlots(
interaction,
10
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
playNutSlots
} = require(
"../../utils/nutSlots"
);
module.exports = {
name:
"nutSlotsBet100",
async execute(
interaction
) {
return playNutSlots(
interaction,
100
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
playNutSlots
} = require(
"../../utils/nutSlots"
);
module.exports = {
name:
"nutSlotsBet1000",
async execute(
interaction
) {
return playNutSlots(
interaction,
1000
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
playNutSlots
} = require(
"../../utils/nutSlots"
);
module.exports = {
name:
"nutSlotsBet50",
async execute(
interaction
) {
return playNutSlots(
interaction,
50
);
}
};
+19
View File
@@ -0,0 +1,19 @@
const {
playNutSlots
} = require(
"../../utils/nutSlots"
);
module.exports = {
name:
"nutSlotsBet500",
async execute(
interaction
) {
return playNutSlots(
interaction,
500
);
}
};
+21
View File
@@ -0,0 +1,21 @@
// buttons/nutHub/slots.js
const {
showNutSlots
} = require(
"../../utils/nutSlots"
);
module.exports = {
name:
"nutHubSlots",
async execute(
interaction
) {
return showNutSlots(
interaction
);
}
};
+4 -4
View File
@@ -13,11 +13,11 @@ module.exports = {
const db = mClient.db(process.env.M_DB)
const nutsColl = db.collection('items_nuts')
await nutsColl.findOneAndUpdate({ userID: from.id }, { $inc: { nuts: -amount } }, { upsert: true })
await nutsColl.findOneAndUpdate({ userID: to.id }, { $inc: { nuts: +amount } }, { upsert: true })
await nutsColl.findOneAndUpdate({ userId: from.id }, { $inc: { nuts: -amount } }, { upsert: true })
await nutsColl.findOneAndUpdate({ userId: to.id }, { $inc: { nuts: +amount } }, { upsert: true })
const yourBalance = await nutsColl.findOne({ userID: from.id })
const theirBalance = await nutsColl.findOne({ userID: to.id })
const yourBalance = await nutsColl.findOne({ userId: from.id })
const theirBalance = await nutsColl.findOne({ userId: to.id })
const embed = new EmbedBuilder()
.setTitle('Gib Nuss!')
.setDescription(`Willst du wirklich **${amount}** Nüsse an <@${to.id}> senden?`)
+2 -2
View File
@@ -12,8 +12,8 @@ module.exports = {
const db = mClient.db(process.env.M_DB)
const nutsColl = db.collection('items_nuts')
const yourBalance = await nutsColl.findOne({ userID: from.id })
const theirBalance = await nutsColl.findOne({ userID: to.id })
const yourBalance = await nutsColl.findOne({ userId: from.id })
const theirBalance = await nutsColl.findOne({ userId: to.id })
const embed = new EmbedBuilder()
.setTitle('Gib Nuss!')
+1 -1
View File
@@ -19,7 +19,7 @@ module.exports = {
let fields
nutsData.forEach((data) => {
skip++
fields = (fields ? fields : '') + (`${skip}. <@${data.userID}> : ${data.nuts} Nuts\r\n`)
fields = (fields ? fields : '') + (`${skip}. <@${data.userId}> : ${data.nuts} Nuts\r\n`)
})
const embed = new EmbedBuilder()
.setTitle('Nuts Leaderboard')
+1 -1
View File
@@ -17,7 +17,7 @@ module.exports = {
let fields
nutsData.forEach((data) => {
skip++
fields = (fields ? fields : '') + (`${skip}. <@${data.userID}> : ${data.nuts} Nuts\r\n`)
fields = (fields ? fields : '') + (`${skip}. <@${data.userId}> : ${data.nuts} Nuts\r\n`)
})
const embed = new EmbedBuilder()
.setTitle('Nuts Leaderboard')
+3 -3
View File
@@ -8,7 +8,7 @@ module.exports = {
const db = mClient.db(process.env.M_DB)
const nutsColl = db.collection('items_nuts')
const self = await nutsColl.find({ userID: interaction.user.id }).toArray()
const self = await nutsColl.find({ userId: interaction.user.id }).toArray()
const all = await nutsColl.find({ nuts: { $gt: 0 } }).sort({ nuts: -1 }).toArray()
function findIndex(){
@@ -16,7 +16,7 @@ module.exports = {
for (let i = 0; i < all.length; i++) {
index++
if (all[i].nuts === self[0].nuts) {
return index
return index - 1
}
}
}
@@ -27,7 +27,7 @@ module.exports = {
let fields
nutsData.forEach((data) => {
skip++
fields = (fields ? fields : '') + (`${skip}. <@${data.userID}> : ${data.nuts} Nuts\r\n`)
fields = (fields ? fields : '') + (`${skip}. <@${data.userId}> : ${data.nuts} Nuts\r\n`)
})
const embed = new EmbedBuilder()
.setTitle('Nuts Leaderboard')
+1 -1
View File
@@ -1,4 +1,4 @@
const ping = require("../../../src/ping")
const ping = require("../../../utils/ping")
module.exports = {
name: 'ping',
+146 -42
View File
@@ -1,54 +1,158 @@
const { EmbedBuilder } = require('discord.js')
const { mClient } = require('../../../index')
function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
require('dotenv').config()
const {
EmbedBuilder
} = require("discord.js");
const {
getNutBalance
} = require(
"../../../utils/nutEconomy"
);
function delay(ms) {
return new Promise(
resolve =>
setTimeout(
resolve,
ms
)
);
}
module.exports = {
name: 'checknuts',
description: 'get nuts',
aliases: ['chn'],
async execute(message, args) {
var target = message.author
var addressing = `Du hast`
if (args[0]) {
if (!args[0].startsWith('<@' || !args[0].endsWith('>'))) { return message.reply('Invalid User Specification') }
try {
target = await message.guild.members.fetch(args[0].slice(2, -1))
} catch (error) {
return message.reply({
content: `User: ${args[0]} nicht gefunden!`
}).then(async (msg) => {
await delay(4000)
message.delete()
msg.delete()
})
name:
"checknuts",
description:
"get nuts",
aliases: [
"chn"
],
async execute(
message,
args
) {
let target =
message.author;
let addressing =
"Du hast";
if (
args[0]
) {
if (
!args[0].startsWith("<@")
||
!args[0].endsWith(">")
) {
return message.reply(
"Invalid User Specification"
);
}
if (target.user.id !== message.author.id) {
if (!target.user.globalName) {
addressing = `${target.user.username} hat`
} else {
addressing = `${target.user.globalName} hat`
try {
target =
await message.guild.members.fetch(
args[0].slice(
2,
-1
)
);
} catch (error) {
return message.reply({
content:
`User: ${args[0]} nicht gefunden!`
})
.then(
async msg => {
await delay(
4000
);
message.delete();
msg.delete();
}
);
}
if (
target.user.id !==
message.author.id
) {
if (
!target.user.globalName
) {
addressing =
`${target.user.username} hat`;
}
else {
addressing =
`${target.user.globalName} hat`;
}
}
}
const db = mClient.db(process.env.M_DB)
const nutsColl = db.collection('items_nuts')
var nutsData = await nutsColl.findOne({ userID: target.id })
let content
if (!nutsData) {
content = `${addressing} noch keine Nüsse gesammelt :(`
} else {
content = `${addressing} bereits **${nutsData.nuts}** Nüsse gesammelt!`
const targetId =
target.id
??
target.user?.id;
const balance =
await getNutBalance(
targetId
);
let content;
if (
balance <=
0
) {
content =
`${addressing} noch keine Nüsse gesammelt :(`;
}
const embed = new EmbedBuilder()
.setTitle('Checknuts!')
.setThumbnail(target.displayAvatarURL())
.setDescription(content)
else {
content =
`${addressing} bereits **${balance}** Nüsse gesammelt!`;
}
const embed =
new EmbedBuilder()
.setTitle(
"Checknuts!"
)
.setThumbnail(
target.displayAvatarURL()
)
.setDescription(
content
);
return message.reply({
embeds: [embed]
})
embeds: [
embed
]
});
}
}
};
+82 -25
View File
@@ -1,31 +1,88 @@
const { EmbedBuilder } = require('discord.js')
const { mClient } = require('../../../index')
require('dotenv').config()
module.exports = {
name: 'nutsCooldown',
description: 'shows nut cooldown',
aliases: ['ncd'],
async execute(message, args) {
const db = mClient.db(process.env.M_DB)
const nutsColl = db.collection('items_cooldowns')
const cooldown = await nutsColl.findOne({ userID: message.author.id })
const {
EmbedBuilder
} = require("discord.js");
let content = `Du kannst wieder nussen! :)`
let thumbnail = 'https://cdn-icons-png.flaticon.com/512/7451/7451659.png'
let title = 'Go Nuts!'
if (cooldown) {
content = `<t:${cooldown.cooldown}:R> kannst du wieder nussen! ;)`
thumbnail = 'https://cdn.discordapp.com/attachments/1152723542836772914/1152987472788193361/No-nuts-PhotoRoom.png-PhotoRoom.png'
title = 'To Nut or Not to Nut...'
const {
getCooldown,
getUnixTime
} = require(
"../../../utils/nutEconomy"
);
module.exports = {
name:
"nutsCooldown",
description:
"shows nut cooldown",
aliases: [
"ncd"
],
async execute(
message,
args
) {
const cooldown =
await getCooldown(
message.author.id,
"nut"
);
const now =
getUnixTime();
let content =
"Du kannst wieder nussen! :)";
let thumbnail =
"https://cdn-icons-png.flaticon.com/512/7451/7451659.png";
let title =
"Go Nuts!";
if (
cooldown >
now
) {
content =
`<t:${cooldown}:R> kannst du wieder nussen! ;)`;
thumbnail =
"https://cdn.discordapp.com/attachments/1152723542836772914/1152987472788193361/No-nuts-PhotoRoom.png-PhotoRoom.png";
title =
"To Nut or Not to Nut...";
}
const embed = new EmbedBuilder()
.setThumbnail(thumbnail)
.setTitle(title)
.setDescription(content)
const embed =
new EmbedBuilder()
.setThumbnail(
thumbnail
)
.setTitle(
title
)
.setDescription(
content
);
await message.reply({
embeds: [embed]
})
embeds: [
embed
]
});
}
}
};
+123 -56
View File
@@ -1,64 +1,131 @@
const { mClient } = require('../../../index')
require('dotenv').config()
const {
getCollections,
changeNuts,
claimCooldown,
randomInteger
} = require(
"../../../utils/nutEconomy"
);
module.exports = {
name: 'nuts',
description: 'get nuts',
aliases: ['n', 'nget', 'nutsget'],
async execute(message, args) {
const db = mClient.db(process.env.M_DB)
const nutsColl = db.collection('items_nuts')
const nutsStatsColl = db.collection('stats_nuts')
const cdColl = db.collection('items_cooldowns')
const cdData = await cdColl.findOne({ userID: message.author.id })
name:
"nuts",
if (cdData?.cooldown > (Date.now() / 1000)) {
return await message.reply({
content: `Du kannst erst <t:${Math.floor(cdData?.cooldown)}:R> wieder nussen :(`,
})
}
description:
"get nuts",
let cd = Math.floor((Date.now() + 3600000) / 1000)
await cdColl.findOneAndUpdate({
userID: message.author.id
}, {
$set: {
cooldown: cd,
application: 'nuts'
}
}, {
upsert: true
})
aliases: [
"n",
"nget",
"nutsget"
],
const amount = Math.floor(Math.random() * 10)
await nutsColl.findOneAndUpdate({
userID: message.author.id
}, {
$inc: { nuts: amount }
}, {
upsert: true
})
async execute(
message,
args
) {
const {
db
} =
getCollections();
await nutsStatsColl.findOneAndUpdate(
{ userID: message.author.id },
{ $inc: { [`stat.${amount}`]: 1 } },
{ upsert: true }
)
await nutsStatsColl.findOneAndUpdate(
{ userID: "__global" },
{ $inc: { [`stat.${amount}`]: 1 } },
{ upsert: true }
)
// Helper for singular/plural
const formatNut = (amount) => `${amount === 1 ? 'eine' : amount} ${amount === 1 ? 'Nuss' : 'Nüsse'}`
const nutsStatsColl =
db.collection(
"stats_nuts"
);
let nutText = `Du hast ${formatNut(amount)} bekommen! :chestnut:`
if (amount <= 0) {
nutText = `Du hast keine Nuss bekommen! :yoink_help:`
}
return await message.reply({
content: nutText
})
}
}
const cooldown =
await claimCooldown(
message.author.id,
"nut",
60 * 60
);
if (
!cooldown.claimed
) {
return message.reply({
content:
`Du kannst erst <t:${cooldown.next}:R> wieder nussen :(`
});
}
const amount =
randomInteger(
0,
9
);
await changeNuts(
message.author.id,
amount
);
await nutsStatsColl.findOneAndUpdate(
{
userId:
message.author.id
},
{
$inc: {
[`stat.${amount}`]:
1
}
},
{
upsert:
true
}
);
await nutsStatsColl.findOneAndUpdate(
{
userId:
"__global"
},
{
$inc: {
[`stat.${amount}`]:
1
}
},
{
upsert:
true
}
);
const formatNut =
amount =>
`${amount === 1 ? "eine" : amount} ${amount === 1 ? "Nuss" : "Nüsse"}`;
let nutText =
`Du hast ${formatNut(amount)} bekommen! :chestnut:`;
if (
amount <=
0
) {
nutText =
"Du hast keine Nuss bekommen! :yoink_help:";
}
return message.reply({
content:
nutText
});
}
};
+210 -64
View File
@@ -1,78 +1,224 @@
const { EmbedBuilder } = require('discord.js')
const { mClient } = require('../../../index')
function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
require('dotenv').config()
const {
EmbedBuilder
} = require("discord.js");
const {
getNutBalance,
transferNuts
} = require(
"../../../utils/nutEconomy"
);
function delay(ms) {
return new Promise(
resolve =>
setTimeout(
resolve,
ms
)
);
}
module.exports = {
name: 'givenuts',
description: 'give nuts',
aliases: ['gn'],
async execute(message, args) {
const from = message.author
if (args[0] == `<@${message.author.id}>` || !args[0]) {
name:
"givenuts",
description:
"give nuts",
aliases: [
"gn"
],
async execute(
message,
args
) {
const from =
message.author;
if (
args[0] ==
`<@${message.author.id}>`
||
!args[0]
) {
return message.reply({
content: "Du musst einen anderen User erwähnen"
}).then(async (msg) => {
await delay(4000)
message.delete()
msg.delete()
content:
"Du musst einen anderen User erwähnen"
})
.then(
async msg => {
await delay(
4000
);
message.delete();
msg.delete();
}
);
}
if (!args[0].startsWith('<@' || !args[0].endsWith('>'))) { return message.reply('Invalid User Specification') }
try {
target = await message.guild.members.fetch(args[0].slice(2, -1))
} catch (error) {
return message.reply({
content: `User: ${args[0]} nicht gefunden!`
}).then(async (msg) => {
await delay(4000)
message.delete()
msg.delete()
})
if (
!args[0].startsWith("<@")
||
!args[0].endsWith(">")
) {
return message.reply(
"Invalid User Specification"
);
}
const to = target
let target;
try {
var amount = Number(args[1])
target =
await message.guild.members.fetch(
args[0].slice(
2,
-1
)
);
} catch (error) {
console.log(error)
return message.reply({
content: 'Du musst eine Menge angeben!'
}).then(async (msg) => {
await delay(4000)
message.delete()
msg.delete()
content:
`User: ${args[0]} nicht gefunden!`
})
}
if (!amount) {
return message.reply({content : 'Danke für nichts, du Hu-'})
.then(
async msg => {
await delay(
4000
);
message.delete();
msg.delete();
}
);
}
const db = mClient.db(process.env.M_DB)
const nutsColl = db.collection('items_nuts')
const yourBalance = await nutsColl.findOne({ userID: from.id })
if (amount <= 0) {
return await message.reply({
content: `Hier wird nicht geklaut >:(\r\nDas gibt **${yourBalance.nuts}** Abzug!`
})
}
if (yourBalance.nuts < amount) {
return await message.reply({
content: 'Du kannst nicht mehr geben als du hast!',
ephemeral: true
})
}
var content = `Du sendest **${amount}** Nüsse an <@${target.id}>`
if (amount == 1) {
content = `Du sendest **eine** Nuss an <@${target.id}>`
}
await nutsColl.findOneAndUpdate({ userID: from.id }, { $inc: { nuts: -amount } }, { upsert: true })
await nutsColl.findOneAndUpdate({ userID: to.id }, { $inc: { nuts: +amount } }, { upsert: true })
const embed = new EmbedBuilder()
.setTitle('Gib Nuss!')
.setDescription(content)
.setThumbnail(target.displayAvatarURL())
return message.reply({ embeds: [embed] })
const to =
target;
let amount;
try {
amount =
Number(
args[1]
);
} catch (error) {
console.log(
error
);
return message.reply({
content:
"Du musst eine Menge angeben!"
})
.then(
async msg => {
await delay(
4000
);
message.delete();
msg.delete();
}
);
}
if (
!amount
) {
return message.reply({
content:
"Danke für nichts, du Hu-"
});
}
const yourBalance =
await getNutBalance(
from.id
);
if (
amount <=
0
) {
return message.reply({
content:
`Hier wird nicht geklaut >:(\r\nDas gibt **${yourBalance}** Abzug!`
});
}
if (
yourBalance <
amount
) {
return message.reply({
content:
"Du kannst nicht mehr geben als du hast!"
});
}
let content =
`Du sendest **${amount}** Nüsse an <@${target.id}>`;
if (
amount ==
1
) {
content =
`Du sendest **eine** Nuss an <@${target.id}>`;
}
await transferNuts(
from.id,
to.id,
amount
);
const embed =
new EmbedBuilder()
.setTitle(
"Gib Nuss!"
)
.setDescription(
content
)
.setThumbnail(
target.displayAvatarURL()
);
return message.reply({
embeds: [
embed
]
});
}
}
};
+159 -46
View File
@@ -1,49 +1,162 @@
const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js')
const { mClient } = require('../../../index')
function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
require('dotenv').config()
const {
EmbedBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle
} = require("discord.js");
const {
getCollections
} = require(
"../../../utils/nutEconomy"
);
module.exports = {
name: 'nutsleaderboard',
description: 'show nuts leaderboard',
aliases: ['nl', 'tn', 'topnuts'],
async execute(message, args) {
const row = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setLabel('◄')
.setStyle(ButtonStyle.Primary)
.setCustomId('nuts_leaderboard_left')
)
.addComponents(
new ButtonBuilder()
.setLabel('🧑')
.setStyle(ButtonStyle.Primary)
.setCustomId('nuts_leaderboard_self')
)
.addComponents(
new ButtonBuilder()
.setLabel('►')
.setStyle(ButtonStyle.Primary)
.setCustomId('nuts_leaderboard_right')
)
const db = mClient.db(process.env.M_DB)
const nutsColl = db.collection('items_nuts')
let skip = 0
const nutsData = await nutsColl.find({ nuts: { $gt: 0 } }).sort({ nuts: -1 }).skip(skip).limit(5).toArray()
let fields
let placements = skip + 1
nutsData.forEach((data) => {
fields = (fields ? fields : '') + (`${placements}. <@${data.userID}> : ${data.nuts} Nuts\r\n`)
})
const embed = new EmbedBuilder()
.setTitle('Nuts Leaderboard')
.setThumbnail(message.guild.iconURL({ dynamic: true}))
.setDescription(fields.toString())
.setColor('#5865F2') // Discord's blurple color
.setFooter({ text: 'Use ◄ ► to navigate' });
name:
"nutsleaderboard",
description:
"show nuts leaderboard",
aliases: [
"nl",
"tn",
"topnuts"
],
async execute(
message,
args
) {
const row =
new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setLabel(
"◄"
)
.setStyle(
ButtonStyle.Primary
)
.setCustomId(
"nuts_leaderboard_left"
),
new ButtonBuilder()
.setLabel(
"🧑"
)
.setStyle(
ButtonStyle.Primary
)
.setCustomId(
"nuts_leaderboard_self"
),
new ButtonBuilder()
.setLabel(
"►"
)
.setStyle(
ButtonStyle.Primary
)
.setCustomId(
"nuts_leaderboard_right"
)
);
const {
nuts
} =
getCollections();
const skip =
0;
const nutsData =
await nuts
.find({
nuts: {
$gt:
0
}
})
.sort({
nuts:
-1
})
.skip(
skip
)
.limit(
5
)
.toArray();
let fields;
let placements =
skip +
1;
nutsData.forEach(
data => {
fields =
(
fields
?
fields
:
""
)
+
`${placements}. <@${data.userId}> : ${data.nuts} Nuts\r\n`;
placements++;
}
);
const embed =
new EmbedBuilder()
.setTitle(
"Nuts Leaderboard"
)
.setThumbnail(
message.guild.iconURL({
dynamic:
true
})
)
.setDescription(
fields.toString()
)
.setColor(
"#5865F2"
)
.setFooter({
text:
"Use ◄ ► to navigate"
});
await message.reply({
embeds: [embed],
components: [row]
})
embeds: [
embed
],
components: [
row
]
});
}
}
};
+1 -1
View File
@@ -1,5 +1,5 @@
const { SlashCommandBuilder } = require("discord.js");
const ping = require("../../../src/ping");
const ping = require("../../../utils/ping");
module.exports = {
data: new SlashCommandBuilder()
+1 -1
View File
@@ -10,7 +10,7 @@ const { mClient } = require('../../..');
require('dotenv').config()
const db = mClient.db(process.env.M_DB); // adjust if needed
const coll = db.collection('items_daily_numbers');
const coll = db.collection('items_ezd');
module.exports = {
data: new SlashCommandBuilder()
+32
View File
@@ -0,0 +1,32 @@
// commands/applications/nutHub.js
const {
SlashCommandBuilder
} = require("discord.js");
const {
showNutHub
} = require(
"../../../utils/nutHub"
);
module.exports = {
data:
new SlashCommandBuilder()
.setName(
"nuthub"
)
.setDescription(
"open the Nut Hub"
),
async execute(
interaction
) {
return showNutHub(
interaction
);
}
};
File diff suppressed because it is too large Load Diff
+168 -30
View File
@@ -60,83 +60,199 @@ async function selectMenuHandler(interaction) {
}
}
function getParticipantWeight(participant, now = Date.now()) {
// Never selected before -> strongly favor them
if (!participant.lastExpertAt) {
return 6;
}
const lastExpertAt = new Date(participant.lastExpertAt).getTime();
// Invalid date safety
if (Number.isNaN(lastExpertAt)) {
return 6;
}
const daysSinceExpert = Math.max(
0,
(now - lastExpertAt) / (24 * 60 * 60 * 1000)
);
/*
* Square-root weighting prevents very old participants
* from completely dominating the random selection.
*
* Examples:
* 1 day -> ~1.41
* 7 days -> ~2.83
* 30 days -> ~5.57
*/
return Math.sqrt(daysSinceExpert + 1);
}
function weightedRandom(participants) {
if (!participants.length) {
return null;
}
const weightedParticipants = participants.map(participant => ({
participant,
weight: getParticipantWeight(participant)
}));
const totalWeight = weightedParticipants.reduce(
(total, item) => total + item.weight,
0
);
let random = Math.random() * totalWeight;
for (const item of weightedParticipants) {
random -= item.weight;
if (random <= 0) {
return item.participant;
}
}
// Floating-point safety fallback
return weightedParticipants[weightedParticipants.length - 1].participant;
}
async function selectWgeParticipant(
participateColl,
{ excludeUserId = null } = {}
) {
const filter = {
status: true
};
if (excludeUserId) {
filter.userID = {
$ne: excludeUserId
};
}
const participants = await participateColl
.find(filter)
.toArray();
return weightedRandom(participants);
}
async function modalSubmitHandler(interaction) {
if (interaction.customId !== 'wgeBaseModal') return;
if (interaction.customId !== 'wgeBaseModal') {
return;
}
const db = mClient.db('neo-db');
const wgeColl = db.collection('timer_wge');
const participateColl = db.collection('items_wge');
const historyWgeColl = db.collection('history_wge');
const assigningUser = interaction.user;
const selectedUserId = interaction.components[0].component.values[0];
const topic = interaction.fields.getTextInputValue('wgeTopicInput');
const defaultAssignedTime = 7 * 24 * 60 * 60 * 1000; // 7 days
// Get current running WGE so we can exclude the current expert
const selectedUserId =
interaction.components[0].component.values[0];
const topic = interaction.fields.getTextInputValue(
'wgeTopicInput'
);
const defaultAssignedTime =
7 * 24 * 60 * 60 * 1000;
// Get current running WGE so the previous expert
// cannot immediately be selected again.
const prevWge = await wgeColl.findOne({
debug: false,
state: 'running'
});
let assignedUser;
let selectedParticipant;
// Random assignment
// ---------------------------------
// RANDOM / WEIGHTED ASSIGNMENT
// ---------------------------------
if (selectedUserId === process.env.D_ID) {
const randomParticipant = await participateColl.aggregate([
selectedParticipant = await selectWgeParticipant(
participateColl,
{
$match: {
status: true,
...(prevWge && {
userID: { $ne: prevWge.assignedUser }
})
}
},
{ $sample: { size: 1 } }
]).toArray();
excludeUserId: prevWge?.assignedUser
}
);
if (randomParticipant.length === 0) {
if (!selectedParticipant) {
return interaction.reply({
content: 'There are no eligible participants available.',
content:
'There are no eligible participants available.',
flags: MessageFlags.Ephemeral
});
}
const member = await interaction.guild.members
.fetch(selectedParticipant.userID)
.catch(() => null);
if (!member) {
return interaction.reply({
content:
'The selected participant could not be found in the server.',
flags: MessageFlags.Ephemeral
});
}
const member = await interaction.guild.members.fetch(randomParticipant[0].userID);
assignedUser = member.user;
}
// Manual assignment
// ---------------------------------
// MANUAL ASSIGNMENT
// ---------------------------------
else {
const participant = await participateColl.findOne({
selectedParticipant = await participateColl.findOne({
userID: selectedUserId,
status: true
});
if (!participant) {
if (!selectedParticipant) {
return interaction.reply({
content: `<@${selectedUserId}> is not participating!`,
content:
`<@${selectedUserId}> is not participating!`,
flags: MessageFlags.Ephemeral
});
}
if (prevWge && selectedUserId === prevWge.assignedUser) {
if (
prevWge &&
selectedUserId === prevWge.assignedUser
) {
return interaction.reply({
content: `<@${selectedUserId}> was the previous expert and cannot be selected again this round.`,
content:
`<@${selectedUserId}> was the previous expert and cannot be selected again this round.`,
flags: MessageFlags.Ephemeral
});
}
assignedUser = { id: selectedUserId };
assignedUser = {
id: selectedUserId
};
}
// Archive previous WGE
// ---------------------------------
// ARCHIVE PREVIOUS WGE
// ---------------------------------
if (prevWge) {
await historyWgeColl.insertOne({
assignedUser: prevWge.assignedUser,
assigningUser: prevWge.assigningUser,
timeRemaining: prevWge.timeRemaining,
topic: prevWge.topic,
debug: prevWge.debug,
createdAt: prevWge.createdAt
});
@@ -150,7 +266,28 @@ async function modalSubmitHandler(interaction) {
});
}
// Create new WGE
const assignedAt = new Date();
// ---------------------------------
// UPDATE PARTICIPANT STATISTICS
// ---------------------------------
await participateColl.updateOne(
{
_id: selectedParticipant._id
},
{
$set: {
lastExpertAt: assignedAt
},
$inc: {
expertCount: 1
}
}
);
// ---------------------------------
// CREATE NEW WGE
// ---------------------------------
await wgeColl.insertOne({
assignedUser: assignedUser.id,
assigningUser: assigningUser.id,
@@ -169,10 +306,11 @@ async function modalSubmitHandler(interaction) {
topic,
debug: false,
createdAt: new Date()
createdAt: assignedAt
});
return interaction.reply({
content: `<@${assignedUser.id}> is the next expert! Their topic is **${topic}**`
content:
`<@${assignedUser.id}> is the next expert! Their topic is **${topic}**`
});
}
+1 -1
View File
@@ -29,7 +29,7 @@ module.exports = {
)?.[1];
if (found) {
const numberedColl = db.collection("items_daily_numbers");
const numberedColl = db.collection("items_ezd");
const today = new Date().toLocaleDateString("en-CA", {
timeZone: "Europe/Berlin"
+204 -54
View File
@@ -1,78 +1,219 @@
const { mClient } = require("..");
const { mClient } = require('..');
async function getMember(client, userId) {
const guild = client.guilds.cache.get(process.env.D_GuildID);
if (!guild) return null;
// cache first (fast path)
let member = guild.members.cache.get(userId);
// fallback (safe path)
if (!member) {
member = await guild.members.fetch(userId).catch(() => null);
if (!guild) {
return null;
}
return member;
return (
guild.members.cache.get(userId) ||
await guild.members.fetch(userId).catch(() => null)
);
}
function getParticipantWeight(participant, now = Date.now()) {
// Never selected before -> strongly favor them
if (!participant.lastExpertAt) {
return 6;
}
const lastExpertAt = new Date(participant.lastExpertAt).getTime();
// Invalid date safety
if (Number.isNaN(lastExpertAt)) {
return 6;
}
const daysSinceExpert = Math.max(
0,
(now - lastExpertAt) / (24 * 60 * 60 * 1000)
);
/*
* Square-root weighting prevents very old participants
* from completely dominating the random selection.
*
* Examples:
* 1 day -> ~1.41
* 7 days -> ~2.83
* 30 days -> ~5.57
*/
return Math.sqrt(daysSinceExpert + 1);
}
function weightedRandom(participants) {
if (!participants.length) {
return null;
}
const weightedParticipants = participants.map(participant => ({
participant,
weight: getParticipantWeight(participant)
}));
const totalWeight = weightedParticipants.reduce(
(total, item) => total + item.weight,
0
);
let random = Math.random() * totalWeight;
for (const item of weightedParticipants) {
random -= item.weight;
if (random <= 0) {
return item.participant;
}
}
// Floating-point safety fallback
return weightedParticipants[weightedParticipants.length - 1].participant;
}
async function selectWgeParticipant(
participateColl,
{ excludeUserId = null } = {}
) {
const filter = {
status: true
};
if (excludeUserId) {
filter.userID = {
$ne: excludeUserId
};
}
const participants = await participateColl
.find(filter)
.toArray();
return weightedRandom(participants);
}
module.exports = {
name: 'timerComplete',
async execute(timer, client) {
const member = await getMember(client, timer.assignedUser);
if (!member) return;
const db = mClient.db('neo-db');
await member.send(
`❌ Timer ran out: **${timer.topic}** ...Re-assigning randomly...`
).catch(() => { });
const wgeColl = db.collection('timer_wge');
const participateColl = db.collection('items_wge');
const channelColl = db.collection('config_channels');
// Reset Timer, Reassign Randomly
const db = mClient.db('neo-db')
const wgeColl = db.collection('timer_wge')
const guild = client.guilds.cache.get(process.env.D_GuildID);
// Randomizer Function
const participateColl = db.collection('items_wge')
const randomParticipant = await participateColl.aggregate([
{ $match: { status: true } },
{ $sample: { size: 1 } }
]).toArray()
if (!guild) {
console.error('timerComplete: Guild not found.');
return;
}
if (!randomParticipant.length) { return interaction.reply({ content: 'No one else is participating!' }) }
// Notify previous expert
const previousMember = await getMember(
client,
timer.assignedUser
);
const wge = await wgeColl.findOne({
$and: [
{ debug: false },
{ state: 'running' }
]
})
if (previousMember) {
await previousMember.send(
`❌ Timer ran out: **${timer.topic}** ...Re-assigning randomly...`
).catch(error => {
console.error(
`timerComplete: Failed to DM ${timer.assignedUser}:`,
error
);
});
}
const channelColl = db.collection('config_channels')
const guild = await client.guilds.cache.get(process.env.D_GuildID);
const wgeChannel = await channelColl.findOne({
$and: [
{ purpose: 'wge' },
{ guildID: process.env.D_GuildID }
]
})
const channel = await client.channels.cache.get(wgeChannel.channelID)
// Get configured WGE channel
const wgeChannelConfig = await channelColl.findOne({
purpose: 'wge',
guildID: process.env.D_GuildID
});
random = await guild.members.fetch(randomParticipant[0].userID)
wgeAssignedUser = random.user
if (!wgeChannelConfig) {
console.error(
'timerComplete: WGE channel configuration not found.'
);
return;
}
const channel = client.channels.cache.get(
wgeChannelConfig.channelID
);
if (!channel) {
console.error('timerComplete: WGE channel not found.');
return;
}
// Weighted random selection.
// Previous expert is explicitly excluded.
const selectedParticipant = await selectWgeParticipant(
participateColl,
{
excludeUserId: timer.assignedUser
}
);
if (!selectedParticipant) {
await channel.send({
content:
`The timer for <@${timer.assignedUser}> has run out, ` +
`but there are currently no other participating users available for reassignment.`
});
return;
}
const newMember = await guild.members
.fetch(selectedParticipant.userID)
.catch(() => null);
if (!newMember) {
console.error(
`timerComplete: Could not fetch selected user ${selectedParticipant.userID}.`
);
return;
}
const newAssignedUser = newMember.user;
const wgeDefaultAssignedTime =
7 * 24 * 60 * 60 * 1000;
const assignedAt = new Date();
/*
* Update participant statistics.
*
* $inc automatically creates expertCount with value 1
* if the field does not exist yet.
*/
await participateColl.updateOne(
{
_id: selectedParticipant._id
},
{
$set: {
lastExpertAt: assignedAt
},
$inc: {
expertCount: 1
}
}
);
// Remove the expired timer
await wgeColl.deleteOne({
_id: wge._id
})
const wgeDefaultAssignedTime = 7 * 24 * 60 * 60 * 1000; // 7 days in ms
channel.send({
content: `The Timer for <@${wge.assignedUser}> has run out! Assigning randomly... \r\n<@${wgeAssignedUser.id}> is the next expert! Their topic still is **${wge.topic}**`
})
_id: timer._id
});
// Create new timer
await wgeColl.insertOne({
assignedUser: wgeAssignedUser.id,
assigningUser: wge.assigningUser,
assignedUser: newAssignedUser.id,
assigningUser: timer.assigningUser,
startedAt: Date.now(),
timeRemaining: wgeDefaultAssignedTime,
@@ -85,11 +226,20 @@ module.exports = {
complete: false
},
topic: wge.topic,
topic: timer.topic,
debug: false,
createdAt: new Date()
createdAt: assignedAt
});
await channel.send({
content:
`The timer for <@${timer.assignedUser}> has run out! Assigning randomly...\n` +
`<@${newAssignedUser.id}> is the next expert! Their topic is still **${timer.topic}**`
});
console.log(
`timerComplete: Reassigned WGE from ${timer.assignedUser} to ${newAssignedUser.id}.`
);
}
};
+208 -29
View File
@@ -1,33 +1,212 @@
const { Client, GatewayIntentBits, Collection, Partials } = require('discord.js')
const { MongoClient } = require('mongodb')
require('dotenv').config()
const mClient = new MongoClient(process.env.M_URI)
exports.mClient = mClient;
const fs = require('node:fs')
const {
Client,
GatewayIntentBits,
Collection,
Partials
} = require("discord.js");
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildPresences,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.DirectMessages,
GatewayIntentBits.MessageContent,
],
partials: [Partials.Channel, Partials.Message, Partials.User, Partials.GuildMember, Partials.Reaction]
});
exports.client = client
const {
MongoClient
} = require("mongodb");
client.commands = new Collection() // slash commands collection
client.legacyCommands = new Collection() // legacy commands collection
client.aliases = new Collection() // list of command aliases
client.buttons = new Collection() // list of buttons
client.selectMenus = new Collection() // list of selectMenus
const fs =
require("node:fs");
fs.readdirSync('./handlers').forEach((handler) => {
require(`./handlers/${handler}`)(client)
});
require("dotenv").config();
client.login(process.env.D_Token)
/*
* ============================================================
* MONGODB
* ============================================================
*/
const mClient =
new MongoClient(
process.env.M_URI
);
/*
* Export this BEFORE handlers/commands are loaded.
*
* utils/nutEconomy.js imports mClient from index.js.
*/
exports.mClient =
mClient;
/*
* ============================================================
* DISCORD CLIENT
* ============================================================
*/
const client =
new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildPresences,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.DirectMessages,
GatewayIntentBits.MessageContent
],
partials: [
Partials.Channel,
Partials.Message,
Partials.User,
Partials.GuildMember,
Partials.Reaction
]
});
exports.client =
client;
/*
* ============================================================
* COLLECTIONS
* ============================================================
*/
client.commands =
new Collection();
client.legacyCommands =
new Collection();
client.aliases =
new Collection();
client.buttons =
new Collection();
client.selectMenus =
new Collection();
/*
* ============================================================
* STARTUP
* ============================================================
*/
async function start() {
try {
/*
* Connect Mongo first.
*/
await mClient.connect();
console.log(
"[MongoDB] Connected"
);
/*
* Load handlers only after MongoDB is ready.
*/
const handlers =
fs.readdirSync(
"./handlers"
);
for (
const handler
of handlers
) {
require(
`./handlers/${handler}`
)(
client
);
}
/*
* Finally connect to Discord.
*/
await client.login(
process.env.D_Token
);
} catch (error) {
console.error(
"Failed to start bot:",
error
);
process.exit(
1
);
}
}
/*
* ============================================================
* SHUTDOWN
* ============================================================
*/
async function shutdown(
signal
) {
console.log(
`[System] ${signal} received, shutting down...`
);
try {
client.destroy();
} catch {}
try {
await mClient.close();
} catch {}
process.exit(
0
);
}
process.on(
"SIGINT",
() =>
shutdown(
"SIGINT"
)
);
process.on(
"SIGTERM",
() =>
shutdown(
"SIGTERM"
)
);
/*
* ============================================================
* START
* ============================================================
*/
start();
File diff suppressed because it is too large Load Diff
+1013
View File
File diff suppressed because it is too large Load Diff
+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
};
+240
View File
@@ -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
};
+975
View File
@@ -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
};
+795
View File
@@ -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
};
View File