417 lines
8.7 KiB
JavaScript
417 lines
8.7 KiB
JavaScript
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:** <t:${joinUntil}:R>`,
|
|
`**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
|
|
);
|
|
}
|
|
} |