crafting?????
This commit is contained in:
parent
68d7e28335
commit
3fcfe5851b
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.up = function(knex) {
|
||||||
|
return knex.schema
|
||||||
|
.createTable('craftingStationCooldowns', table => {
|
||||||
|
table.string('station').notNullable();
|
||||||
|
table.string('user').notNullable();
|
||||||
|
table.timestamp('usedAt').notNullable();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.down = function(knex) {
|
||||||
|
return knex.schema
|
||||||
|
.dropTable('craftingStationCooldowns');
|
||||||
|
};
|
|
@ -0,0 +1,122 @@
|
||||||
|
import { AutocompleteInteraction, GuildMember, Interaction, SlashCommandBuilder } from 'discord.js';
|
||||||
|
import { craftingStations, defaultRecipes } from '../lib/rpg/data';
|
||||||
|
import { canUseStation, formatItem, formatItems, formatItemsArray, formatRecipe, getItemQuantity, getMaxStack, getRecipe, getStation, giveItem } from '../lib/rpg/items';
|
||||||
|
import { CraftingStationCooldown, db } from '../lib/db';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
data: new SlashCommandBuilder()
|
||||||
|
.setName('craft')
|
||||||
|
.setDescription('Craft an item with items you have')
|
||||||
|
.addStringOption(option =>
|
||||||
|
option
|
||||||
|
.setName('station')
|
||||||
|
.setAutocomplete(true)
|
||||||
|
.setDescription('Which station to use')
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
.addStringOption(option =>
|
||||||
|
option
|
||||||
|
.setName('recipe')
|
||||||
|
.setAutocomplete(true)
|
||||||
|
.setDescription('What to craft')
|
||||||
|
.setRequired(true)
|
||||||
|
)
|
||||||
|
.setDMPermission(false),
|
||||||
|
|
||||||
|
execute: async (interaction: Interaction, member: GuildMember) => {
|
||||||
|
if (!interaction.isChatInputCommand()) return;
|
||||||
|
|
||||||
|
const recipeID = parseInt(interaction.options.getString('recipe', true));
|
||||||
|
|
||||||
|
await interaction.deferReply({ephemeral: true});
|
||||||
|
|
||||||
|
const recipe = getRecipe(recipeID);
|
||||||
|
if (!recipe) return interaction.followUp('Recipe does not exist!');
|
||||||
|
|
||||||
|
const station = getStation(recipe.station)!;
|
||||||
|
if (!canUseStation(member.id, station)) return interaction.followUp(`${station.emoji} You need ${formatItem(station.requires)} to use this station!`);
|
||||||
|
|
||||||
|
for (const input of recipe.inputs) {
|
||||||
|
const inv = await getItemQuantity(member.id, input.item.id);
|
||||||
|
if (inv.quantity < input.quantity) return interaction.followUp(`You need ${formatItems(input.item, input.quantity)} for this recipe! (You have ${formatItems(input.item, inv.quantity)})`);
|
||||||
|
}
|
||||||
|
for (const req of recipe.requirements) {
|
||||||
|
const inv = await getItemQuantity(member.id, req.item.id);
|
||||||
|
if (inv.quantity < req.quantity) return interaction.followUp(`You need ${formatItems(req.item, req.quantity)} to begin this recipe! (You have ${formatItems(req.item, inv.quantity)}. Don't worry, these items will not be consumed.)`);
|
||||||
|
}
|
||||||
|
for (const out of recipe.outputs) {
|
||||||
|
const inv = await getItemQuantity(member.id, out.item.id);
|
||||||
|
if (inv.quantity + out.quantity > getMaxStack(out.item)) return interaction.followUp(`You do not have enough inventory storage for this recipe! (${formatItems(out.item, inv.quantity + out.quantity)} is bigger than the stack size of ${getMaxStack(out.item)}.)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cooldown;
|
||||||
|
if (station.cooldown) {
|
||||||
|
cooldown = await db<CraftingStationCooldown>('craftingStationCooldowns')
|
||||||
|
.where('station', station.key)
|
||||||
|
.where('user', member.id)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (cooldown && (cooldown.usedAt + station.cooldown * 1000) > Date.now())
|
||||||
|
return interaction.followUp(`${station.emoji} You can use this station again <t:${Math.floor(cooldown.usedAt / 1000 + station.cooldown)}:R>!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// proceed with crafting!
|
||||||
|
|
||||||
|
for (const input of recipe.inputs) {
|
||||||
|
giveItem(member.id, input.item, -input.quantity);
|
||||||
|
}
|
||||||
|
const outputs = station.manipulateResults ? station.manipulateResults(recipe.outputs) : recipe.outputs;
|
||||||
|
for (const output of outputs) {
|
||||||
|
giveItem(member.id, output.item, output.quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextUsableAt;
|
||||||
|
if (station.cooldown) {
|
||||||
|
if (!cooldown) {
|
||||||
|
await db<CraftingStationCooldown>('craftingStationCooldowns')
|
||||||
|
.insert({
|
||||||
|
station: station.key,
|
||||||
|
user: member.id,
|
||||||
|
usedAt: db.fn.now()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await db<CraftingStationCooldown>('craftingStationCooldowns')
|
||||||
|
.where('station', station.key)
|
||||||
|
.where('user', member.id)
|
||||||
|
.update({
|
||||||
|
usedAt: db.fn.now()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
nextUsableAt = Date.now() + station.cooldown * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return interaction.followUp(`${station.emoji} Crafted ${formatItemsArray(outputs)}!${nextUsableAt ? `\n${station.name} usable again <t:${Math.floor(nextUsableAt / 1000)}:R>` : ''}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
autocomplete: async (interaction: AutocompleteInteraction) => {
|
||||||
|
const focused = interaction.options.getFocused(true);
|
||||||
|
|
||||||
|
if (focused.name === 'station') {
|
||||||
|
const found = craftingStations
|
||||||
|
.filter(station => canUseStation(interaction.user.id, station))
|
||||||
|
.filter(station => station.name.toLowerCase().includes(focused.value.toLowerCase()))
|
||||||
|
.map(station => ({
|
||||||
|
name: `${station.emoji} ${station.name}`,
|
||||||
|
value: station.key
|
||||||
|
}));
|
||||||
|
|
||||||
|
return interaction.respond(found);
|
||||||
|
} else if (focused.name === 'recipe') {
|
||||||
|
const found = defaultRecipes
|
||||||
|
.filter(recipe => recipe.station === interaction.options.getString('station'))
|
||||||
|
.filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.value.toLowerCase())).length > 0)
|
||||||
|
.map(recipe => ({
|
||||||
|
name: formatRecipe(recipe),
|
||||||
|
value: recipe.id
|
||||||
|
}));
|
||||||
|
|
||||||
|
return interaction.respond(found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,6 +1,7 @@
|
||||||
import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'discord.js';
|
import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'discord.js';
|
||||||
import { CustomItem, db } from '../lib/db';
|
import { CustomItem, db } from '../lib/db';
|
||||||
import { behaviors, formatItems, getItem, giveItem, itemAutocomplete } from '../lib/rpg/items';
|
import { formatItems, getItem, giveItem, itemAutocomplete } from '../lib/rpg/items';
|
||||||
|
import { behaviors } from '../lib/rpg/data';
|
||||||
|
|
||||||
//function extendOption(t: string) {
|
//function extendOption(t: string) {
|
||||||
// return {name: t, value: t};
|
// return {name: t, value: t};
|
||||||
|
|
|
@ -63,4 +63,9 @@ export interface ItemInventory {
|
||||||
user: string,
|
user: string,
|
||||||
item: number,
|
item: number,
|
||||||
quantity: number
|
quantity: number
|
||||||
|
}
|
||||||
|
export interface CraftingStationCooldown {
|
||||||
|
station: string,
|
||||||
|
user: string,
|
||||||
|
usedAt: number
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { Behavior, CraftingStation, DefaultItem, Recipe, formatItems, getDefaultItem } from './items';
|
import { Behavior, CraftingStation, DefaultItem, DefaultRecipe, formatItems, getDefaultItem } from './items';
|
||||||
|
|
||||||
export enum DefaultItems {
|
export enum DefaultItems {
|
||||||
COIN = 1,
|
COIN = 1,
|
||||||
|
@ -73,8 +73,9 @@ export const craftingStations: CraftingStation[] = [
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const recipes: Recipe[] = [
|
export const defaultRecipes: DefaultRecipe[] = [
|
||||||
{
|
{
|
||||||
|
id: -1,
|
||||||
station: 'forage',
|
station: 'forage',
|
||||||
inputs: [],
|
inputs: [],
|
||||||
requirements: [],
|
requirements: [],
|
||||||
|
@ -83,6 +84,7 @@ export const recipes: Recipe[] = [
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: -2,
|
||||||
station: 'workbench',
|
station: 'workbench',
|
||||||
inputs: [
|
inputs: [
|
||||||
{ item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 }
|
{ item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { AutocompleteInteraction, User } from 'discord.js';
|
import { AutocompleteInteraction, User } from 'discord.js';
|
||||||
import { CustomItem, ItemInventory, db } from '../db';
|
import { CustomItem, ItemInventory, db } from '../db';
|
||||||
import { DefaultItems, defaultItems } from './data';
|
import { DefaultItems, craftingStations, defaultItems, defaultRecipes } from './data';
|
||||||
|
|
||||||
export type DefaultItem = Omit<CustomItem, 'guild'>; // uses negative IDs
|
export type DefaultItem = Omit<CustomItem, 'guild'>; // uses negative IDs
|
||||||
export type Item = DefaultItem | CustomItem;
|
export type Item = DefaultItem | CustomItem;
|
||||||
|
@ -37,12 +37,22 @@ export interface CraftingStation {
|
||||||
manipulateResults?: (outputs: Items[]) => Items[]
|
manipulateResults?: (outputs: Items[]) => Items[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Recipe {
|
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,
|
station: string,
|
||||||
inputs: Items[],
|
inputs: Items[],
|
||||||
requirements: Items[],
|
requirements: Items[],
|
||||||
outputs: Items[]
|
outputs: Items[]
|
||||||
}
|
}
|
||||||
|
export type Recipe = DefaultRecipe
|
||||||
|
|
||||||
export async function getCustomItem(id: number) {
|
export async function getCustomItem(id: number) {
|
||||||
return await db<CustomItem>('customItems')
|
return await db<CustomItem>('customItems')
|
||||||
|
@ -63,7 +73,21 @@ export async function getItem(id: number): Promise<Item | undefined> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getItemQuantity(user: string, itemID: number) {
|
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')
|
return (await db<ItemInventory>('itemInventories')
|
||||||
.where('item', itemID)
|
.where('item', itemID)
|
||||||
.where('user', user)
|
.where('user', user)
|
||||||
|
|
Loading…
Reference in New Issue