import { AutocompleteInteraction, User } from 'discord.js'; import { CustomItem, ItemInventory, db } from '../db'; type DefaultItem = Omit; // uses negative IDs type Item = DefaultItem | CustomItem; interface Behavior { name: string, description: string, itemType: 'plain' | 'weapon' | 'consumable', // triggers upon use // for 'weapons', this is on hit // for 'consumable', this is on use // for 'plain', ...?? // returns `true` upon success, `false` otherwise action?: (item: Item, user: User) => Promise } enum DefaultItems { COIN = 1, WORKBENCH = 2, PEBBLE = 3 } export const defaultItems: DefaultItem[] = [ { 'id': -1, 'name': 'Coin', 'emoji': '🪙', 'type': 'plain', 'maxStack': 9999, 'untradable': false }, { 'id': -2, 'name': 'Workbench', 'description': 'A place for you to work with tools, for simple things', 'emoji': '🛠️', 'type': 'plain', 'maxStack': 1, 'untradable': false }, { 'id': -3, 'name': 'Pebble', 'description': 'If you get 5 of them you will instantly ! !!!', 'emoji': '🪨', 'type': 'plain', 'maxStack': 64, 'untradable': false } ]; export const behaviors: Behavior[] = [ { 'name': 'heal', 'description': 'Heals the user by `behaviorValue`', 'itemType': 'consumable', 'action': async (item: Item, user: User) => { // todo return false; } } ]; interface Items { item: Item, quantity: number } const defaultFormatRecipe = (inputs: Items[], requirements: Items[], outputs: Items[]) => `${formatItemsArray(inputs)}${requirements.length === 0 ? '' : ` w/ ${formatItemsArray(requirements)}`} => ${formatItemsArray(outputs)}`; interface CraftingStation { key: string, name: string, description: string, emoji: string, requires?: Item, // in seconds cooldown?: number, formatRecipe?: (inputs: Items[], requirements: Items[], outputs: Items[]) => string, manipulateResults?: (outputs: Items[]) => Items[] } export const craftingStations: CraftingStation[] = [ { key: 'forage', name: 'Forage', description: 'Pick up various sticks and stones from the forest', emoji: '🌲', cooldown: 60 * 5, formatRecipe: (_inputs, _requirements, outputs) => `${outputs.map(i => formatItems(i.item, i.quantity) + '?').join(' ')}`, manipulateResults: (outputs) => outputs.map(o => ({item: o.item, quantity: Math.floor(o.quantity * Math.random())})).filter(o => o.quantity !== 0) }, { key: 'hand', name: 'Hand', description: 'You can use your hands to make a small assortment of things', emoji: '✋' }, { key: 'workbench', name: 'Workbench', description: 'A place for you to work with tools, for simple things', emoji: '🛠️', requires: getDefaultItem(DefaultItems.WORKBENCH) } ]; interface Recipe { station: string, inputs: Items[], requirements: Items[], outputs: Items[] } export const recipes: Recipe[] = [ { station: 'forage', inputs: [], requirements: [], outputs: [ { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } ] }, { station: 'workbench', inputs: [ { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } ], requirements: [], outputs: [ { item: getDefaultItem(DefaultItems.WORKBENCH), quantity: 1 } ] } ]; export async function getCustomItem(id: number) { return await db('customItems') .where('id', id) .first(); } export function getDefaultItem(id: DefaultItems): Item export function getDefaultItem(id: number): Item | undefined { return defaultItems.find(item => Math.abs(item.id) === Math.abs(id)); } export async function getItem(id: number): Promise { if (id >= 0) { return await getCustomItem(id); } else { return getDefaultItem(id); } } export async function getItemQuantity(user: string, itemID: number) { return (await db('itemInventories') .where('item', itemID) .where('user', user) .first()) || { 'user': user, 'item': itemID, 'quantity': 0 }; } export async function giveItem(user: string, item: Item, quantity = 1) { const storedItem = await db('itemInventories') .where('user', user) .where('item', item.id) .first(); let inv; if (storedItem) { inv = await db('itemInventories') .update({ 'quantity': db.raw('MIN(quantity + ?, ?)', [quantity, getMaxStack(item)]) }) .limit(1) .where('user', user) .where('item', item.id) .returning('*'); } else { inv = await db('itemInventories') .insert({ 'user': user, 'item': Math.min(item.id, getMaxStack(item)), 'quantity': quantity }) .returning('*'); } return inv[0]; } export function getMaxStack(item: Item) { return item.type === 'weapon' ? 1 : item.maxStack; } export function formatItem(item: Item | undefined) { if (!item) return '? **MISSINGNO**'; return `${item.emoji} **${item.name}**`; } export function formatItems(item: Item | undefined, quantity: number) { return `${quantity}x ${formatItem(item)}`; } export function formatItemsArray(items: Items[]) { return items.map(i => formatItems(i.item, i.quantity)).join(' '); } export async function itemAutocomplete(interaction: AutocompleteInteraction) { const focused = interaction.options.getFocused(); const customItems = await db('customItems') .select('emoji', 'name', 'id') // @ts-expect-error this LITERALLY works .whereLike(db.raw('UPPER(name)'), `%${focused.toUpperCase()}%`) .where('guild', interaction.guildId!) .limit(25); const foundDefaultItems = defaultItems.filter(item => item.name.toUpperCase().includes(focused.toUpperCase())); const items = [...foundDefaultItems, ...customItems]; await interaction.respond( items.map(choice => ({ name: `${choice.emoji} ${choice.name}`, value: choice.id.toString() })) ); }