import { AutocompleteInteraction } from 'discord.js'; import { CustomItem, ItemBehavior, ItemInventory, db } from '../db'; import { Autocomplete } from '../autocomplete'; // uses negative IDs export type DefaultItem = Omit & { behaviors?: Omit[] }; export type Item = DefaultItem | CustomItem; export interface Items { item: Item, quantity: number } export enum DefaultItems { COIN = 1, WORKBENCH = 2, PEBBLE = 3, TWIG = 4, APPLE = 5, BERRIES = 6, LOG = 7, AXE = 8, BLOOD = 9, BAIT = 10, FISHING_ROD = 11, CARP = 12, PUFFERFISH = 13, EXOTIC_FISH = 14, SHOVEL = 15, DIRT = 16, MINESHAFT = 17, PICKAXE = 18, IRON_PICKAXE = 19, COAL = 20, IRON_ORE = 21, IRON_INGOT = 22, DIAMOND = 23, RUBY = 24, EMERALD = 25, FURNACE = 26, FRIED_FISH = 27, } 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 }, { id: -4, name: 'Twig', description: 'Just a tiny bit of wood', emoji: '🌿', type: 'plain', maxStack: 64, untradable: false }, { id: -5, name: 'Apple', description: 'A forager\'s snack', emoji: '🍎', type: 'consumable', maxStack: 16, untradable: false, behaviors: [{ behavior: 'heal', value: 10 }], }, { id: -6, name: 'Berries', description: 'A little treat for the road!', emoji: 'πŸ“', type: 'consumable', maxStack: 16, untradable: false, behaviors: [{ behavior: 'heal', value: 4 }], }, { id: -7, name: 'Log', description: '㏒', emoji: 'πŸͺ΅', type: 'plain', maxStack: 64, untradable: false }, { id: -8, name: 'Axe', description: 'You could chop trees with this. Or commit murder! The choice is up to you', emoji: 'πŸͺ“', type: 'weapon', maxStack: 1, untradable: false }, { id: -9, name: 'Blood', description: 'ow', emoji: '🩸', type: 'plain', maxStack: 50, untradable: false }, { id: -10, name: 'Bait', description: 'I guess you could eat this.', emoji: 'πŸͺ±', type: 'consumable', maxStack: 128, untradable: false, behaviors: [{ behavior: 'heal', value: 1 }], }, { id: -11, name: 'Fishing Rod', description: 'Give a man a fish, and he will eat for a day', emoji: '🎣', type: 'plain', maxStack: 1, untradable: false }, { id: -12, name: 'Carp', description: 'wow', emoji: '🐟️', type: 'plain', maxStack: 16, untradable: false }, { id: -13, name: 'Pufferfish', description: 'yummy!', emoji: '🐑', type: 'plain', maxStack: 16, untradable: false }, { id: -14, name: 'Exotic Fish', description: 'lucky!', emoji: '🐠', type: 'plain', maxStack: 16, untradable: false, }, { id: -15, name: 'Shovel', description: 'Did you know there\'s no shovel emoji', emoji: '♠️', type: 'plain', maxStack: 1, untradable: false, }, { id: -16, name: 'Dirt', description: 'https://media.discordapp.net/attachments/819472665291128873/1081454188325785650/ezgif-2-5ccc7dedf8.gif', emoji: '🟫', type: 'consumable', maxStack: 64, untradable: false, }, { id: -17, name: 'Mineshaft', description: 'A place for you to mine ores and minerals!', emoji: '⛏️', type: 'plain', maxStack: 1, untradable: true }, { id: -18, name: 'Pickaxe', description: 'Basic mining equipment', emoji: '⛏️', type: 'plain', maxStack: 8, untradable: false }, { id: -19, name: 'Iron Pickaxe', description: 'More durable and strong mining equipment', emoji: 'βš’οΈ', type: 'plain', maxStack: 8, untradable: false }, { id: -20, name: 'Coal', description: 'Fuel, NOT EDIBLE', emoji: '◾️', type: 'plain', maxStack: 128, untradable: false }, { id: -21, name: 'Iron Ore', description: 'Unsmelted iron', emoji: '◽️', type: 'plain', maxStack: 128, untradable: false }, { id: -22, name: 'Iron Ingot', description: 'A sturdy material', emoji: '◻️', type: 'plain', maxStack: 128, untradable: false }, { id: -23, name: 'Diamond', description: 'Shiny rock!', emoji: 'πŸ’Ž', type: 'plain', maxStack: 128, untradable: false }, { id: -24, name: 'Ruby', description: 'Reference to the progarmiing......g.', emoji: 'πŸ”»', type: 'plain', maxStack: 128, untradable: false }, { id: -25, name: 'Emerald', description: 'A currency in some other world', emoji: '🟩', type: 'plain', maxStack: 128, untradable: false }, { id: -26, name: 'Furnace', description: 'A smeltery for your own needs', emoji: 'πŸ”₯', type: 'plain', maxStack: 1, untradable: false }, { id: -27, name: 'Fried Fish', description: 'A very nice and filling meal', emoji: '🍱', type: 'consumable', maxStack: 16, untradable: false, behaviors: [{ behavior: 'heal', value: 35 }], }, ]; 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 getCustomItem(id: number) { return await db('customItems') .where('id', id) .first(); } export async function getItemQuantity(user: string, itemID: number): Promise { 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): Promise { const storedItem = await db('itemInventories') .where('user', user) .where('item', item.id) .first(); let inv; if (storedItem) { if (storedItem.quantity + quantity === 0 && item.id !== DefaultItems.BLOOD) { // let blood show as 0x await db('itemInventories') .delete() .where('user', user) .where('item', item.id); return { user: user, item: item.id, quantity: 0 }; } 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 isDefaultItem(item: Item): item is DefaultItem { return (item as DefaultItem).behaviors !== undefined; // ough } export function formatItem(item: Item | undefined, disableBold = false) { if (!item) return disableBold ? '? MISSINGNO' : '? **MISSINGNO**'; return disableBold ? `${item.emoji} ${item.name}` : `${item.emoji} **${item.name}**`; } export function formatItems(item: Item | undefined, quantity: number, disableBold = false) { return `${quantity}x ${formatItem(item, disableBold)}`; } export function formatItemsArray(items: Items[], disableBold = false) { if (items.length === 0) return disableBold ? 'nothing' : '**nothing**'; return items.map(i => formatItems(i.item, i.quantity, disableBold)).join(' '); } function createItemAutocomplete(onlyCustom: boolean, filterType: 'plain' | 'weapon' | 'consumable' | null, inventory: boolean = false): Autocomplete { return async (interaction: AutocompleteInteraction) => { const focused = interaction.options.getFocused(); const itemQuery = 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); if (filterType) itemQuery.where('type', filterType); if (inventory) itemQuery .innerJoin('itemInventories', 'itemInventories.item', '=', 'customItems.id') .where('itemInventories.user', '=', interaction.member!.user.id) .where('itemInventories.quantity', '>', '0'); const customItems = await itemQuery; let foundDefaultItems = defaultItems.filter(item => item.name.toUpperCase().includes(focused.toUpperCase())); if (filterType) foundDefaultItems = foundDefaultItems.filter(i => i.type === filterType); if (inventory) foundDefaultItems = (await Promise.all(foundDefaultItems.map(async i => ({...i, inv: await getItemQuantity(interaction.member!.user.id, i.id)})))).filter(i => i.inv.quantity > 0); let items; if (onlyCustom) { items = customItems; } else { items = [...foundDefaultItems, ...customItems]; } return items.map(choice => ({ name: `${choice.emoji} ${choice.name}`, value: choice.id.toString() })); }; } export const itemAutocomplete = createItemAutocomplete(false, null); export const customItemAutocomplete = createItemAutocomplete(true, null); export const plainAutocomplete = createItemAutocomplete(false, 'plain'); export const weaponAutocomplete = createItemAutocomplete(false, 'weapon'); export const consumableAutocomplete = createItemAutocomplete(false, 'consumable'); export const plainInventoryAutocomplete = createItemAutocomplete(false, 'plain', true); export const weaponInventoryAutocomplete = createItemAutocomplete(false, 'weapon', true); export const consumableInventoryAutocomplete = createItemAutocomplete(false, 'consumable', true);