From 8bdd6868e862e8c15f20a7a37223eab2540c6121 Mon Sep 17 00:00:00 2001 From: DeSqBlocki Date: Sat, 15 Aug 2026 08:44:24 +0200 Subject: [PATCH] added more steal logic --- commands/applications/cookies.js | 605 ++++++++++++++++++++++++------- 1 file changed, 475 insertions(+), 130 deletions(-) diff --git a/commands/applications/cookies.js b/commands/applications/cookies.js index 543fe13..fdf1fc4 100644 --- a/commands/applications/cookies.js +++ b/commands/applications/cookies.js @@ -1,6 +1,7 @@ const { SlashCommandBuilder, - EmbedBuilder + EmbedBuilder, + MessageFlags } = require("discord.js"); const { mClient } = require("../.."); @@ -143,8 +144,13 @@ async function getCookieBalance(userId) { } -async function changeCookies(userId, amount, session = undefined) { - const { cookies } = getCollections(); +async function changeCookies( + userId, + amount +) { + const { cookies } = + getCollections(); + return cookies.updateOne( { @@ -152,15 +158,17 @@ async function changeCookies(userId, amount, session = undefined) { }, { $inc: { - cookies: amount + cookies: + amount }, + $setOnInsert: { userId } }, { - upsert: true, - session + upsert: + true } ); } @@ -168,70 +176,108 @@ async function changeCookies(userId, amount, session = undefined) { /* * COOKIE TRANSFER - * - * This uses a transaction so both balances update together. - * - * MongoDB transactions require Atlas or a replica set. */ -async function transferCookies(fromUserId, toUserId, amount) { - const { cookies } = getCollections(); +async function transferCookies( + fromUserId, + toUserId, + amount +) { + const { cookies } = + getCollections(); - const session = mClient.startSession(); - try { - await session.withTransaction(async () => { + /* + * Atomically debit the sender only if + * they still have enough cookies. + */ + const debit = + await cookies.updateOne( + { + userId: + fromUserId, - /* - * Only debit if the sender still has enough cookies. - * - * This protects against two commands being executed - * at almost the same time. - */ - const debit = await cookies.updateOne( - { - userId: fromUserId, - cookies: { - $gte: amount - } - }, - { - $inc: { - cookies: -amount - } - }, - { - session + cookies: { + $gte: + amount + } + }, + { + $inc: { + cookies: + -amount } - ); - - - if (debit.modifiedCount !== 1) { - throw new Error("INSUFFICIENT_COOKIES"); } + ); + if ( + debit.modifiedCount !== 1 + ) { + throw new Error( + "INSUFFICIENT_COOKIES" + ); + } + + + /* + * Credit the recipient. + */ + try { + await cookies.updateOne( + { + userId: + toUserId + }, + { + $inc: { + cookies: + amount + }, + + $setOnInsert: { + userId: + toUserId + } + }, + { + upsert: + true + } + ); + + } catch (error) { + + /* + * Credit failed. + * + * Attempt to restore the sender's + * cookies so the transfer doesn't + * destroy currency. + */ + try { await cookies.updateOne( { - userId: toUserId + userId: + fromUserId }, { $inc: { - cookies: amount - }, - $setOnInsert: { - userId: toUserId + cookies: + amount } - }, - { - upsert: true, - session } ); - }); - } finally { - await session.endSession(); + } catch (rollbackError) { + console.error( + "CRITICAL: Cookie transfer rollback failed:", + rollbackError + ); + } + + + throw error; } } @@ -365,77 +411,91 @@ async function collect(interaction) { */ async function steal(interaction) { - const user = interaction.user; + const user = + interaction.user; - const target = interaction.options.getUser( - "target", - true - ); + const target = + interaction.options.getUser( + "target", + true + ); /* - * Can't rob yourself. + * VALIDATION */ + if (target.id === user.id) { return interaction.reply({ content: "C'mon, don't embarrass yourself. You can't steal from yourself.", - ephemeral: true + flags: + MessageFlags.Ephemeral }); } - /* - * Don't let people farm bot accounts. - */ if (target.bot) { return interaction.reply({ content: - "Bots have highly sophisticated cookie security systems.", - ephemeral: true + "Bots have highly sophisticated Cookie security systems.", + flags: + MessageFlags.Ephemeral }); } - const now = getUnixTime(); - - /* - * Check steal cooldown before doing anything expensive. + * COOLDOWN */ - const cooldown = await getCooldown( - user.id, - interaction.guildId, - "steal" - ); + + const now = + getUnixTime(); + + + const cooldown = + await getCooldown( + user.id, + interaction.guildId, + "steal" + ); if (cooldown > now) { return interaction.reply({ content: `Lay low for a while. You can steal again .`, - ephemeral: true + flags: + MessageFlags.Ephemeral }); } /* - * Fetch both balances concurrently. + * BALANCES */ + const [ yourCookies, theirCookies - ] = await Promise.all([ - getCookieBalance(user.id), - getCookieBalance(target.id) - ]); + ] = + 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.", - ephemeral: true + flags: + MessageFlags.Ephemeral }); } @@ -444,35 +504,47 @@ async function steal(interaction) { return interaction.reply({ content: `Shame on you! ${target} doesn't have any Cookies. D:`, - ephemeral: true + + flags: + MessageFlags.Ephemeral }); } /* - * Chaos mode: - * fixed 50% steal chance. + * DYNAMIC SUCCESS CHANCE */ + + const successChance = + getStealChance( + yourCookies, + theirCookies + ); + + const success = - Math.random() < STEAL_SUCCESS_CHANCE; + Math.random() < + successChance; /* - * Successful robbery. + * SUCCESS */ - if (success) { - const maxSteal = Math.max( - 1, - Math.ceil(theirCookies / 4) - ); - const amount = Math.min( - theirCookies, - randomInteger( - 1, - maxSteal - ) - ); + if (success) { + + /* + * Roll a weighted percentage + * of the target's entire vault. + */ + + const { + amount, + percentage + } = + getStealAmount( + theirCookies + ); try { @@ -481,53 +553,93 @@ async function steal(interaction) { user.id, amount ); + } catch (error) { - if (error.message === "INSUFFICIENT_COOKIES") { + + /* + * Target may have spent/given/stolen + * cookies between our balance check + * and the actual transfer. + */ + + if ( + error.message === + "INSUFFICIENT_COOKIES" + ) { return interaction.reply({ content: `${target}'s Cookie balance changed before you could finish the robbery.`, - ephemeral: true + + flags: + MessageFlags.Ephemeral }); } + throw error; } - const nextSteal = await setCooldown( - user.id, - interaction.guildId, - "steal", - STEAL_COOLDOWN - ); + /* + * Apply cooldown after robbery. + */ + + const nextSteal = + await setCooldown( + user.id, + interaction.guildId, + "steal", + STEAL_COOLDOWN + ); + + + const actualPercentage = + ( + amount / + theirCookies * + 100 + ); return interaction.reply({ content: `${randomStealSuccessResponse(amount, target)}\n` + - `**Success Chance:** ${formatPercentage(STEAL_SUCCESS_CHANCE)} • ` + - `Next robbery ` + `🍪 You made off with **${actualPercentage.toFixed(1)}%** of their vault!\n` + + `🎲 **Success Chance:** ${formatPercentage(successChance)}\n` + + `⏱️ Next robbery ` }); } /* - * Failed robbery. + * FAILURE * - * Lose between 1 and roughly 1/6 of your balance. + * Penalty is also percentage based. + * + * Roll between roughly 1–8% of the + * attacker's current vault. */ - const maxPenalty = Math.max( - 1, - Math.ceil(yourCookies / 6) - ); - const penalty = Math.min( - yourCookies, - randomInteger( + const penaltyPercentage = + randomFloat( + 0.01, + 0.08 + ); + + + const penalty = + Math.max( 1, - maxPenalty - ) - ); + + Math.min( + yourCookies, + + Math.floor( + yourCookies * + penaltyPercentage + ) + ) + ); try { @@ -536,36 +648,57 @@ async function steal(interaction) { target.id, penalty ); + } catch (error) { - if (error.message === "INSUFFICIENT_COOKIES") { + + if ( + error.message === + "INSUFFICIENT_COOKIES" + ) { return interaction.reply({ content: "Your Cookie balance changed before the robbery could finish.", - ephemeral: true + + flags: + MessageFlags.Ephemeral }); } + throw error; } - const nextSteal = await setCooldown( - user.id, - interaction.guildId, - "steal", - STEAL_COOLDOWN - ); + /* + * Apply cooldown. + */ + + const nextSteal = + await setCooldown( + user.id, + interaction.guildId, + "steal", + STEAL_COOLDOWN + ); + + + const actualPenaltyPercentage = + ( + penalty / + yourCookies * + 100 + ); return interaction.reply({ content: `${randomStealFailResponse(penalty, target)}\n` + - `**Success Chance:** ${formatPercentage(STEAL_SUCCESS_CHANCE)} • ` + - `Next robbery ` + `💸 You lost **${actualPenaltyPercentage.toFixed(1)}%** of your vault.\n` + + `🎲 **Success Chance:** ${formatPercentage(successChance)}\n` + + `⏱️ Next robbery ` }); } - /* * /cookies check */ @@ -793,7 +926,219 @@ function randomStealFailResponse(amount, target) { return randomItem(responses); } +/* + * STEAL HELPERS + */ +function getStealChance( + attackerCookies, + targetCookies +) { + const MIN_CHANCE = + 0.15; + + const MAX_CHANCE = + 0.70; + + const BASE_CHANCE = + 0.50; + + + /* + * Wealthy targets are harder to rob. + * + * Logarithmic scaling prevents massive + * vaults from becoming completely immune. + */ + + const targetPenalty = + Math.log10( + targetCookies + 1 + ) + * + 0.06; + + + /* + * Compare attacker wealth to victim. + */ + + const wealthRatio = + attackerCookies / + Math.max( + targetCookies, + 1 + ); + + + /* + * Catch-up mechanic. + * + * If the attacker has less than the target, + * they gain up to +18 percentage points. + */ + + const povertyBonus = + wealthRatio < 1 + ? ( + 1 - + wealthRatio + ) + * + 0.18 + + : 0; + + + /* + * Anti-bullying penalty. + * + * Rich attackers targeting poorer users + * become progressively less successful. + */ + + const bullyPenalty = + wealthRatio > 1 + ? Math.min( + Math.log10( + wealthRatio + 1 + ) + * + 0.12, + + 0.20 + ) + + : 0; + + + const chance = + BASE_CHANCE + - + targetPenalty + + + povertyBonus + - + bullyPenalty; + + + /* + * Always keep robbery possible, + * but never make it nearly guaranteed. + */ + + return Math.max( + MIN_CHANCE, + + Math.min( + MAX_CHANCE, + chance + ) + ); +} + +function getStealAmount( + targetCookies +) { + const MAX_STEAL_AMOUNT = + 5000; + + + const percentage = + getStealPercentage(); + + + const calculatedAmount = + Math.floor( + targetCookies * + percentage + ); + + + const amount = + Math.max( + 1, + + Math.min( + calculatedAmount, + targetCookies, + MAX_STEAL_AMOUNT + ) + ); + + + return { + amount, + percentage + }; +} + +function getStealPercentage() { + const roll = + Math.random(); + + + /* + * 50% chance: + * steal 1–3% + */ + + if (roll < 0.50) { + return randomFloat( + 0.01, + 0.03 + ); + } + + + /* + * 30% chance: + * steal 3–5% + */ + + if (roll < 0.80) { + return randomFloat( + 0.03, + 0.05 + ); + } + + + /* + * 15% chance: + * steal 5–8% + */ + + if (roll < 0.95) { + return randomFloat( + 0.05, + 0.08 + ); + } + + + /* + * 5% jackpot: + * steal 8–10% + */ + + return randomFloat( + 0.08, + 0.10 + ); +} + +function randomFloat( + min, + max +) { + return ( + Math.random() * + (max - min) + + + min + ); +} /* * GENERIC HELPERS