import { AutocompleteInteraction, GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; import { CraftingStationCooldown, db } from '../lib/db'; import { getStation, canUseStation, craftingStations } from '../lib/rpg/craftingStations'; import { formatItem, getItemQuantity, formatItems, getMaxStack, giveItem, formatItemsArray } from '../lib/rpg/items'; import { getRecipe, defaultRecipes, formatRecipe } from '../lib/rpg/recipes'; module.exports = { data: new SlashCommandBuilder() .setName('craft') .setDescription('Craft an item with items you have') .addStringOption(option => option .setName('station') .setAutocomplete(true) .setDescription('Which station to use') .setRequired(true) ) .addStringOption(option => option .setName('recipe') .setAutocomplete(true) .setDescription('What to craft') .setRequired(true) ) .setDMPermission(false), execute: async (interaction: Interaction, member: GuildMember) => { if (!interaction.isChatInputCommand()) return; const recipeID = parseInt(interaction.options.getString('recipe', true)); await interaction.deferReply({ephemeral: true}); const recipe = getRecipe(recipeID); if (!recipe) return interaction.followUp('Recipe does not exist!'); const station = getStation(recipe.station)!; if (!canUseStation(member.id, station)) return interaction.followUp(`${station.emoji} You need ${formatItem(station.requires)} to use this station!`); for (const input of recipe.inputs) { const inv = await getItemQuantity(member.id, input.item.id); if (inv.quantity < input.quantity) return interaction.followUp(`You need ${formatItems(input.item, input.quantity)} for this recipe! (You have ${formatItems(input.item, inv.quantity)})`); } for (const req of recipe.requirements) { const inv = await getItemQuantity(member.id, req.item.id); if (inv.quantity < req.quantity) return interaction.followUp(`You need ${formatItems(req.item, req.quantity)} to begin this recipe! (You have ${formatItems(req.item, inv.quantity)}. Don't worry, these items will not be consumed.)`); } for (const out of recipe.outputs) { const inv = await getItemQuantity(member.id, out.item.id); if (inv.quantity + out.quantity > getMaxStack(out.item)) return interaction.followUp(`You do not have enough inventory storage for this recipe! (${formatItems(out.item, inv.quantity + out.quantity)} is bigger than the stack size of ${getMaxStack(out.item)}.)`); } let cooldown; if (station.cooldown) { cooldown = await db('craftingStationCooldowns') .where('station', station.key) .where('user', member.id) .first(); if (cooldown && (cooldown.usedAt + station.cooldown * 1000) > Date.now()) return interaction.followUp(`${station.emoji} You can use this station again !`); } // proceed with crafting! for (const input of recipe.inputs) { giveItem(member.id, input.item, -input.quantity); } const outputs = station.manipulateResults ? station.manipulateResults(recipe.outputs) : recipe.outputs; for (const output of outputs) { giveItem(member.id, output.item, output.quantity); } let nextUsableAt; if (station.cooldown) { if (!cooldown) { await db('craftingStationCooldowns') .insert({ station: station.key, user: member.id, usedAt: Date.now() }); } else { await db('craftingStationCooldowns') .where('station', station.key) .where('user', member.id) .update({ usedAt: Date.now() }); } nextUsableAt = Date.now() + station.cooldown * 1000; } return interaction.followUp(`${station.emoji} Crafted ${formatItemsArray(outputs)}!${nextUsableAt ? `\n${station.name} usable again ` : ''}`); }, autocomplete: async (interaction: AutocompleteInteraction) => { const focused = interaction.options.getFocused(true); if (focused.name === 'station') { const found = craftingStations .filter(station => canUseStation(interaction.user.id, station)) .filter(station => station.name.toLowerCase().includes(focused.value.toLowerCase())) .map(station => ({ name: `${station.emoji} ${station.name}`, value: station.key })); return interaction.respond(found); } else if (focused.name === 'recipe') { const found = defaultRecipes .filter(recipe => recipe.station === interaction.options.getString('station')) .filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.value.toLowerCase())).length > 0) .map(recipe => ({ name: formatRecipe(recipe, true), value: recipe.id.toString() })); return interaction.respond(found); } } };