Files
ShaiBot/commands/applications/cookies.js
T
2026-08-18 18:57:24 +02:00

1982 lines
37 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const {
SlashCommandBuilder,
EmbedBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
MessageFlags
} = require("discord.js");
const { randomUUID } =
require("node:crypto");
const {
getCollections,
getCookieBalance,
changeCookies,
transferCookies,
getCooldown,
setCooldown,
getUnixTime,
randomInteger,
randomFloat,
randomItem,
formatPercentage
} = require("../../utils/cookieEconomy");
/*
* CONFIG
*/
const COLLECT_COOLDOWN =
60 * 60;
const STEAL_COOLDOWN =
15 * 60;
const SLOT_MIN_BET =
10;
const SLOT_MAX_BET =
1000;
const BOSS_SPAWN_COST =
500;
const BOSS_JOIN_DURATION =
30 * 60;
const BOSS_MINIMUM_PLAYERS =
5;
const ABRA_ID =
"378135175059603457";
const ABRA_MULTIPLIER =
1.5;
/*
* SLOT TABLE
*
* RTP ≈ 94%
*/
const SLOT_OUTCOMES = [
{
weight: 300,
type: "loss",
multiplier: 0
},
{
weight: 390,
type: "pair",
multiplier: 0.5
},
{
weight: 150,
type: "triple",
symbol: "🍋",
multiplier: 1.5
},
{
weight: 80,
type: "triple",
symbol: "🍒",
multiplier: 2
},
{
weight: 45,
type: "triple",
symbol: "🍪",
multiplier: 3
},
{
weight: 25,
type: "triple",
symbol: "⭐",
multiplier: 5
},
{
weight: 10,
type: "triple",
symbol: "💎",
multiplier: 10
}
];
const SLOT_SYMBOLS = [
"🍒",
"🍋",
"🍪",
"⭐",
"💎"
];
module.exports = {
data: new SlashCommandBuilder()
.setName("cookies")
.setDescription(
"The ShaiBot cookie economy."
)
.addSubcommand(subcommand =>
subcommand
.setName("collect")
.setDescription(
"Collect a fresh batch of cookies."
)
)
.addSubcommand(subcommand =>
subcommand
.setName("steal")
.setDescription(
"Attempt to steal another user's cookies."
)
.addUserOption(option =>
option
.setName("target")
.setDescription(
"The unfortunate victim."
)
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName("heist")
.setDescription(
"Attempt to breach the ShaiBot Cookie Vault."
)
)
.addSubcommand(subcommand =>
subcommand
.setName("slots")
.setDescription(
"Play the Shaiwase Cookie Slots."
)
.addIntegerOption(option =>
option
.setName("bet")
.setDescription(
"How many Cookies to wager."
)
.setRequired(true)
.setMinValue(
SLOT_MIN_BET
)
.setMaxValue(
SLOT_MAX_BET
)
)
)
.addSubcommandGroup(group =>
group
.setName("boss")
.setDescription(
"Cookie Boss commands."
)
.addSubcommand(subcommand =>
subcommand
.setName("spawn")
.setDescription(
`Spawn a Cookie Boss for ${BOSS_SPAWN_COST} Cookies.`
)
)
)
.addSubcommand(subcommand =>
subcommand
.setName("check")
.setDescription(
"Check a Cookie balance."
)
.addUserOption(option =>
option
.setName("target")
.setDescription(
"The user whose Cookies you want to check."
)
)
)
.addSubcommand(subcommand =>
subcommand
.setName("give")
.setDescription(
"Give some of your Cookies to another user."
)
.addUserOption(option =>
option
.setName("target")
.setDescription(
"The recipient."
)
.setRequired(true)
)
.addIntegerOption(option =>
option
.setName("amount")
.setDescription(
"How many Cookies to give."
)
.setRequired(true)
.setMinValue(1)
)
)
.addSubcommand(subcommand =>
subcommand
.setName("leaderboard")
.setDescription(
"Show the richest Cookie collectors."
)
),
async execute(interaction) {
const group =
interaction.options
.getSubcommandGroup(false);
const subcommand =
interaction.options
.getSubcommand();
if (
group === "boss" &&
subcommand === "spawn"
) {
return spawnBoss(
interaction
);
}
switch (subcommand) {
case "collect":
return collect(
interaction
);
case "steal":
return steal(
interaction
);
case "heist":
return heist(
interaction
);
case "slots":
return slots(
interaction
);
case "check":
return check(
interaction
);
case "give":
return give(
interaction
);
case "leaderboard":
return leaderboard(
interaction
);
}
}
};
/*
* COLLECT
*/
async function collect(interaction) {
const userId =
interaction.user.id;
const now =
getUnixTime();
const cooldown =
await getCooldown(
userId,
interaction.guildId,
"collect"
);
if (cooldown > now) {
return interaction.reply({
content:
`The batch is still cooking. Come back <t:${cooldown}:R> 🍪`,
flags:
MessageFlags.Ephemeral
});
}
let amount =
randomInteger(
1,
20
);
if (
userId ===
ABRA_ID
) {
amount =
Math.ceil(
amount *
ABRA_MULTIPLIER
);
}
await changeCookies(
userId,
amount
);
await setCooldown(
userId,
interaction.guildId,
"collect",
COLLECT_COOLDOWN
);
const balance =
await getCookieBalance(
userId
);
const embed =
new EmbedBuilder()
.setTitle(
"🍪 Fresh Batch"
)
.setDescription(
randomCollectResponse(
amount,
interaction.user
)
)
.addFields({
name:
"Your Vault",
value:
`**${balance.toLocaleString()} Cookies**`
});
return interaction.reply({
embeds: [
embed
]
});
}
/*
* NORMAL PLAYER STEAL
*/
async function steal(interaction) {
const user =
interaction.user;
const target =
interaction.options.getUser(
"target",
true
);
if (
target.id ===
user.id
) {
return interaction.reply({
content:
"You can't steal from yourself.",
flags:
MessageFlags.Ephemeral
});
}
/*
* ShaiBot itself is reserved for /cookies heist.
*/
if (
target.id ===
interaction.client.user.id
) {
return interaction.reply({
content:
"The ShaiBot Vault requires a proper `/cookies heist`.",
flags:
MessageFlags.Ephemeral
});
}
if (target.bot) {
return interaction.reply({
content:
"Other bots are protected by Cookie Law.",
flags:
MessageFlags.Ephemeral
});
}
const now =
getUnixTime();
const cooldown =
await getCooldown(
user.id,
interaction.guildId,
"steal"
);
if (cooldown > now) {
return interaction.reply({
content:
`You can steal again <t:${cooldown}:R>.`,
flags:
MessageFlags.Ephemeral
});
}
const [
yourCookies,
theirCookies
] =
await Promise.all([
getCookieBalance(
user.id
),
getCookieBalance(
target.id
)
]);
if (yourCookies < 1) {
return interaction.reply({
content:
"You need at least **1 Cookie** to attempt a robbery.",
flags:
MessageFlags.Ephemeral
});
}
if (theirCookies < 1) {
return interaction.reply({
content:
`${target} doesn't have any Cookies.`,
flags:
MessageFlags.Ephemeral
});
}
const successChance =
getStealChance(
yourCookies,
theirCookies
);
const success =
Math.random() <
successChance;
let embed;
if (success) {
const {
amount
} =
getStealAmount(
theirCookies
);
await transferCookies(
target.id,
user.id,
amount
);
const actualPercentage =
(
amount /
theirCookies *
100
);
embed =
new EmbedBuilder()
.setTitle(
"🥷 Cookie Robbery Successful"
)
.setDescription(
randomStealSuccessResponse(
amount,
target
)
)
.addFields(
{
name:
"Vault Taken",
value:
`**${actualPercentage.toFixed(1)}%**`,
inline:
true
},
{
name:
"Success Chance",
value:
`**${formatPercentage(successChance)}**`,
inline:
true
}
);
} else {
const penaltyPercentage =
randomFloat(
0.01,
0.08
);
const penalty =
Math.max(
1,
Math.min(
yourCookies,
Math.floor(
yourCookies *
penaltyPercentage
)
)
);
/*
* Normal steal failure pays the victim.
*/
await transferCookies(
user.id,
target.id,
penalty
);
embed =
new EmbedBuilder()
.setTitle(
"🚨 Cookie Robbery Failed"
)
.setDescription(
randomStealFailResponse(
penalty,
target
)
)
.addFields(
{
name:
"Success Chance",
value:
`**${formatPercentage(successChance)}**`,
inline:
true
}
);
}
const nextSteal =
await setCooldown(
user.id,
interaction.guildId,
"steal",
STEAL_COOLDOWN
);
embed.addFields({
name:
"Next Robbery",
value:
`<t:${nextSteal}:R>`
});
return interaction.reply({
embeds: [
embed
]
});
}
/*
* HEIST ENTRY
*/
async function heist(interaction) {
const vaultId =
interaction.client.user.id;
const [
vault,
player
] =
await Promise.all([
getCookieBalance(
vaultId
),
getCookieBalance(
interaction.user.id
)
]);
if (player < 1) {
return interaction.reply({
content:
"You need Cookies before attempting a vault heist.",
flags:
MessageFlags.Ephemeral
});
}
if (vault < 1) {
return interaction.reply({
content:
"The ShaiBot Vault is currently empty.",
flags:
MessageFlags.Ephemeral
});
}
const security =
getSecurityLabel(
vault
);
const embed =
new EmbedBuilder()
.setTitle(
"🏦 ShaiBot Cookie Vault"
)
.setDescription(
[
`**Vault:** ${vault.toLocaleString()} 🍪`,
`**Security:** ${security}`,
"",
"Choose your approach.",
"",
"🥷 **Sneak** — safer, smaller haul.",
"💻 **Hack** — balanced risk and reward.",
"💥 **Smash** — dangerous, but lucrative."
].join("\n")
)
.setFooter({
text:
`cookieHeist:${interaction.user.id}`
});
const row =
new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(
"cookieHeistSneak"
)
.setLabel(
"Sneak"
)
.setEmoji(
"🥷"
)
.setStyle(
ButtonStyle.Primary
),
new ButtonBuilder()
.setCustomId(
"cookieHeistHack"
)
.setLabel(
"Hack"
)
.setEmoji(
"💻"
)
.setStyle(
ButtonStyle.Secondary
),
new ButtonBuilder()
.setCustomId(
"cookieHeistSmash"
)
.setLabel(
"Smash"
)
.setEmoji(
"💥"
)
.setStyle(
ButtonStyle.Danger
)
);
return interaction.reply({
embeds: [
embed
],
components: [
row
]
});
}
/*
* SLOTS
*/
async function slots(interaction) {
const bet =
interaction.options.getInteger(
"bet",
true
);
const playerId =
interaction.user.id;
const vaultId =
interaction.client.user.id;
const [
playerBalance,
vaultBalance
] =
await Promise.all([
getCookieBalance(
playerId
),
getCookieBalance(
vaultId
)
]);
if (
playerBalance <
bet
) {
return interaction.reply({
content:
"You don't have enough Cookies for that bet.",
flags:
MessageFlags.Ephemeral
});
}
/*
* The house needs enough BEFORE the wager
* to honor a 10x payout after receiving the bet.
*/
if (
vaultBalance <
bet * 9
) {
return interaction.reply({
content:
"The ShaiBot Vault doesn't currently have enough liquidity for that wager.",
flags:
MessageFlags.Ephemeral
});
}
await transferCookies(
playerId,
vaultId,
bet
);
const spinning =
new EmbedBuilder()
.setTitle(
"🎰 Shaiwase Cookie Slots"
)
.setDescription(
buildSlotDisplay(
generateRandomGrid()
)
)
.addFields({
name:
"Bet",
value:
`**${bet.toLocaleString()} 🍪**`
});
await interaction.reply({
embeds: [
spinning
]
});
await sleep(750);
await interaction.editReply({
embeds: [
EmbedBuilder
.from(spinning)
.setDescription(
buildSlotDisplay(
generateRandomGrid()
)
)
]
});
await sleep(750);
await interaction.editReply({
embeds: [
EmbedBuilder
.from(spinning)
.setDescription(
buildSlotDisplay(
generateRandomGrid()
)
)
]
});
await sleep(750);
const outcome =
rollSlotOutcome();
const grid =
generateOutcomeGrid(
outcome
);
const payout =
Math.floor(
bet *
outcome.multiplier
);
if (payout > 0) {
await transferCookies(
vaultId,
playerId,
payout
);
}
const [
finalPlayer,
finalVault
] =
await Promise.all([
getCookieBalance(
playerId
),
getCookieBalance(
vaultId
)
]);
const profit =
payout - bet;
const finalEmbed =
new EmbedBuilder()
.setTitle(
"🎰 Shaiwase Cookie Slots"
)
.setDescription(
buildSlotDisplay(
grid
)
)
.addFields(
{
name:
"Result",
value:
getSlotResultName(
outcome
)
},
{
name:
"Bet",
value:
`${bet.toLocaleString()} 🍪`,
inline:
true
},
{
name:
"Multiplier",
value:
`×${outcome.multiplier}`,
inline:
true
},
{
name:
"Payout",
value:
`${payout.toLocaleString()} 🍪`,
inline:
true
},
{
name:
"Profit",
value:
`${profit >= 0 ? "+" : ""}${profit.toLocaleString()} 🍪`,
inline:
true
},
{
name:
"Your Vault",
value:
`${finalPlayer.toLocaleString()} 🍪`,
inline:
true
},
{
name:
"House Vault",
value:
`${finalVault.toLocaleString()} 🍪`,
inline:
true
}
);
return interaction.editReply({
embeds: [
finalEmbed
]
});
}
/*
* BOSS SPAWN
*/
async function spawnBoss(interaction) {
const {
db
} =
getCollections();
const events =
db.collection(
"items_events"
);
/*
* One unresolved boss per guild.
*/
const existing =
await events.findOne({
eventType:
"CookieBoss",
guildId:
interaction.guildId,
state: {
$in: [
"Joining",
"Active",
"Starting",
"Resolving"
]
}
});
if (existing) {
return interaction.reply({
content:
"There is already an active Cookie Boss in this server.",
flags:
MessageFlags.Ephemeral
});
}
const balance =
await getCookieBalance(
interaction.user.id
);
if (
balance <
BOSS_SPAWN_COST
) {
return interaction.reply({
content:
`You need **${BOSS_SPAWN_COST} Cookies** to summon the boss.`,
flags:
MessageFlags.Ephemeral
});
}
const eventId =
randomUUID();
const escrowId =
`boss:${eventId}`;
const now =
getUnixTime();
const joinUntil =
now +
BOSS_JOIN_DURATION;
/*
* Spawner fee seeds the encounter pool.
*/
await transferCookies(
interaction.user.id,
escrowId,
BOSS_SPAWN_COST
);
const embed =
new EmbedBuilder()
.setTitle(
"🍪 The Crumbbound Behemoth Appears!"
)
.setDescription(
[
"*Drawn by the scent of an irresponsibly large Cookie Vault,",
"the Crumbbound Behemoth has lumbered into Shaiwase.*",
"",
`**Recruitment ends:** <t:${joinUntil}:R>`,
`**Minimum adventurers:** ${BOSS_MINIMUM_PLAYERS}`,
`**Reward Pool:** ${BOSS_SPAWN_COST.toLocaleString()} 🍪`,
"",
"Choose how many Cookies you want to commit to the fight.",
"Your entry tier determines your temporary HP and Power."
].join("\n")
)
.setFooter({
text:
`cookieBoss:${eventId}`
});
const joinRow =
new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(
"cookieBossJoinScout"
)
.setLabel(
"Scout — 100"
)
.setEmoji(
"🍪"
)
.setStyle(
ButtonStyle.Secondary
),
new ButtonBuilder()
.setCustomId(
"cookieBossJoinFighter"
)
.setLabel(
"Fighter — 500"
)
.setEmoji(
"⚔️"
)
.setStyle(
ButtonStyle.Primary
),
new ButtonBuilder()
.setCustomId(
"cookieBossJoinChampion"
)
.setLabel(
"Champion — 1000"
)
.setEmoji(
"🔥"
)
.setStyle(
ButtonStyle.Danger
)
);
const startRow =
new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(
"cookieBossStart"
)
.setLabel(
"Start Battle"
)
.setEmoji(
"⚔️"
)
.setStyle(
ButtonStyle.Success
)
);
const message =
await interaction.reply({
embeds: [
embed
],
components: [
joinRow,
startRow
],
fetchReply:
true
});
await events.insertOne({
eventType:
"CookieBoss",
eventId,
escrowId,
guildId:
interaction.guildId,
channelId:
interaction.channelId,
messageId:
message.id,
creatorId:
interaction.user.id,
state:
"Joining",
createdAt:
now,
joinUntil,
battleStartedAt:
null,
battleEndsAt:
null,
minimumParticipants:
BOSS_MINIMUM_PLAYERS,
rewardPool:
BOSS_SPAWN_COST,
boss: {
bossId:
"crumbbound-behemoth",
name:
"The Crumbbound Behemoth",
health:
15000,
maxHealth:
15000,
rage:
0
},
participants:
{}
});
}
/*
* CHECK
*/
async function check(interaction) {
const target =
interaction.options.getUser(
"target"
)
??
interaction.user;
const balance =
await getCookieBalance(
target.id
);
const embed =
new EmbedBuilder()
.setTitle(
"🍪 Cookie Vault"
)
.setDescription(
`${target} has **${balance.toLocaleString()} Cookies**.`
);
return interaction.reply({
embeds: [
embed
]
});
}
/*
* GIVE
*/
async function give(interaction) {
const user =
interaction.user;
const target =
interaction.options.getUser(
"target",
true
);
const amount =
interaction.options.getInteger(
"amount",
true
);
if (
target.id ===
user.id
) {
return interaction.reply({
content:
"You cannot give Cookies to yourself.",
flags:
MessageFlags.Ephemeral
});
}
if (target.bot) {
return interaction.reply({
content:
"Use the Cookie games if you want to feed the house.",
flags:
MessageFlags.Ephemeral
});
}
try {
await transferCookies(
user.id,
target.id,
amount
);
} catch (error) {
if (
error.message ===
"INSUFFICIENT_COOKIES"
) {
return interaction.reply({
content:
"You don't have enough Cookies.",
flags:
MessageFlags.Ephemeral
});
}
throw error;
}
const embed =
new EmbedBuilder()
.setTitle(
"🍪 Cookies Shared"
)
.setDescription(
`${user} gave ${target} **${amount.toLocaleString()} Cookies**.`
);
return interaction.reply({
embeds: [
embed
]
});
}
/*
* LEADERBOARD
*/
async function leaderboard(interaction) {
const {
cookies
} =
getCollections();
/*
* Only Discord snowflake-style account IDs.
*
* This prevents boss escrow accounts from appearing.
*/
const results =
await cookies
.find({
userId: {
$regex:
/^\d+$/
},
cookies: {
$gt: 0
}
})
.sort({
cookies: -1
})
.limit(10)
.toArray();
const description =
results.length
? results
.map(
(
record,
index
) => {
return (
`${getLeaderboardMedal(index)} ` +
`<@${record.userId}> — ` +
`**${record.cookies.toLocaleString()}** 🍪`
);
}
)
.join("\n")
: "Nobody has collected any Cookies yet.";
const embed =
new EmbedBuilder()
.setTitle(
"🍪 Cookie Leaderboard"
)
.setDescription(
description
)
.setTimestamp();
return interaction.reply({
embeds: [
embed
]
});
}
/*
* STEAL HELPERS
*/
function getStealChance(
attackerCookies,
targetCookies
) {
const MIN_CHANCE =
0.15;
const MAX_CHANCE =
0.70;
const BASE_CHANCE =
0.50;
const targetPenalty =
Math.log10(
targetCookies + 1
) *
0.06;
const wealthRatio =
attackerCookies /
Math.max(
targetCookies,
1
);
const povertyBonus =
wealthRatio < 1
? (
1 -
wealthRatio
) *
0.18
: 0;
const bullyPenalty =
wealthRatio > 1
? Math.min(
Math.log10(
wealthRatio + 1
) *
0.12,
0.20
)
: 0;
return Math.max(
MIN_CHANCE,
Math.min(
MAX_CHANCE,
BASE_CHANCE
-
targetPenalty
+
povertyBonus
-
bullyPenalty
)
);
}
function getStealAmount(
targetCookies
) {
const MAX_STEAL_AMOUNT =
5000;
const percentage =
getStealPercentage();
const amount =
Math.max(
1,
Math.min(
Math.floor(
targetCookies *
percentage
),
targetCookies,
MAX_STEAL_AMOUNT
)
);
return {
amount,
percentage
};
}
function getStealPercentage() {
const roll =
Math.random();
if (roll < 0.50)
return randomFloat(
0.01,
0.03
);
if (roll < 0.80)
return randomFloat(
0.03,
0.05
);
if (roll < 0.95)
return randomFloat(
0.05,
0.08
);
return randomFloat(
0.08,
0.10
);
}
/*
* SLOT HELPERS
*/
function rollSlotOutcome() {
const total =
SLOT_OUTCOMES.reduce(
(
sum,
outcome
) =>
sum +
outcome.weight,
0
);
let roll =
Math.random() *
total;
for (
const outcome
of SLOT_OUTCOMES
) {
roll -=
outcome.weight;
if (roll <= 0)
return outcome;
}
return SLOT_OUTCOMES[0];
}
function generateRandomGrid() {
return Array.from(
{
length: 3
},
() =>
Array.from(
{
length: 3
},
() =>
randomItem(
SLOT_SYMBOLS
)
)
);
}
function generateOutcomeGrid(
outcome
) {
const grid =
generateRandomGrid();
if (
outcome.type ===
"triple"
) {
grid[1] = [
outcome.symbol,
outcome.symbol,
outcome.symbol
];
return grid;
}
if (
outcome.type ===
"pair"
) {
const symbol =
randomItem(
SLOT_SYMBOLS
);
let other;
do {
other =
randomItem(
SLOT_SYMBOLS
);
} while (
other === symbol
);
grid[1] = [
symbol,
symbol,
other
];
return grid;
}
/*
* Guaranteed non-pair middle line.
*/
let line;
do {
line = [
randomItem(
SLOT_SYMBOLS
),
randomItem(
SLOT_SYMBOLS
),
randomItem(
SLOT_SYMBOLS
)
];
} while (
new Set(line).size < 3
);
grid[1] =
line;
return grid;
}
function buildSlotDisplay(
grid
) {
return [
"```",
`${grid[0][0]} │ ${grid[0][1]} │ ${grid[0][2]}`,
`${grid[1][0]} │ ${grid[1][1]} │ ${grid[1][2]}`,
`${grid[2][0]} │ ${grid[2][1]} │ ${grid[2][2]}`,
"```"
].join("\n");
}
function getSlotResultName(
outcome
) {
if (
outcome.type ===
"loss"
) {
return "No match";
}
if (
outcome.type ===
"pair"
) {
return "Pair";
}
switch (
outcome.symbol
) {
case "🍋":
return "Triple Lemon";
case "🍒":
return "Triple Cherry";
case "🍪":
return "Triple Cookie";
case "⭐":
return "Triple Star";
case "💎":
return "💎 JACKPOT 💎";
default:
return "Triple";
}
}
function getSecurityLabel(
vault
) {
if (vault < 1000)
return "LOW";
if (vault < 10000)
return "MODERATE";
if (vault < 50000)
return "HIGH";
if (vault < 250000)
return "VERY HIGH";
return "MAXIMUM";
}
/*
* TEXT
*/
function randomCollectResponse(
amount,
user
) {
return randomItem([
`${user}, I made you **${amount} Cookies** 🍪`,
`${user}, here's **${amount} Cookies**. I ate the rest.`,
`${user}, your **${amount} Cookies** are fresh out of the oven.`,
`A wild baker appeared and handed ${user} **${amount} Cookies**.`,
`${user}, Abra insists these **${amount} Cookies** are totally legitimate.`
]);
}
function randomStealSuccessResponse(
amount,
target
) {
return randomItem([
`You stole **${amount.toLocaleString()} Cookies** from ${target}!`,
`Clean getaway. ${target} is **${amount.toLocaleString()} Cookies** poorer.`,
`The Cookie Vault was breached for **${amount.toLocaleString()} Cookies**.`
]);
}
function randomStealFailResponse(
amount,
target
) {
return randomItem([
`${target} caught you and took **${amount.toLocaleString()} Cookies**.`,
`The robbery failed. ${target} gained **${amount.toLocaleString()} Cookies**.`,
`Cookie Police confiscated **${amount.toLocaleString()} Cookies** and handed them to ${target}.`
]);
}
function getLeaderboardMedal(
index
) {
switch (index) {
case 0:
return "🥇";
case 1:
return "🥈";
case 2:
return "🥉";
default:
return `**${index + 1}.**`;
}
}
function sleep(ms) {
return new Promise(
resolve =>
setTimeout(
resolve,
ms
)
);
}