From dd4d6e48171a18623073a012687a8b4b921dfa6b Mon Sep 17 00:00:00 2001 From: DeSqBlocki Date: Sun, 13 Sep 2026 21:56:18 +0200 Subject: [PATCH] added achievements for new steal logic --- commands/applications/cookies.js | 85 +++++++++++++++++++++----------- utils/profileSystem.js | 19 ++++++- 2 files changed, 73 insertions(+), 31 deletions(-) diff --git a/commands/applications/cookies.js b/commands/applications/cookies.js index 07c6bc7..d6e7f34 100644 --- a/commands/applications/cookies.js +++ b/commands/applications/cookies.js @@ -15,7 +15,9 @@ const { } = require("../../utils/cookieEconomy"); const { - trackProfileEvent + trackProfileEvent, + getProfileCollections, + getDefaultStats } = require("../../utils/profileSystem"); const { @@ -235,12 +237,8 @@ async function steal(interaction) { }); } - const { - cookies, - statsProfile - } = getCookieCommandCollections({ - requireStatsProfile: true - }); + const { cookies } = getCookieCommandCollections(); + const { stats: statsProfile } = getProfileCollections(); const now = new Date(); const stealWindowCutoff = new Date( @@ -398,6 +396,27 @@ async function steal(interaction) { ) : null; + /* + * Ensure the victim has a complete profile document before heat state is + * written. This avoids creating a sparse stats_profiles document when a + * player is targeted before any other profile event has initialized them. + */ + await statsProfile.updateOne( + { + userId: target.id, + guildId: interaction.guildId + }, + { + $setOnInsert: getDefaultStats( + target.id, + interaction.guildId + ) + }, + { + upsert: true + } + ); + /* * Rolling heat protection. * @@ -407,7 +426,8 @@ async function steal(interaction) { */ await statsProfile.updateOne( { - userId: target.id + userId: target.id, + guildId: interaction.guildId }, { $pull: { @@ -428,7 +448,8 @@ async function steal(interaction) { */ const heatUpdateResult = await statsProfile.findOneAndUpdate( { - userId: target.id + userId: target.id, + guildId: interaction.guildId }, { $push: { @@ -439,7 +460,6 @@ async function steal(interaction) { } }, { - upsert: true, returnDocument: "before" } ); @@ -451,6 +471,30 @@ async function steal(interaction) { const activeAttempts = targetProfile?.stealProtection?.attempts ?? []; + const uniqueAttackers = new Set([ + ...activeAttempts.map( + attempt => attempt.userId + ), + user.id + ]).size; + + /* + * Achievement/event tracking for the victim. The current attempt is + * included, while repeated attempts by the same user only count once + * toward the unique-attacker total. + */ + await trackProfileEvent( + interaction, + target.id, + { + type: "steal_targeted", + attackerUserId: user.id, + uniqueAttackers, + attemptsInWindow: activeAttempts.length + 1, + windowSeconds: heatWindowMs / 1000 + } + ); + const heat = activeAttempts.reduce( (total, attempt) => { const timestamp = new Date( @@ -1111,9 +1155,7 @@ async function leaderboard(interaction) { * collection names for the two collections used by this command. */ -function getCookieCommandCollections({ - requireStatsProfile = false -} = {}) { +function getCookieCommandCollections() { const collections = getCollections(); const database = collections.db ?? @@ -1125,28 +1167,13 @@ function getCookieCommandCollections({ collections.items_cookies ?? database?.collection?.("items_cookies"); - const statsProfile = - collections.statsProfile ?? - collections.statsProfiles ?? - collections.stats_profile ?? - database?.collection?.("stats_profile"); - if (!cookies) { throw new Error( "getCollections() must expose the items_cookies collection as cookies/itemsCookies/items_cookies, or expose a db handle." ); } - if (requireStatsProfile && !statsProfile) { - throw new Error( - "getCollections() must expose the stats_profile collection as statsProfile/statsProfiles/stats_profile, or expose a db handle." - ); - } - - return { - cookies, - statsProfile - }; + return { cookies }; } diff --git a/utils/profileSystem.js b/utils/profileSystem.js index 6dc0e22..c3fa272 100644 --- a/utils/profileSystem.js +++ b/utils/profileSystem.js @@ -636,7 +636,7 @@ function getDefaultStats(userId, guildId) { allInRunItBack: 0, bankruptcyCount: 0, bankruptcyRecoveryBest: 0, - + maxUniqueStealAttackers: 0, etTuCrumb: 0, noHardFeelings: 0, cookieLaundering: 0 @@ -846,7 +846,22 @@ async function trackProfileEvent( } ); break; - + case "steal_targeted": + await stats.updateOne( + { + userId, + guildId + }, + { + $max: { + "special.maxUniqueStealAttackers": + Number( + event.uniqueAttackers ?? 0 + ) + } + } + ); + break; case "steal_success": await stats.updateOne( {