Compare commits
6 Commits
68d7e28335
...
39014d3b90
Author | SHA1 | Date |
---|---|---|
Jill | 39014d3b90 | |
Jill | 32cdaf5199 | |
Jill | 169b81ea06 | |
Jill | 41af7fa2e2 | |
Jill | 42d875a68f | |
Jill | 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,123 @@
|
|||
import { AutocompleteInteraction, GuildMember, Interaction, SlashCommandBuilder } from 'discord.js';
|
||||
import { CraftingStationCooldown, db } from '../lib/db';
|
||||
import { getStation, canUseStation, craftingStations } from '../lib/rpg/craftingStations';
|
||||
import { formatItem, getItemQuantity, formatItems, getMaxStack, giveItem, formatItemsArray } from '../lib/rpg/items';
|
||||
import { getRecipe, defaultRecipes, formatRecipe } from '../lib/rpg/recipes';
|
||||
|
||||
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: Date.now()
|
||||
});
|
||||
} else {
|
||||
await db<CraftingStationCooldown>('craftingStationCooldowns')
|
||||
.where('station', station.key)
|
||||
.where('user', member.id)
|
||||
.update({
|
||||
usedAt: Date.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, true),
|
||||
value: recipe.id.toString()
|
||||
}));
|
||||
|
||||
return interaction.respond(found);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'discord.js';
|
||||
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/behaviors';
|
||||
|
||||
//function extendOption(t: string) {
|
||||
// return {name: t, value: t};
|
||||
|
|
|
@ -63,4 +63,9 @@ export interface ItemInventory {
|
|||
user: string,
|
||||
item: number,
|
||||
quantity: number
|
||||
}
|
||||
export interface CraftingStationCooldown {
|
||||
station: string,
|
||||
user: string,
|
||||
usedAt: number
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import type { User } from 'discord.js';
|
||||
import type { Item } from './items';
|
||||
|
||||
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 const behaviors: Behavior[] = [
|
||||
{
|
||||
name: 'heal',
|
||||
description: 'Heals the user by `behaviorValue`',
|
||||
itemType: 'consumable',
|
||||
action: async (item, user) => {
|
||||
// todo
|
||||
return false;
|
||||
}
|
||||
}
|
||||
];
|
|
@ -0,0 +1,65 @@
|
|||
import { pickRandom } from '../util';
|
||||
import { DefaultItems, Item, Items, formatItems, getDefaultItem, getItemQuantity } from './items';
|
||||
|
||||
export interface CraftingStation {
|
||||
key: string,
|
||||
name: string,
|
||||
description: string,
|
||||
emoji: string,
|
||||
requires?: Item,
|
||||
// in seconds
|
||||
cooldown?: number,
|
||||
formatRecipe?: (inputs: Items[], requirements: Items[], outputs: Items[], disableBold?: boolean) => string,
|
||||
manipulateResults?: (outputs: Items[]) => Items[]
|
||||
}
|
||||
|
||||
export function getStation(key: string) {
|
||||
return craftingStations.find(station => station.key === key);
|
||||
}
|
||||
|
||||
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, disableBold = false) => `${outputs.map(i => formatItems(i.item, i.quantity, disableBold) + '?').join(' ')}`,
|
||||
manipulateResults: (outputs) => {
|
||||
const totalItems = outputs.reduce((a, b) => a + b.quantity, 0);
|
||||
// grab from 1/3 to the entire pool, ensure it never goes below 1
|
||||
const rolledItems = Math.max(Math.round(totalItems/3 + Math.random() * totalItems*2/3), 1);
|
||||
const res: Items[] = [];
|
||||
for (let i = 0; i < rolledItems; i++) {
|
||||
const rolled = pickRandom(outputs);
|
||||
const r = res.find(r => r.item.id === rolled.item.id);
|
||||
if (r) {
|
||||
r.quantity = r.quantity + 1;
|
||||
} else {
|
||||
res.push({ item: rolled.item, quantity: 1 });
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
},
|
||||
{
|
||||
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)
|
||||
}
|
||||
];
|
||||
|
||||
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;
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
import { Behavior, CraftingStation, DefaultItem, Recipe, formatItems, getDefaultItem } from './items';
|
||||
|
||||
export 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, user) => {
|
||||
// todo
|
||||
return false;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
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)
|
||||
}
|
||||
];
|
||||
|
||||
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 }
|
||||
]
|
||||
}
|
||||
];
|
|
@ -1,54 +1,79 @@
|
|||
import { AutocompleteInteraction, User } from 'discord.js';
|
||||
import { AutocompleteInteraction } from 'discord.js';
|
||||
import { CustomItem, ItemInventory, db } from '../db';
|
||||
import { DefaultItems, defaultItems } 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 enum DefaultItems {
|
||||
COIN = 1,
|
||||
WORKBENCH = 2,
|
||||
PEBBLE = 3,
|
||||
TWIG = 4,
|
||||
APPLE = 5,
|
||||
BERRIES = 6,
|
||||
}
|
||||
|
||||
export interface Recipe {
|
||||
station: string,
|
||||
inputs: Items[],
|
||||
requirements: Items[],
|
||||
outputs: Items[]
|
||||
}
|
||||
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
|
||||
},
|
||||
{
|
||||
id: -6,
|
||||
name: 'Berries',
|
||||
description: 'A little treat for the road!',
|
||||
emoji: '🍓',
|
||||
type: 'consumable',
|
||||
maxStack: 16,
|
||||
untradable: false
|
||||
}
|
||||
];
|
||||
|
||||
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 {
|
||||
|
@ -63,7 +88,13 @@ export async function getItem(id: number): Promise<Item | undefined> {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getItemQuantity(user: string, itemID: number) {
|
||||
export async function getCustomItem(id: number) {
|
||||
return await db<CustomItem>('customItems')
|
||||
.where('id', id)
|
||||
.first();
|
||||
}
|
||||
|
||||
export async function getItemQuantity(user: string, itemID: number): Promise<ItemInventory> {
|
||||
return (await db<ItemInventory>('itemInventories')
|
||||
.where('item', itemID)
|
||||
.where('user', user)
|
||||
|
@ -108,15 +139,16 @@ 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 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) {
|
||||
return `${quantity}x ${formatItem(item)}`;
|
||||
export function formatItems(item: Item | undefined, quantity: number, disableBold = false) {
|
||||
return `${quantity}x ${formatItem(item, disableBold)}`;
|
||||
}
|
||||
export function formatItemsArray(items: Items[]) {
|
||||
return items.map(i => formatItems(i.item, i.quantity)).join(' ');
|
||||
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(' ');
|
||||
}
|
||||
|
||||
export async function itemAutocomplete(interaction: AutocompleteInteraction) {
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
import { getStation } from './craftingStations';
|
||||
import { DefaultItems, Items, formatItemsArray, getDefaultItem } from './items';
|
||||
|
||||
export interface DefaultRecipe {
|
||||
id: number,
|
||||
station: string,
|
||||
inputs: Items[],
|
||||
requirements: Items[],
|
||||
outputs: Items[]
|
||||
}
|
||||
export type Recipe = DefaultRecipe
|
||||
|
||||
export const defaultRecipes: DefaultRecipe[] = [
|
||||
{
|
||||
id: -1,
|
||||
station: 'forage',
|
||||
inputs: [],
|
||||
requirements: [],
|
||||
outputs: [
|
||||
{ item: getDefaultItem(DefaultItems.PEBBLE), quantity: 4 },
|
||||
{ item: getDefaultItem(DefaultItems.TWIG), quantity: 3 },
|
||||
{ item: getDefaultItem(DefaultItems.BERRIES), quantity: 2 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: -2,
|
||||
station: 'hand',
|
||||
inputs: [
|
||||
{ item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 },
|
||||
{ item: getDefaultItem(DefaultItems.TWIG), quantity: 2 },
|
||||
],
|
||||
requirements: [],
|
||||
outputs: [
|
||||
{ item: getDefaultItem(DefaultItems.WORKBENCH), quantity: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: -3,
|
||||
station: 'forage',
|
||||
inputs: [],
|
||||
requirements: [],
|
||||
outputs: [
|
||||
{ item: getDefaultItem(DefaultItems.PEBBLE), quantity: 3 },
|
||||
{ item: getDefaultItem(DefaultItems.TWIG), quantity: 4 },
|
||||
{ item: getDefaultItem(DefaultItems.APPLE), quantity: 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: -4,
|
||||
station: 'forage',
|
||||
inputs: [],
|
||||
requirements: [],
|
||||
outputs: [
|
||||
{ item: getDefaultItem(DefaultItems.TWIG), quantity: 1 },
|
||||
{ item: getDefaultItem(DefaultItems.COIN), quantity: 1 },
|
||||
{ item: getDefaultItem(DefaultItems.APPLE), quantity: 4 },
|
||||
{ item: getDefaultItem(DefaultItems.BERRIES), quantity: 6 },
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const defaultFormatRecipe = (inputs: Items[], requirements: Items[], outputs: Items[], disableBold = false) =>
|
||||
`${formatItemsArray(inputs, disableBold)}${requirements.length === 0 ? '' : ` w/ ${formatItemsArray(requirements, disableBold)}`} => ${formatItemsArray(outputs, disableBold)}`;
|
||||
|
||||
export function formatRecipe(recipe: DefaultRecipe, disableBold = false) {
|
||||
const station = getStation(recipe.station);
|
||||
return (station?.formatRecipe || defaultFormatRecipe)(recipe.inputs, recipe.requirements, recipe.outputs, disableBold);
|
||||
}
|
|
@ -23,4 +23,8 @@ export async function writeTmpFile(data: string | Buffer, filename?: string, ext
|
|||
const path = join(tmpdir(), file);
|
||||
await fsp.writeFile(path, data);
|
||||
return path;
|
||||
}
|
||||
|
||||
export function pickRandom<T>(list: T[]): T {
|
||||
return list[Math.floor(Math.random() * list.length)];
|
||||
}
|
Loading…
Reference in New Issue