const { SlashCommandBuilder, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, MessageFlags } = require("discord.js"); const { randomUUID } = require("node:crypto"); const { getCollections, getCookieBalance, transferCookies, getUnixTime } = require("../../utils/cookieEconomy"); const SPAWN_COST = 500; const JOIN_DURATION = 30 * 60; const MINIMUM_PLAYERS = 5; module.exports = { data: new SlashCommandBuilder() .setName("crumblestrike") .setDescription( "Cooperative Cookie boss encounters." ) .addSubcommand(subcommand => subcommand .setName("spawn") .setDescription( `Summon the Crumbbound Behemoth for ${SPAWN_COST} Cookies.` ) ), async execute(interaction) { switch ( interaction.options.getSubcommand() ) { case "spawn": return spawnCrumbleStrike( interaction ); default: return interaction.reply({ content: "Unknown Crumble Strike command.", flags: MessageFlags.Ephemeral }); } } }; async function spawnCrumbleStrike( interaction ) { if (!interaction.guild) { return interaction.reply({ content: "Crumble Strike can only be started inside a server.", flags: MessageFlags.Ephemeral }); } const { db } = getCollections(); const events = db.collection( "items_events" ); const existing = await events.findOne({ eventType: "CrumbleStrike", guildId: interaction.guildId, state: { $in: [ "Joining", "Starting", "Active", "Resolving" ] } }); if (existing) { return interaction.reply({ content: "There is already an active Crumble Strike encounter in this server.", flags: MessageFlags.Ephemeral }); } const balance = await getCookieBalance( interaction.user.id ); if ( balance < SPAWN_COST ) { return interaction.reply({ content: `You need **${SPAWN_COST.toLocaleString()} Cookies** to summon the boss.`, flags: MessageFlags.Ephemeral }); } const eventId = randomUUID(); const escrowId = `crumblestrike:${eventId}`; const now = getUnixTime(); const joinUntil = now + JOIN_DURATION; try { await transferCookies( interaction.user.id, escrowId, SPAWN_COST ); } catch (error) { if ( error.message === "INSUFFICIENT_COOKIES" ) { return interaction.reply({ content: "Your Cookie balance changed before the boss could be summoned.", flags: MessageFlags.Ephemeral }); } throw error; } const embed = new EmbedBuilder() .setTitle( "🍪 Crumble Strike" ) .setDescription( [ "## The Crumbbound Behemoth", "", "*Drawn by the scent of an irresponsibly large Cookie Vault,*", "*the beast has lumbered into Shaiwase.*", "", `**Summoner:** ${interaction.user}`, `**Recruitment ends:** `, `**Minimum adventurers:** ${MINIMUM_PLAYERS}`, `**Participants:** 0/${MINIMUM_PLAYERS}`, `**Reward Pool:** ${SPAWN_COST.toLocaleString()} 🍪`, "", "Choose your role below to begin preparing for battle.", "", "⚔️ **DPS** — Specializes in offensive actions.", "🛡️ **Tank** — Specializes in mitigating boss attacks.", "❤️ **Healer** — Specializes in restoring party HP.", "", "Once enough players have joined, the summoner may start the battle early." ].join("\n") ) .setFooter({ text: `crumbleStrike:${eventId}` }); const roles = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId( "crumbleStrikeRoleDPS" ) .setLabel( "DPS" ) .setEmoji( "⚔️" ) .setStyle( ButtonStyle.Danger ), new ButtonBuilder() .setCustomId( "crumbleStrikeRoleTank" ) .setLabel( "Tank" ) .setEmoji( "🛡️" ) .setStyle( ButtonStyle.Primary ), new ButtonBuilder() .setCustomId( "crumbleStrikeRoleHealer" ) .setLabel( "Healer" ) .setEmoji( "❤️" ) .setStyle( ButtonStyle.Success ) ); const start = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId( "crumbleStrikeStart" ) .setLabel( "Start Battle" ) .setEmoji( "⚔️" ) .setStyle( ButtonStyle.Secondary ) ); let message; try { message = await interaction.reply({ embeds: [ embed ], components: [ roles, start ], fetchReply: true }); } catch (error) { await safeRefund( escrowId, interaction.user.id, SPAWN_COST ); throw error; } try { await events.insertOne({ eventType: "CrumbleStrike", 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: MINIMUM_PLAYERS, rewardPool: SPAWN_COST, boss: { bossId: "crumbbound-behemoth", name: "The Crumbbound Behemoth", health: 15000, maxHealth: 15000, limitGauge: 0, actionGauge: 0 }, participants: {} }); } catch (error) { try { await message.delete(); } catch {} await safeRefund( escrowId, interaction.user.id, SPAWN_COST ); throw error; } } async function safeRefund( from, to, amount ) { try { await transferCookies( from, to, amount ); } catch (error) { console.error( "CRITICAL: Crumble Strike refund failed:", error ); } }