163 lines
4.8 KiB
TypeScript
163 lines
4.8 KiB
TypeScript
import { AutocompleteInteraction, User } from 'discord.js';
|
|
import { CustomItem, ItemInventory, db } from '../db';
|
|
import { DefaultItems, craftingStations, defaultItems, defaultRecipes } from './data';
|
|
|
|
export type DefaultItem = Omit<CustomItem, 'guild'>; // uses negative IDs
|
|
export type Item = DefaultItem | CustomItem;
|
|
|
|
export 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<boolean>
|
|
}
|
|
|
|
export interface Items {
|
|
item: Item,
|
|
quantity: number
|
|
}
|
|
|
|
const defaultFormatRecipe = (inputs: Items[], requirements: Items[], outputs: Items[]) =>
|
|
`${formatItemsArray(inputs)}${requirements.length === 0 ? '' : ` w/ ${formatItemsArray(requirements)}`} => ${formatItemsArray(outputs)}`;
|
|
|
|
export 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 function getStation(key: string) {
|
|
return craftingStations.find(station => station.key === key);
|
|
}
|
|
export function formatRecipe(recipe: DefaultRecipe) {
|
|
const station = getStation(recipe.station);
|
|
return (station?.formatRecipe || defaultFormatRecipe)(recipe.inputs, recipe.requirements, recipe.outputs);
|
|
}
|
|
|
|
export interface DefaultRecipe {
|
|
id: number,
|
|
station: string,
|
|
inputs: Items[],
|
|
requirements: Items[],
|
|
outputs: Items[]
|
|
}
|
|
export type Recipe = DefaultRecipe
|
|
|
|
export async function getCustomItem(id: number) {
|
|
return await db<CustomItem>('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<Item | undefined> {
|
|
if (id >= 0) {
|
|
return await getCustomItem(id);
|
|
} else {
|
|
return getDefaultItem(id);
|
|
}
|
|
}
|
|
|
|
export function getDefaultRecipe(id: number): DefaultRecipe | undefined {
|
|
return defaultRecipes.find(recipe => recipe.id === id);
|
|
}
|
|
export function getRecipe(id: number): Recipe | undefined {
|
|
return getDefaultRecipe(id); // currently just a stub
|
|
}
|
|
|
|
export async function canUseStation(user: string, station: CraftingStation) {
|
|
if (!station.requires) return true;
|
|
|
|
const inv = await getItemQuantity(user, station.requires.id);
|
|
return inv.quantity > 0;
|
|
}
|
|
|
|
export async function getItemQuantity(user: string, itemID: number): Promise<ItemInventory> {
|
|
return (await db<ItemInventory>('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<ItemInventory>('itemInventories')
|
|
.where('user', user)
|
|
.where('item', item.id)
|
|
.first();
|
|
|
|
let inv;
|
|
if (storedItem) {
|
|
inv = await db<ItemInventory>('itemInventories')
|
|
.update({
|
|
quantity: db.raw('MIN(quantity + ?, ?)', [quantity, getMaxStack(item)])
|
|
})
|
|
.limit(1)
|
|
.where('user', user)
|
|
.where('item', item.id)
|
|
.returning('*');
|
|
} else {
|
|
inv = await db<ItemInventory>('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<CustomItem>('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() }))
|
|
);
|
|
} |