jillo-bot/src/commands/craft.ts

147 lines
6.2 KiB
TypeScript
Raw Normal View History

2023-11-16 11:33:11 +01:00
import { AutocompleteInteraction, GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js';
2023-11-18 13:24:56 +01:00
import { CraftingStationCooldown, CustomCraftingRecipe, db } from '../lib/db';
2023-11-15 22:01:55 +01:00
import { getStation, canUseStation, craftingStations, verb, CraftingStation } from '../lib/rpg/craftingStations';
import { formatItem, getItemQuantity, formatItems, getMaxStack, giveItem, formatItemsArray } from '../lib/rpg/items';
2023-11-18 13:24:56 +01:00
import { getRecipe, defaultRecipes, formatRecipe, resolveCustomRecipe } from '../lib/rpg/recipes';
2023-11-16 11:33:11 +01:00
import { Command } from '../types/index';
2023-11-21 21:28:32 +01:00
import { initHealth, resetInvincible } from '../lib/rpg/pvp';
2023-11-22 14:45:55 +01:00
import { set } from '../lib/autocomplete';
2023-11-15 16:04:36 +01:00
2023-11-16 11:33:11 +01:00
export default {
2023-11-15 16:04:36 +01:00
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),
2023-11-16 11:33:11 +01:00
execute: async (interaction: CommandInteraction) => {
2023-11-15 16:04:36 +01:00
if (!interaction.isChatInputCommand()) return;
2023-11-16 11:33:11 +01:00
const member = interaction.member! as GuildMember;
2023-11-21 20:17:15 +01:00
await initHealth(member.id);
2023-11-15 16:04:36 +01:00
const recipeID = parseInt(interaction.options.getString('recipe', true));
await interaction.deferReply({ephemeral: true});
2023-11-18 13:24:56 +01:00
const recipe = await getRecipe(recipeID);
2023-11-15 16:04:36 +01:00
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);
2023-11-15 17:09:15 +01:00
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.)`);
2023-11-15 16:04:36 +01:00
}
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()
2023-11-15 16:04:36 +01:00
});
} else {
await db<CraftingStationCooldown>('craftingStationCooldowns')
.where('station', station.key)
.where('user', member.id)
.update({
usedAt: Date.now()
2023-11-15 16:04:36 +01:00
});
}
nextUsableAt = Date.now() + station.cooldown * 1000;
}
2023-11-21 21:28:32 +01:00
await resetInvincible(member.id);
2023-11-15 22:01:55 +01:00
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>` : ''}`);
2023-11-15 16:04:36 +01:00
},
2023-11-22 14:45:55 +01:00
autocomplete: set({
2023-11-22 14:32:54 +01:00
station: async (interaction: AutocompleteInteraction) =>
(await Promise.all(
2023-11-15 22:01:55 +01:00
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)
2023-11-22 14:32:54 +01:00
.filter(station => station.name.toLowerCase().includes(interaction.options.getFocused().toLowerCase()))
2023-11-15 16:04:36 +01:00
.map(station => ({
name: `${station.emoji} ${station.name}`,
value: station.key
2023-11-22 14:32:54 +01:00
})),
recipe: async (interaction: AutocompleteInteraction) => {
const focused = interaction.options.getFocused();
2023-11-18 13:24:56 +01:00
const station = interaction.options.getString('station');
2023-11-15 16:04:36 +01:00
2023-11-18 13:24:56 +01:00
const foundDefaultRecipes = defaultRecipes
.filter(recipe => recipe.station === station)
2023-11-22 14:32:54 +01:00
.filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.toLowerCase())).length > 0);
2023-11-18 13:24:56 +01:00
const customRecipes = await db<CustomCraftingRecipe>('customCraftingRecipes')
.where('station', station)
.where('guild', interaction.guildId);
2023-11-18 13:24:56 +01:00
const resolvedCustomRecipes = await Promise.all(customRecipes.map(resolveCustomRecipe));
const foundCustomRecipes = resolvedCustomRecipes
2023-11-22 14:32:54 +01:00
.filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.toLowerCase())).length > 0);
2023-11-18 13:24:56 +01:00
const recipes = [...foundDefaultRecipes, ...foundCustomRecipes];
2023-11-22 14:32:54 +01:00
return recipes
.map(recipe => ({
name: formatRecipe(recipe, true),
value: recipe.id.toString()
}));
2023-11-15 16:04:36 +01:00
}
2023-11-22 14:45:55 +01:00
}),
2023-11-16 11:33:11 +01:00
} satisfies Command;