359 lines
13 KiB
TypeScript
359 lines
13 KiB
TypeScript
import { AutocompleteInteraction, CommandInteraction, SlashCommandBuilder } from 'discord.js';
|
|
import { Counter, CustomCraftingRecipeItem, CustomItem, ItemBehavior, db } from '../lib/db';
|
|
import { 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<CustomItem>('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<ItemBehavior>('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<ItemBehavior>('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<ItemBehavior>('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<ItemBehavior>('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<CustomCraftingRecipeItem>('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<Counter>('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<CustomItem>('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');
|
|
if (itemID) {
|
|
const item = await getItem(parseInt(itemID));
|
|
if (item) {
|
|
foundBehaviors = foundBehaviors.filter(b => b.type === item.type);
|
|
}
|
|
}
|
|
|
|
return foundBehaviors.map(b => ({name: `${b.type}:${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 behaviors = await db<ItemBehavior>('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: `${b.behavior.type}:${formatBehavior(b.behavior, b.value)} - ${b.behavior.description}`,
|
|
value: b.behavior.name
|
|
}));
|
|
},
|
|
}),
|
|
} satisfies Command; |