147 lines
6.2 KiB
TypeScript
147 lines
6.2 KiB
TypeScript
import { AutocompleteInteraction, GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js';
|
|
import { CraftingStationCooldown, CustomCraftingRecipe, db } from '../lib/db';
|
|
import { getStation, canUseStation, craftingStations, verb, CraftingStation } from '../lib/rpg/craftingStations';
|
|
import { formatItem, getItemQuantity, formatItems, getMaxStack, giveItem, formatItemsArray } from '../lib/rpg/items';
|
|
import { getRecipe, defaultRecipes, formatRecipe, resolveCustomRecipe } from '../lib/rpg/recipes';
|
|
import { Command } from '../types/index';
|
|
import { initHealth, resetInvincible } from '../lib/rpg/pvp';
|
|
import { set } from '../lib/autocomplete';
|
|
|
|
export default {
|
|
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: CommandInteraction) => {
|
|
if (!interaction.isChatInputCommand()) return;
|
|
|
|
const member = interaction.member! as GuildMember;
|
|
|
|
await initHealth(member.id);
|
|
|
|
const recipeID = parseInt(interaction.options.getString('recipe', true));
|
|
|
|
await interaction.deferReply({ephemeral: true});
|
|
|
|
const recipe = await 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)}x.)`);
|
|
}
|
|
|
|
let cooldown;
|
|
if (station.cooldown) {
|
|
cooldown = await db<CraftingStationCooldown>('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 <t:${Math.floor(cooldown.usedAt / 1000 + station.cooldown)}:R>!`);
|
|
}
|
|
|
|
// 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<CraftingStationCooldown>('craftingStationCooldowns')
|
|
.insert({
|
|
station: station.key,
|
|
user: member.id,
|
|
usedAt: Date.now()
|
|
});
|
|
} else {
|
|
await db<CraftingStationCooldown>('craftingStationCooldowns')
|
|
.where('station', station.key)
|
|
.where('user', member.id)
|
|
.update({
|
|
usedAt: Date.now()
|
|
});
|
|
}
|
|
|
|
nextUsableAt = Date.now() + station.cooldown * 1000;
|
|
}
|
|
|
|
await resetInvincible(member.id);
|
|
return interaction.followUp(`${station.emoji} ${verb(station)} ${formatItemsArray(outputs)}!${outputs.length === 1 ? `\n_${outputs[0].item.description}_` : ''}${nextUsableAt ? `\n${station.name} usable again <t:${Math.floor(nextUsableAt / 1000)}:R>` : ''}`);
|
|
},
|
|
|
|
autocomplete: set({
|
|
station: async (interaction: AutocompleteInteraction) =>
|
|
(await Promise.all(
|
|
craftingStations
|
|
.map(async station => [station, await canUseStation(interaction.user.id, station)])
|
|
))
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
.filter(([_station, usable]) => usable)
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
.map(([station, _]) => station as CraftingStation)
|
|
.filter(station => station.name.toLowerCase().includes(interaction.options.getFocused().toLowerCase()))
|
|
.map(station => ({
|
|
name: `${station.emoji} ${station.name}`,
|
|
value: station.key
|
|
})),
|
|
recipe: async (interaction: AutocompleteInteraction) => {
|
|
const focused = interaction.options.getFocused();
|
|
const station = interaction.options.getString('station');
|
|
|
|
const foundDefaultRecipes = defaultRecipes
|
|
.filter(recipe => recipe.station === station)
|
|
.filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.toLowerCase())).length > 0);
|
|
|
|
const customRecipes = await db<CustomCraftingRecipe>('customCraftingRecipes')
|
|
.where('station', station)
|
|
.where('guild', interaction.guildId);
|
|
|
|
const resolvedCustomRecipes = await Promise.all(customRecipes.map(resolveCustomRecipe));
|
|
|
|
const foundCustomRecipes = resolvedCustomRecipes
|
|
.filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.toLowerCase())).length > 0);
|
|
|
|
const recipes = [...foundDefaultRecipes, ...foundCustomRecipes];
|
|
|
|
return recipes
|
|
.map(recipe => ({
|
|
name: formatRecipe(recipe, true),
|
|
value: recipe.id.toString()
|
|
}));
|
|
}
|
|
}),
|
|
} satisfies Command; |