Files
ShaiBot/commands/cookies.js
T
2025-08-14 16:15:16 +02:00

313 lines
14 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.
// cookies.js — rewritten & rebalanced
const { MongoClient, ServerApiVersion } = require('mongodb');
require('dotenv').config();
const cookieSchema = require("../schemas/cookieSchema"); // expected to support findOne, findOneAndUpdate, updateOne, find
const cooldownSchema = require("../schemas/cooldownSchema"); // expected to support findOne, findOneAndUpdate
const Discord = require('discord.js');
const URI = process.env.URI;
if (!URI) throw new Error("Missing process.env.URI for MongoDB");
// single client reused
const client = new MongoClient(URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
serverApi: ServerApiVersion.v1
});
client.connect().catch(err => {
console.error("Mongo connect error:", err);
});
// util
const msToTime = (duration) => {
const ms = Math.floor((duration % 1000) / 100);
const s = Math.floor((duration / 1000) % 60);
const m = Math.floor((duration / (1000 * 60)) % 60);
const h = Math.floor((duration / (1000 * 60 * 60)) % 24);
const pad = n => (n < 10 ? "0" + n : "" + n);
return `${pad(h)}:${pad(m)}:${pad(s)}`;
};
const delay = ms => new Promise(r => setTimeout(r, ms));
// --- Helpers ---
async function getCookies(userId) {
const rec = await cookieSchema.findOne({ _id: userId });
return rec && typeof rec.cookies === 'number' ? rec.cookies : 0;
}
async function changeCookies(userId, delta, username = null) {
// upsert and set username if provided
const update = { $inc: { cookies: delta } };
if (username) update.$set = { user: username };
return cookieSchema.findOneAndUpdate(
{ _id: userId },
update,
{ upsert: true, returnDocument: 'after' }
);
}
async function getCooldown(uniqueId) {
const rec = await cooldownSchema.findOne({ _id: uniqueId });
return rec && rec.cooldown ? rec.cooldown : 0;
}
async function setCooldown(uniqueId, command, subcommand, msTimestamp) {
await cooldownSchema.findOneAndUpdate(
{ _id: uniqueId },
{ _id: uniqueId, command, subcommand, cooldown: msTimestamp },
{ upsert: true }
);
}
function cooldownId(guildId, userId, command, subcommand) {
return `${guildId}-${userId}-${command}-${subcommand}`;
}
// --- Game logic & rebalances ---
// Notes about balancing:
// - Collect: 1..20 cookies (consistent reward); Abra (special user id) gets 1.5x.
// - Steal:
// * successChance = attacker / (attacker + defender), clamped to [0.10, 0.90]
// * steal amount: random 1..ceil(defender/4)
// * failure penalty: attacker loses random 1..ceil(attacker/6) (transferred to defender)
// - Cooldowns: collect = 1 hour, steal = 15 minutes
const ABRA_ID = "378135175059603457"; // kept from original for bonus
async function collect(interaction, user) {
const sub = interaction.options._subcommand;
const id = cooldownId(interaction.guildId, interaction.user.id, interaction.commandName, sub);
const now = Date.now();
const existing = await getCooldown(id);
if (existing > now) {
return interaction.reply({
content: `The batch is still cooking, come back in **${msToTime(existing - now)}**`,
ephemeral: true
});
}
// set cooldown (1 hour)
const cooldownUntil = now + 60 * 60 * 1000;
await setCooldown(id, interaction.commandName, sub, cooldownUntil);
// balanced collect amount: 1..20
let amount = Math.floor(Math.random() * 20) + 1;
if (user.id === ABRA_ID) amount = Math.ceil(amount * 1.5);
// update DB
await changeCookies(user.id, amount, user.username);
// playful responses (kept / adapted)
const collectResponses = (amount, user) => {
const s = amount === 1 ? 'Cookie' : 'Cookies';
return [
`${user}, I made you **${amount}** ${s} 🍪`,
`${user}, here's your **${amount}** ${s} 🍪 I ate the rest, sorry >.<`,
`Only **${amount}** ${s} 🍪 for you today ${user}... Abra stole the rest :c`,
`${user}, would you like a Glass of Milk with your **${amount}** ${s}? 🍪`,
`**${amount}** ${s} 🍪 Careful, ${user}! They're still warm :3`,
`**${amount}** ${s} 🍪 Best Batch yet!`,
`**${amount}** ${s} 🍪 These, I bequeath unto thine own self upon this day. Thou must guard them safely.`,
`${user}, your **${amount}** ${s} 🍪 are fresh out of the oven... try not to burn your tongue!`,
`${user}, I bribed the Cookie Monster to spare these **${amount}** ${s} 🍪 just for you.`,
`${user}, Santa came early! You got **${amount}** ${s} 🍪 (milk not included).`,
`${user}, your **${amount}** ${s} 🍪 are artisanal, gluten-free, and slightly cursed.`,
`A wild baker appeared! They hand you **${amount}** ${s} 🍪`,
`${user}, Abra said these **${amount}** ${s} 🍪 are "totally not stolen"... 🤔`,
`${user}, here’s your **${amount}** ${s} 🍪 — watch out, they may be booby-trapped.`
];
};
const responses = collectResponses(amount, user);
const i = Math.floor(Math.random() * responses.length);
return interaction.reply({ content: responses[i] });
}
async function steal(interaction, user) {
const target = interaction.options.getUser('target');
if (!target) {
return interaction.reply({ content: `Pick someone to steal from.`, ephemeral: true });
}
if (target.id === user.id) {
return interaction.reply({
content: `C'mon, don't embarrass yourself! We all know you couldn't do that, even if you tried.`,
ephemeral: true
});
}
// pull balances
const your = await getCookies(user.id);
const their = await getCookies(target.id);
if (your <= 0) {
return interaction.reply({ content: `You need at least 1 Cookie to attempt a Robbery`, ephemeral: true });
}
if (their <= 0) {
return interaction.reply({ content: `Shame on you! ${target.username} doesn't have any Cookies D:`, ephemeral: true });
}
// steal cooldown (15 minutes)
const sub = interaction.options._subcommand;
const id = cooldownId(interaction.guildId, interaction.user.id, interaction.commandName, sub);
const now = Date.now();
const existing = await getCooldown(id);
const chaos = true // 50% Stela Chance Mode
if (existing > now) {
return interaction.reply({
content: `Hold up — try stealing again in **${msToTime(existing - now)}**`,
ephemeral: true
});
}
await setCooldown(id, interaction.commandName, sub, now + 15 * 60 * 1000);
// success chance and clamping
let successChance = your / (your + their);
if (isNaN(successChance) || !isFinite(successChance)) successChance = 0.2;
if (chaos) {
successChance = 0.5
} else {
successChance = Math.max(0.10, Math.min(0.90, successChance));
}
const success = Math.random() < successChance;
if (success) {
const stealSuccessResponses = (amount, target) => {
const s = amount === 1 ? 'Cookie' : 'Cookies';
return [
`You've successfully stolen **${amount}** ${s} from **${target.username}**!`,
`Ninja mode: activated. You snatched **${amount}** ${s} from **${target.username}** without a crumb left behind.`,
`Abra taught you well — **${amount}** ${s} from **${target.username}** are now yours!`,
`Like a phantom in the night, you took **${amount}** ${s} from **${target.username}**.`,
`You just pulled the sweetest heist: **${amount}** ${s} from **${target.username}**!`,
`Mission complete. **${target.username}** is **${amount}** ${s} poorer, you are richer.`,
`You distracted **${target.username}** with a dance and swiped **${amount}** ${s}.`,
`Clean getaway! **${target.username}** won’t know about the missing **${amount}** ${s} until it’s too late.`,
`The Cookie Vault has been breached. **${amount}** ${s} taken from **${target.username}**.`,
`You emerged from the shadows with **${amount}** ${s} from **${target.username}**.`
];
};
const maxSteal = Math.max(1, Math.ceil(their / 4)); // steal up to ~25% of victim
let amount = Math.floor(Math.random() * maxSteal) + 1;
amount = Math.min(amount, their);
// transfer
await Promise.all([
changeCookies(user.id, amount, user.username),
changeCookies(target.id, -amount, target.username)
]);
const responses = stealSuccessResponses(amount, target);
const i = Math.floor(Math.random() * responses.length);
return interaction.reply({ content: `${responses[i]} (Chance: ${Math.round(successChance * 100, 2)}%)` });
} else {
const stealFailResponses = (penalty, target) => {
const s = penalty === 1 ? 'Cookie' : 'Cookies';
return [
`You were caught red-handed by **${target.username}** and lost **${penalty}** ${s} as a penalty!`,
`Fail! **${target.username}** caught you mid-bite — you dropped **${penalty}** ${s}.`,
`Not your best moment... You lost **${penalty}** ${s} to **${target.username}** in the struggle.`,
`Stealth level: 0. You handed **${penalty}** ${s} to **${target.username}** as an apology.`,
`The cookie jar was booby-trapped! You gave **${penalty}** ${s} to **${target.username}** while running away.`,
`Ouch. The guard dog barked, and now **${target.username}** has your **${penalty}** ${s}.`,
`You tripped over your own shoelace — **${target.username}** reclaimed **${penalty}** ${s}.`,
`Caught by the Cookie Police! **${penalty}** ${s} confiscated and given to **${target.username}**.`,
`Your pockets had holes... **${target.username}** picked up **${penalty}** ${s} you dropped.`
];
};
// failure penalty: attacker loses a little (1..ceil(your/6)) to the victim
const maxPenalty = Math.max(1, Math.ceil(your / 6));
const penalty = Math.min(your, Math.floor(Math.random() * maxPenalty) + 1);
await Promise.all([
changeCookies(user.id, -penalty, user.username),
changeCookies(target.id, +penalty, target.username)
]);
const responses = stealFailResponses(penalty, target);
const i = Math.floor(Math.random() * responses.length);
return interaction.reply({ content: `${responses[i]} (Chance: ${Math.round(successChance * 100, 2)}%)` });
}
}
async function check(interaction, user) {
let target = interaction.options.getUser('target');
if (!target) target = user;
const cookies = await getCookies(target.id);
const mood = cookies === 0 ? '😔' : '😲';
return interaction.reply({ content: `**${target.username}** has **${cookies}** Cookies! ${mood}` });
}
async function give(interaction, user) {
const target = interaction.options.getUser('target');
const amount = interaction.options.getInteger('amount');
if (!target) return interaction.reply({ content: `You need to specify a target.`, ephemeral: true });
if (target.id === user.id) return interaction.reply({ content: 'You cannot give yourself Cookies ._."', ephemeral: true });
if (!Number.isInteger(amount) || amount < 1) {
return interaction.reply({ content: `**${user}** shared *checks notes* **${amount}** Cookie(s) with ${target}....wait a minute!!`, ephemeral: true });
}
const your = await getCookies(user.id);
if (your < amount) return interaction.reply({ content: `We don't run a charity here, get more Cookies!`, ephemeral: true });
await Promise.all([
changeCookies(user.id, -amount, user.username),
changeCookies(target.id, +amount, target.username)
]);
// small delay to mimic previous behavior
await delay(200);
return interaction.reply({ content: `**${user}** shared **${amount}** Cookie(s) with ${target}` });
}
async function leader(interaction) {
const results = await cookieSchema.find().sort({ cookies: -1 }).limit(10);
let cookieSorted = '';
results.forEach((r, i) => {
cookieSorted += `[${i + 1}] <@${r._id}> (${r.cookies})\n`;
});
const embed = new Discord.MessageEmbed()
.setColor(0x0099FF)
.setThumbnail(interaction.guild.iconURL())
.setTimestamp()
.addFields({ name: `-Cookie Leaderboard-`, value: cookieSorted || 'No cookie data yet.' });
return interaction.reply({ embeds: [embed] });
}
module.exports = {
slash: true,
description: 'collect a cookie',
category: 'Misc',
options: [
{ name: "collect", description: "want some?", type: 1 },
{ name: "steal", description: "try your luck at a lala robbery!", type: 1, options: [{ name: "target", description: "someone", required: true, type: 6 }] },
{ name: "check", description: "check your cookie stash", type: 1, options: [{ name: "target", description: "someone", type: 6 }] },
{ name: "give", description: "share your cookies with someone", type: 1, options: [{ name: "target", description: "someone", required: true, type: 6 }, { name: "amount", description: "cookies", required: true, type: 4 }] },
{ name: "leader", description: "show Top 10 Cookie Clickers", type: 1 }
],
callback: ({ interaction, user }) => {
const sub = interaction.options._subcommand;
switch (sub) {
case "collect": return collect(interaction, user);
case "steal": return steal(interaction, user);
case "check": return check(interaction, user);
case "give": return give(interaction, user);
case "leader": return leader(interaction);
default: return interaction.reply({ content: 'Unknown subcommand', ephemeral: true });
}
}
};