import { AutocompleteInteraction, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { Counter, CustomCraftingRecipeItem, CustomItem, ItemBehavior, db } from '../lib/db'; import { Item, customItemAutocomplete, formatItem, formatItems, getCustomItem, getItem, giveItem, isDefaultItem, itemAutocomplete } from '../lib/rpg/items'; import { Command } from '../types/index'; import { formatRecipe, getCustomRecipe } from '../lib/rpg/recipes'; import { behaviors, formatBehavior, getBehavior } from '../lib/rpg/behaviors'; import { set } from '../lib/autocomplete'; //function extendOption(t: string) { // return {name: t, value: t}; //} export default { data: new SlashCommandBuilder() .setName('item') .setDescription('[ADMIN] Create, edit and otherwise deal with custom items') .addSubcommandGroup(grp => grp .setName('add') .setDescription('[ADMIN] Create an item') .addSubcommand(cmd => cmd .setName('plain') .setDescription('A normal, functionless item') .addStringOption(opt => opt .setName('name') .setDescription('The item name') .setRequired(true) ) .addStringOption(opt => opt .setName('emoji') .setDescription('An emoji or symbol that could represent this item') .setRequired(true) ) .addStringOption(opt => opt .setName('description') .setDescription('A short description') ) .addIntegerOption(opt => opt .setName('maxstack') .setDescription('Maximum amount of this item you\'re able to hold at once') ) .addBooleanOption(opt => opt .setName('untradable') .setDescription('Can you give this item to other people?') ) ) .addSubcommand(cmd => cmd .setName('weapon') .setDescription('A weapon that you can attack things with') .addStringOption(opt => opt .setName('name') .setDescription('The item name') .setRequired(true) ) .addStringOption(opt => opt .setName('emoji') .setDescription('An emoji or symbol that could represent this item') .setRequired(true) ) .addIntegerOption(opt => opt .setName('damage') .setDescription('How much base damage this weapon is intended to deal') .setRequired(true) ) .addStringOption(opt => opt .setName('description') .setDescription('A short description') ) .addBooleanOption(opt => opt .setName('untradable') .setDescription('Can you give this item to other people?') ) ) .addSubcommand(cmd => cmd .setName('consumable') .setDescription('Consumable item, usable once and never again') .addStringOption(opt => opt .setName('name') .setDescription('The item name') .setRequired(true) ) .addStringOption(opt => opt .setName('emoji') .setDescription('An emoji or symbol that could represent this item') .setRequired(true) ) .addStringOption(opt => opt .setName('description') .setDescription('A short description') ) .addIntegerOption(opt => opt .setName('maxstack') .setDescription('Maximum amount of this item you\'re able to hold at once') ) .addBooleanOption(opt => opt .setName('untradable') .setDescription('Can you give this item to other people?') ) ) ) .addSubcommand(cmd => cmd .setName('give') .setDescription('[ADMIN] Give a user an item') .addUserOption(opt => opt .setName('who') .setDescription('The user') .setRequired(true) ) .addStringOption(opt => opt .setName('item') .setDescription('The item') .setAutocomplete(true) .setRequired(true) ) .addIntegerOption(opt => opt .setName('quantity') .setDescription('Amount of items to give') ) ) .addSubcommand(cmd => cmd .setName('delete') .setDescription('[ADMIN] Delete a custom item') .addStringOption(opt => opt .setName('customitem') .setDescription('The item') .setAutocomplete(true) .setRequired(true) ) ) .addSubcommandGroup(grp => grp .setName('behavior') .setDescription('[ADMIN] Item behavior management') .addSubcommand(cmd => cmd .setName('add') .setDescription('[ADMIN] Give an item a behavior') .addStringOption(opt => opt .setName('customitem') .setDescription('The item') .setAutocomplete(true) .setRequired(true) ) .addStringOption(opt => opt .setName('behavior') .setDescription('The behavior to add') .setAutocomplete(true) .setRequired(true) ) .addNumberOption(opt => opt .setName('value') .setDescription('A value to assign the behavior, not always applicable') ) ) .addSubcommand(cmd => cmd .setName('remove') .setDescription('[ADMIN] Rid an item of a behavior') .addStringOption(opt => opt .setName('customitem') .setDescription('The item') .setAutocomplete(true) .setRequired(true) ) .addStringOption(opt => opt .setName('removebehavior') .setDescription('The behavior to remove') .setAutocomplete(true) .setRequired(true) ) ) ) .setDefaultMemberPermissions('0') .setDMPermission(false), execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; await interaction.deferReply({ephemeral: true}); const subcommand = interaction.options.getSubcommand(true); const group = interaction.options.getSubcommandGroup(); if (group === 'add') { const item = await db('customItems') .insert({ 'guild': interaction.guildId!, 'name': interaction.options.getString('name', true).trim(), 'description': interaction.options.getString('description') || undefined, 'emoji': interaction.options.getString('emoji', true).trim(), 'type': subcommand as 'plain' | 'weapon' | 'consumable', // kind of wild that ts makes you do this 'maxStack': (interaction.options.getInteger('maxstack') || interaction.options.getInteger('damage')) || (subcommand === 'weapon' ? 1 : 64), 'untradable': interaction.options.getBoolean('untradable') || false, }) .returning('*'); await interaction.followUp(`${formatItem(item[0])} has been successfully created! 🎉\nYou can now use \`/item behavior\` to give it some custom functionality.`); } else if (group === 'behavior') { const itemID = parseInt(interaction.options.getString('customitem', true)); const item = await getCustomItem(itemID); if (!item) return await interaction.followUp('No such item exists!'); if (item.guild !== interaction.guildId) return await interaction.followUp('This item is from a different server! Nice try though'); if (subcommand === 'add') { const behaviorName = interaction.options.getString('behavior', true); const value = interaction.options.getNumber('value'); const behavior = getBehavior(behaviorName); if (!behavior) return await interaction.followUp(`No such behavior ${behaviorName}!`); const existingBehavior = await db('itemBehaviors') .where('item', item.id) .where('behavior', behavior.name) .first(); if (existingBehavior) { return await interaction.followUp(`${formatItem(item)} already has **${formatBehavior(behavior, existingBehavior.value)}**!`); } await db('itemBehaviors') .insert({ item: item.id, behavior: behavior.name, value: value || undefined, }); return await interaction.followUp(`${formatItem(item)} now has **${formatBehavior(behavior, value || undefined)}**`); } else if (subcommand === 'remove') { const behaviorName = interaction.options.getString('removebehavior', true); const behavior = getBehavior(behaviorName); if (!behavior) return await interaction.followUp(`No such behavior ${behaviorName}!`); const existingBehavior = await db('itemBehaviors') .where('item', item.id) .where('behavior', behavior.name) .first(); if (!existingBehavior) { return await interaction.followUp(`${formatItem(item)} does not have behavior \`${behaviorName}\`!`); } await db('itemBehaviors') .where('item', item.id) .where('behavior', behavior.name) .delete(); return await interaction.followUp(`Deleted behavior ${formatBehavior(behavior, existingBehavior.value)} from ${formatItem(item)}.`); } } else { if (subcommand === 'give') { const user = interaction.options.getUser('who', true); const itemID = parseInt(interaction.options.getString('item', true)); const quantity = interaction.options.getInteger('quantity') || 1; const item = await getItem(itemID); if (!item) return interaction.followUp('No such item exists!'); if (!isDefaultItem(item)) { if (item.guild !== interaction.guildId) return await interaction.followUp('This item is from a different server! Nice try though'); } const inv = await giveItem(user.id, item, quantity); await interaction.followUp(`${user.toString()} now has ${formatItems(item, inv.quantity)}.`); } else if (subcommand === 'delete') { const itemID = parseInt(interaction.options.getString('customitem', true)); const item = await getItem(itemID); if (!item) return interaction.followUp('No such item exists!'); const usedIn = await db('customCraftingRecipeItems') .where('item', item.id); if (usedIn.length > 0) { const recipes = (await Promise.all(usedIn.map(i => getCustomRecipe(i.id)))).filter(r => r !== undefined); return interaction.followUp(`⚠️ This item is used in the following recipes:\n${recipes.map(r => `- ${formatRecipe(r!)}`).join('\n')}`); } const linkedWith = await db('counters') .where('linkedItem', item.id); if (linkedWith.length > 0) { return interaction.followUp(`⚠️ This item is used in the following counters:\n${linkedWith.map(c => `- ${c.key} ${c.value} in <#${c.channel}>`).join('\n')}`); } await db('customItems') .where('id', item.id) .delete(); interaction.followUp(`${formatItem(item)} has been deleted.`); } } }, autocomplete: set({ item: itemAutocomplete, customitem: customItemAutocomplete, behavior: async (interaction: AutocompleteInteraction) => { const focused = interaction.options.getFocused(); let foundBehaviors = behaviors.filter(b => b.name.toLowerCase().includes(focused.toLowerCase())); const itemID = interaction.options.getString('customitem'); let item: Item | undefined; if (itemID) { item = await getItem(parseInt(itemID)); if (item) { foundBehaviors = foundBehaviors.filter(b => b.types.includes(item!.type)); } } return foundBehaviors.map(b => ({name: `${item ? item.type : b.types[0]}:${b.name} - ${b.description}`, value: b.name})); }, removebehavior: async (interaction: AutocompleteInteraction) => { const focused = interaction.options.getFocused(); const itemID = interaction.options.getString('customitem'); if (!itemID) return []; const item = await getItem(parseInt(itemID)); if (!item) return []; const behaviors = await db('itemBehaviors') .where('item', itemID); const foundBehaviors = behaviors .map(b => ({ behavior: getBehavior(b.behavior)!, value: b.value })) .filter(b => b.behavior) .filter(b => b.behavior.name.toLowerCase().includes(focused.toLowerCase())); return foundBehaviors.map(b => ({ name: `${item.type}:${formatBehavior(b.behavior, b.value)} - ${b.behavior.description}`, value: b.behavior.name })); }, }), } satisfies Command;