diff --git a/src/buttonEvents/Claim.ts b/src/buttonEvents/Claim.ts index 4d5d97a..190a09e 100644 --- a/src/buttonEvents/Claim.ts +++ b/src/buttonEvents/Claim.ts @@ -58,7 +58,7 @@ export default class Claim extends ButtonEvent { if (!inventory) { inventory = new Inventory(userId, cardNumber, 1); } else { - inventory.SetQuantity(inventory.Quantity + 1); + inventory.AddQuantity(1); } await inventory.Save(Inventory, inventory); diff --git a/src/buttonEvents/Multidrop.ts b/src/buttonEvents/Multidrop.ts new file mode 100644 index 0000000..604f02c --- /dev/null +++ b/src/buttonEvents/Multidrop.ts @@ -0,0 +1,213 @@ +import { AttachmentBuilder, ButtonInteraction, EmbedBuilder } from "discord.js"; +import { ButtonEvent } from "../type/buttonEvent"; +import AppLogger from "../client/appLogger"; +import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata"; +import Inventory from "../database/entities/app/Inventory"; +import EmbedColours from "../constants/EmbedColours"; +import { readFileSync } from "fs"; +import path from "path"; +import ErrorMessages from "../constants/ErrorMessages"; +import User from "../database/entities/app/User"; +import { GetSacrificeAmount } from "../constants/CardRarity"; + +export default class Multidrop extends ButtonEvent { + public override async execute(interaction: ButtonInteraction) { + const action = interaction.customId.split(" ")[1]; + + switch (action) { + case "keep": + await this.Keep(interaction); + break; + case "sacrifice": + await this.Sacrifice(interaction); + break; + default: + await interaction.reply("Invalid action"); + AppLogger.LogError("Button/Multidrop", `Invalid action, ${action}`); + } + } + + private async Keep(interaction: ButtonInteraction) { + const cardNumber = interaction.customId.split(" ")[2]; + let cardsRemaining = Number(interaction.customId.split(" ")[3]) || 0; + const userId = interaction.customId.split(" ")[4]; + + if (interaction.user.id != userId) { + await interaction.reply("You're not the user this drop was made for!"); + return; + } + + const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber); + + if (!card) { + await interaction.reply("Unable to find card."); + AppLogger.LogWarn("Button/Multidrop/Keep", `Card not found, ${cardNumber}`); + return; + } + + if (cardsRemaining < 0) { + await interaction.reply("Your multidrop has ran out! Please buy a new one!"); + return; + } + + const user = await User.FetchOneById(User, interaction.user.id); + + if (!user) { + AppLogger.LogWarn("Button/Multidrop/Keep", ErrorMessages.UnableToFetchUser); + await interaction.reply(ErrorMessages.UnableToFetchUser); + return; + } + + // Claim + let inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, cardNumber); + + if (!inventory) { + inventory = new Inventory(interaction.user.id, cardNumber, 1); + } else { + inventory.AddQuantity(1); + } + + await inventory.Save(Inventory, inventory); + + // Pack has ran out + if (cardsRemaining == 0) { + const embed = new EmbedBuilder() + .setDescription("Your multidrop has ran out! Please buy a new one!") + .setColor(EmbedColours.Ok); + + await interaction.update({ + embeds: [ embed ], + attachments: [], + components: [], + }); + + return; + } + + // Drop next card + const randomCard = CardDropHelperMetadata.GetRandomCard(); + cardsRemaining -= 1; + + if (!randomCard) { + AppLogger.LogWarn("Button/Multidrop/Keep", ErrorMessages.UnableToFetchCard); + await interaction.reply(ErrorMessages.UnableToFetchCard); + return; + } + + await interaction.deferUpdate(); + + try { + const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path)); + const imageFileName = randomCard.card.path.split("/").pop()!; + + const attachment = new AttachmentBuilder(image, { name: imageFileName }); + + const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id); + const quantityClaimed = inventory ? inventory.Quantity : 0; + + const embed = CardDropHelperMetadata.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency); + + const row = CardDropHelperMetadata.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id, cardsRemaining < 0); + + await interaction.editReply({ + embeds: [ embed ], + files: [ attachment ], + components: [ row ], + }); + } catch (e) { + AppLogger.LogError("Button/Multidrop/Keep", `Error sending next drop for card ${randomCard.card.id}: ${e}`); + + await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. (${randomCard.card.id})`); + } + } + + private async Sacrifice(interaction: ButtonInteraction) { + const cardNumber = interaction.customId.split(" ")[2]; + let cardsRemaining = Number(interaction.customId.split(" ")[3]) || 0; + const userId = interaction.customId.split(" ")[4]; + + if (interaction.user.id != userId) { + await interaction.reply("You're not the user this drop was made for!"); + return; + } + + const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber); + + if (!card) { + await interaction.reply("Unable to find card."); + AppLogger.LogWarn("Button/Multidrop/Sacrifice", `Card not found, ${cardNumber}`); + return; + } + + if (cardsRemaining < 0) { + await interaction.reply("Your multidrop has ran out! Please buy a new one!"); + return; + } + + const user = await User.FetchOneById(User, interaction.user.id); + + if (!user) { + AppLogger.LogWarn("Button/Multidrop/Sacrifice", ErrorMessages.UnableToFetchUser); + await interaction.reply(ErrorMessages.UnableToFetchUser); + return; + } + + // Sacrifice + const sacrificeAmount = GetSacrificeAmount(card.card.type); + + user.AddCurrency(sacrificeAmount); + + await user.Save(User, user); + + // Pack has ran out + if (cardsRemaining == 0) { + const embed = new EmbedBuilder() + .setDescription("Your multidrop has ran out! Please buy a new one!") + .setColor(EmbedColours.Ok); + + await interaction.update({ + embeds: [ embed ], + attachments: [], + components: [], + }); + + return; + } + + // Drop next card + const randomCard = CardDropHelperMetadata.GetRandomCard(); + cardsRemaining -= 1; + + if (!randomCard) { + AppLogger.LogWarn("Button/Multidrop/Sacrifice", ErrorMessages.UnableToFetchCard); + await interaction.reply(ErrorMessages.UnableToFetchCard); + return; + } + + await interaction.deferUpdate(); + + try { + const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path)); + const imageFileName = randomCard.card.path.split("/").pop()!; + + const attachment = new AttachmentBuilder(image, { name: imageFileName }); + + const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id); + const quantityClaimed = inventory ? inventory.Quantity : 0; + + const embed = CardDropHelperMetadata.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency); + + const row = CardDropHelperMetadata.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id, cardsRemaining < 0); + + await interaction.editReply({ + embeds: [ embed ], + files: [ attachment ], + components: [ row ], + }); + } catch (e) { + AppLogger.LogError("Button/Multidrop/Sacrifice", `Error sending next drop for card ${randomCard.card.id}: ${e}`); + + await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. (${randomCard.card.id})`); + } + } +} \ No newline at end of file diff --git a/src/commands/drop.ts b/src/commands/drop.ts index 6f74d3a..6799a2c 100644 --- a/src/commands/drop.ts +++ b/src/commands/drop.ts @@ -10,6 +10,7 @@ import path from "path"; import AppLogger from "../client/appLogger"; import User from "../database/entities/app/User"; import CardConstants from "../constants/CardConstants"; +import ErrorMessages from "../constants/ErrorMessages"; export default class Drop extends Command { constructor() { @@ -22,14 +23,13 @@ export default class Drop extends Command { public override async execute(interaction: CommandInteraction) { if (!CoreClient.AllowDrops) { - await interaction.reply("Bot is currently syncing, please wait until its done."); + await interaction.reply(ErrorMessages.BotSyncing); return; } if (await Config.GetValue("safemode") == "true") { - AppLogger.LogWarn("Commands/Drop", "Safe Mode is active, refusing to send next drop."); - - await interaction.reply("Safe Mode has been activated, please resync to continue."); + AppLogger.LogWarn("Commands/Drop", ErrorMessages.SafeMode); + await interaction.reply(ErrorMessages.SafeMode); return; } @@ -43,16 +43,15 @@ export default class Drop extends Command { } if (user.Currency < CardConstants.ClaimCost) { - await interaction.reply(`Not enough currency! You need ${CardConstants.ClaimCost} currency, you have ${user.Currency}!`); + await interaction.reply(ErrorMessages.NotEnoughCurrency(CardConstants.ClaimCost, user.Currency)); return; } const randomCard = CardDropHelperMetadata.GetRandomCard(); if (!randomCard) { - AppLogger.LogWarn("Commands/Drop", "Unable to fetch card, please try again. (randomCard is null)"); - - await interaction.reply("Unable to fetch card, please try again."); + AppLogger.LogWarn("Commands/Drop", ErrorMessages.UnableToFetchCard); + await interaction.reply(ErrorMessages.UnableToFetchCard); return; } diff --git a/src/commands/multidrop.ts b/src/commands/multidrop.ts new file mode 100644 index 0000000..f35b921 --- /dev/null +++ b/src/commands/multidrop.ts @@ -0,0 +1,87 @@ +import { AttachmentBuilder, CommandInteraction, SlashCommandBuilder } from "discord.js"; +import { Command } from "../type/command"; +import { CoreClient } from "../client/client"; +import ErrorMessages from "../constants/ErrorMessages"; +import Config from "../database/entities/app/Config"; +import AppLogger from "../client/appLogger"; +import User from "../database/entities/app/User"; +import CardConstants from "../constants/CardConstants"; +import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata"; +import { readFileSync } from "fs"; +import path from "path"; +import Inventory from "../database/entities/app/Inventory"; + +export default class Multidrop extends Command { + constructor() { + super(); + + this.CommandBuilder = new SlashCommandBuilder() + .setName("multidrop") + .setDescription("Drop 11 cards for the price of 10!"); + } + + public override async execute(interaction: CommandInteraction) { + if (!CoreClient.AllowDrops) { + await interaction.reply(ErrorMessages.BotSyncing); + return; + } + + if (await Config.GetValue("safemode") == "true") { + AppLogger.LogWarn("Commands/Multidrop", ErrorMessages.SafeMode); + await interaction.reply(ErrorMessages.SafeMode); + return; + } + + let user = await User.FetchOneById(User, interaction.user.id); + + if (!user) { + user = new User(interaction.user.id, CardConstants.StartingCurrency); + await user.Save(User, user); + + AppLogger.LogInfo("Commands/Multidrop", `New user (${interaction.user.id}) saved to the database`); + } + + if (user.Currency < CardConstants.MultidropCost) { + await interaction.reply(ErrorMessages.NotEnoughCurrency(CardConstants.MultidropCost, user.Currency)); + return; + } + + user.RemoveCurrency(CardConstants.MultidropCost); + await user.Save(User, user); + + const randomCard = CardDropHelperMetadata.GetRandomCard(); + const cardsRemaining = CardConstants.MultidropQuantity - 1; + + if (!randomCard) { + AppLogger.LogWarn("Commands/Multidrop", ErrorMessages.UnableToFetchCard); + await interaction.reply(ErrorMessages.UnableToFetchCard); + return; + } + + await interaction.deferReply(); + + try { + const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path)); + const imageFileName = randomCard.card.path.split("/").pop()!; + + const attachment = new AttachmentBuilder(image, { name: imageFileName }); + + const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id); + const quantityClaimed = inventory ? inventory.Quantity : 0; + + const embed = CardDropHelperMetadata.GenerateMultidropEmbed(randomCard, quantityClaimed, imageFileName, cardsRemaining, undefined, user.Currency); + + const row = CardDropHelperMetadata.GenerateMultidropButtons(randomCard, cardsRemaining, interaction.user.id); + + await interaction.editReply({ + embeds: [ embed ], + files: [ attachment ], + components: [ row ], + }); + } catch (e) { + AppLogger.LogError("Commands/Multidrop", `Error sending next drop for card ${randomCard.card.id}: ${e}`); + + await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. (${randomCard.card.id})`); + } + } +} \ No newline at end of file diff --git a/src/constants/CardConstants.ts b/src/constants/CardConstants.ts index 0a87e22..3f9723b 100644 --- a/src/constants/CardConstants.ts +++ b/src/constants/CardConstants.ts @@ -3,4 +3,8 @@ export default class CardConstants { public static readonly TimerGiveAmount = 10; public static readonly DailyCurrency = 100; public static readonly StartingCurrency = 300; + + // Multidrop + public static readonly MultidropCost = this.ClaimCost * 10; + public static readonly MultidropQuantity = 11; } \ No newline at end of file diff --git a/src/constants/ErrorMessages.ts b/src/constants/ErrorMessages.ts new file mode 100644 index 0000000..c8749ec --- /dev/null +++ b/src/constants/ErrorMessages.ts @@ -0,0 +1,8 @@ +export default class ErrorMessages { + public static readonly BotSyncing = "Bot is currently syncing, please wait until its done."; + public static readonly SafeMode = "Safe Mode has been activated, please resync to continue."; + public static readonly UnableToFetchCard = "Unable to fetch card, please try again."; + public static readonly UnableToFetchUser = "Unable to fetch user, please try again."; + + public static readonly NotEnoughCurrency = (need: number, have: number) => `Not enough currency! You need ${need} currency, you have ${have}!`; +} \ No newline at end of file diff --git a/src/helpers/CardDropHelperMetadata.ts b/src/helpers/CardDropHelperMetadata.ts index bc59e93..8b9b273 100644 --- a/src/helpers/CardDropHelperMetadata.ts +++ b/src/helpers/CardDropHelperMetadata.ts @@ -1,5 +1,5 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js"; -import { CardRarity, CardRarityToColour, CardRarityToString } from "../constants/CardRarity"; +import { CardRarity, CardRarityToColour, CardRarityToString, GetSacrificeAmount } from "../constants/CardRarity"; import CardRarityChances from "../constants/CardRarityChances"; import { DropResult } from "../contracts/SeriesMetadata"; import { CoreClient } from "../client/client"; @@ -89,7 +89,7 @@ export default class CardDropHelperMetadata { const hexCode = Number("0x" + drop.card.colour); if (hexCode) { - colour = hexCode; + colour = hexCode; } else { AppLogger.LogWarn("CardDropHelperMetadata/GenerateDropEmbed", `Card's colour override is invalid: ${drop.card.id}, ${drop.card.colour}`); } @@ -149,4 +149,26 @@ export default class CardDropHelperMetadata { .setLabel("Reroll") .setStyle(ButtonStyle.Secondary)); } + + public static GenerateMultidropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, cardsRemaining: number, claimedBy?: string, currency?: number): EmbedBuilder { + const dropEmbed = this.GenerateDropEmbed(drop, quantityClaimed, imageFileName, claimedBy, currency); + + dropEmbed.setFooter({ text: `${dropEmbed.data.footer?.text} · ${cardsRemaining} Remaining`}); + + return dropEmbed; + } + + public static GenerateMultidropButtons(drop: DropResult, cardsRemaining: number, userId: string, disabled = false): ActionRowBuilder { + return new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId(`multidrop keep ${drop.card.id} ${cardsRemaining} ${userId}`) + .setLabel("Keep") + .setStyle(ButtonStyle.Primary) + .setDisabled(disabled), + new ButtonBuilder() + .setCustomId(`multidrop sacrifice ${drop.card.id} ${cardsRemaining} ${userId}`) + .setLabel(`Sacrifice (+${GetSacrificeAmount(drop.card.type)} 🪙)`) + .setStyle(ButtonStyle.Secondary)); + } } diff --git a/src/registry.ts b/src/registry.ts index 67936c8..e4e5d64 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -11,6 +11,7 @@ import Gdrivesync from "./commands/gdrivesync"; import Give from "./commands/give"; import Id from "./commands/id"; import Inventory from "./commands/inventory"; +import Multidrop from "./commands/multidrop"; import Resync from "./commands/resync"; import Sacrifice from "./commands/sacrifice"; import Series from "./commands/series"; @@ -25,6 +26,7 @@ import Droprarity from "./commands/stage/droprarity"; // Button Event Imports import Claim from "./buttonEvents/Claim"; import InventoryButtonEvent from "./buttonEvents/Inventory"; +import MultidropButtonEvent from "./buttonEvents/Multidrop"; import Reroll from "./buttonEvents/Reroll"; import SacrificeButtonEvent from "./buttonEvents/Sacrifice"; import SeriesEvent from "./buttonEvents/Series"; @@ -46,6 +48,7 @@ export default class Registry { CoreClient.RegisterCommand("give", new Give()); CoreClient.RegisterCommand("id", new Id()); CoreClient.RegisterCommand("inventory", new Inventory()); + CoreClient.RegisterCommand("multidrop", new Multidrop()); CoreClient.RegisterCommand("resync", new Resync()); CoreClient.RegisterCommand("sacrifice", new Sacrifice()); CoreClient.RegisterCommand("series", new Series()); @@ -61,6 +64,7 @@ export default class Registry { public static RegisterButtonEvents() { CoreClient.RegisterButtonEvent("claim", new Claim()); CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent()); + CoreClient.RegisterButtonEvent("multidrop", new MultidropButtonEvent()); CoreClient.RegisterButtonEvent("reroll", new Reroll()); CoreClient.RegisterButtonEvent("sacrifice", new SacrificeButtonEvent()); CoreClient.RegisterButtonEvent("series", new SeriesEvent());