using and eating items, behaviors implemented
This commit is contained in:
parent
1a8d49ae9f
commit
4e35d47854
|
@ -1,5 +1,5 @@
|
|||
import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js';
|
||||
import { weaponAutocomplete, getItem, getItemQuantity, formatItems, formatItem } from '../lib/rpg/items';
|
||||
import { getItem, getItemQuantity, formatItems, formatItem, weaponInventoryAutocomplete } from '../lib/rpg/items';
|
||||
import { Command } from '../types/index';
|
||||
import { initHealth, dealDamage, BLOOD_ITEM, BLOOD_ID, resetInvincible, INVINCIBLE_TIMER, getInvincibleMs } from '../lib/rpg/pvp';
|
||||
|
||||
|
@ -48,5 +48,5 @@ export default {
|
|||
await interaction.followUp(`You hit ${user} with ${formatItem(weapon)} for ${BLOOD_ITEM.emoji} **${dmg}** damage! They are now at ${formatItems(BLOOD_ITEM, newHealth.quantity)}.\nYou can attack them again <t:${Math.floor((Date.now() + INVINCIBLE_TIMER) / 1000)}:R> (or if they perform an action first).`);
|
||||
},
|
||||
|
||||
autocomplete: weaponAutocomplete,
|
||||
autocomplete: weaponInventoryAutocomplete,
|
||||
} satisfies Command;
|
|
@ -0,0 +1,60 @@
|
|||
import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js';
|
||||
import { Command } from '../types/index';
|
||||
import { initHealth, resetInvincible } from '../lib/rpg/pvp';
|
||||
import { consumableInventoryAutocomplete, formatItem, formatItems, getItem, getItemQuantity, giveItem } from '../lib/rpg/items';
|
||||
import { getBehavior, getBehaviors } from '../lib/rpg/behaviors';
|
||||
import { Right } from '../lib/util';
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('eat')
|
||||
.setDescription('Eat an item from your inventory')
|
||||
.addStringOption(option =>
|
||||
option
|
||||
.setName('item')
|
||||
.setAutocomplete(true)
|
||||
.setDescription('The item to eat')
|
||||
.setRequired(true)
|
||||
)
|
||||
.setDMPermission(false),
|
||||
|
||||
execute: async (interaction: CommandInteraction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const member = interaction.member! as GuildMember;
|
||||
|
||||
await initHealth(member.id);
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const itemID = parseInt(interaction.options.getString('item', true));
|
||||
const item = await getItem(itemID);
|
||||
if (!item) return await interaction.followUp('Item does not exist!');
|
||||
|
||||
const itemInv = await getItemQuantity(member.id, item.id);
|
||||
if (itemInv.quantity <= 0) return await interaction.followUp(`You do not have ${formatItem(item)}!`);
|
||||
|
||||
const behaviors = await getBehaviors(item);
|
||||
|
||||
const messages = [];
|
||||
for (const itemBehavior of behaviors) {
|
||||
const behavior = getBehavior(itemBehavior.behavior);
|
||||
if (!behavior) continue;
|
||||
if (!behavior.onUse) continue;
|
||||
const res = await behavior.onUse(itemBehavior.value, item, member.id);
|
||||
if (res instanceof Right) {
|
||||
await interaction.followUp(`You tried to eat ${formatItems(item, 1)}... but failed!\n${res.getValue()}`);
|
||||
return;
|
||||
} else {
|
||||
messages.push(res.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
await resetInvincible(member.id);
|
||||
const newInv = await giveItem(member.id, item, -1);
|
||||
|
||||
return await interaction.followUp(`You ate ${formatItems(item, 1)}!\n${messages.map(m => `_${m}_`).join('\n')}\nYou now have ${formatItems(item, newInv.quantity)}`);
|
||||
},
|
||||
|
||||
autocomplete: consumableInventoryAutocomplete,
|
||||
} satisfies Command;
|
|
@ -0,0 +1,58 @@
|
|||
import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js';
|
||||
import { Command } from '../types/index';
|
||||
import { initHealth, resetInvincible } from '../lib/rpg/pvp';
|
||||
import { formatItem, getItem, getItemQuantity, plainInventoryAutocomplete } from '../lib/rpg/items';
|
||||
import { getBehavior, getBehaviors } from '../lib/rpg/behaviors';
|
||||
import { Right } from '../lib/util';
|
||||
|
||||
export default {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('use')
|
||||
.setDescription('Use an item from your inventory')
|
||||
.addStringOption(option =>
|
||||
option
|
||||
.setName('item')
|
||||
.setAutocomplete(true)
|
||||
.setDescription('The item to use')
|
||||
.setRequired(true)
|
||||
)
|
||||
.setDMPermission(false),
|
||||
|
||||
execute: async (interaction: CommandInteraction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const member = interaction.member! as GuildMember;
|
||||
|
||||
await initHealth(member.id);
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const itemID = parseInt(interaction.options.getString('item', true));
|
||||
const item = await getItem(itemID);
|
||||
if (!item) return await interaction.followUp('Item does not exist!');
|
||||
|
||||
const itemInv = await getItemQuantity(member.id, item.id);
|
||||
if (itemInv.quantity <= 0) return await interaction.followUp(`You do not have ${formatItem(item)}!`);
|
||||
|
||||
const behaviors = await getBehaviors(item);
|
||||
|
||||
const messages = [];
|
||||
for (const itemBehavior of behaviors) {
|
||||
const behavior = getBehavior(itemBehavior.behavior);
|
||||
if (!behavior) continue;
|
||||
if (!behavior.onUse) continue;
|
||||
const res = await behavior.onUse(itemBehavior.value, item, member.id);
|
||||
if (res instanceof Right) {
|
||||
await interaction.followUp(`You tried to use ${formatItem(item)}... but failed!\n${res.getValue()}`);
|
||||
return;
|
||||
} else {
|
||||
messages.push(res.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
await resetInvincible(member.id);
|
||||
return await interaction.followUp(`You used ${formatItem(item)}!\n${messages.map(m => `_${m}_`).join('\n')}`);
|
||||
},
|
||||
|
||||
autocomplete: plainInventoryAutocomplete,
|
||||
} satisfies Command;
|
|
@ -1,5 +1,5 @@
|
|||
import { giveItem, type Item, isDefaultItem } from './items';
|
||||
import { Either, Left } from '../util';
|
||||
import { giveItem, type Item, isDefaultItem, formatItems } from './items';
|
||||
import { Either, Left, Right } from '../util';
|
||||
import { ItemBehavior, db } from '../db';
|
||||
import { BLOOD_ITEM, dealDamage } from './pvp';
|
||||
|
||||
|
@ -12,11 +12,11 @@ export interface Behavior {
|
|||
// triggers upon use
|
||||
// for 'weapons', this is on attack
|
||||
// for 'consumable' and `plain`, this is on use
|
||||
// returns Left upon success, the reason for failure otherwise (Right)
|
||||
onUse?: (value: number, item: Item, user: string) => Promise<Either<null, string>>,
|
||||
// returns Left upon success with an optional message, the reason for failure otherwise (Right)
|
||||
onUse?: (value: number | undefined, item: Item, user: string) => Promise<Either<string | null, string>>,
|
||||
// triggers upon `weapons` attack
|
||||
// returns the new damage value upon success (Left), the reason for failure otherwise (Right)
|
||||
onAttack?: (value: number, item: Item, user: string, target: string, damage: number) => Promise<Either<number, string>>,
|
||||
onAttack?: (value: number | undefined, item: Item, user: string, target: string, damage: number) => Promise<Either<number, string>>,
|
||||
}
|
||||
|
||||
const defaultFormat = (behavior: Behavior, value?: number) => `${behavior.name}${value ? ' ' + value.toString() : ''}`;
|
||||
|
@ -27,8 +27,9 @@ export const behaviors: Behavior[] = [
|
|||
description: 'Heals the user by `value`',
|
||||
itemType: 'consumable',
|
||||
async onUse(value, item, user) {
|
||||
if (!value) return new Right('A value is required for this behavior');
|
||||
await dealDamage(user, -Math.floor(value));
|
||||
return new Left(null);
|
||||
return new Left(`You were healed by ${formatItems(BLOOD_ITEM, value)}!`);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -36,8 +37,9 @@ export const behaviors: Behavior[] = [
|
|||
description: 'Damages the user by `value',
|
||||
itemType: 'consumable',
|
||||
async onUse(value, item, user) {
|
||||
if (!value) return new Right('A value is required for this behavior');
|
||||
await dealDamage(user, Math.floor(value));
|
||||
return new Left(null);
|
||||
return new Left(`You were damaged by ${formatItems(BLOOD_ITEM, value)}!`);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -46,6 +48,7 @@ export const behaviors: Behavior[] = [
|
|||
itemType: 'weapon',
|
||||
format: (value) => `random +${value}`,
|
||||
async onAttack(value, item, user, target, damage) {
|
||||
if (!value) return new Right('A value is required for this behavior');
|
||||
return new Left(damage + Math.round(Math.random() * value));
|
||||
},
|
||||
},
|
||||
|
@ -55,6 +58,7 @@ export const behaviors: Behavior[] = [
|
|||
itemType: 'weapon',
|
||||
format: (value) => `random -${value}`,
|
||||
async onAttack(value, item, user, target, damage) {
|
||||
if (!value) return new Right('A value is required for this behavior');
|
||||
return new Left(damage - Math.round(Math.random() * value));
|
||||
},
|
||||
},
|
||||
|
@ -64,6 +68,7 @@ export const behaviors: Behavior[] = [
|
|||
itemType: 'weapon',
|
||||
format: (value) => `lifesteal: ${value}x`,
|
||||
async onAttack(value, item, user, target, damage) {
|
||||
if (!value) return new Right('A value is required for this behavior');
|
||||
giveItem(user, BLOOD_ITEM, Math.floor(damage * value));
|
||||
return new Left(damage);
|
||||
},
|
||||
|
|
|
@ -84,7 +84,8 @@ export const defaultItems: DefaultItem[] = [
|
|||
emoji: '🍎',
|
||||
type: 'consumable',
|
||||
maxStack: 16,
|
||||
untradable: false
|
||||
untradable: false,
|
||||
behaviors: [{ behavior: 'heal', value: 10 }],
|
||||
},
|
||||
{
|
||||
id: -6,
|
||||
|
@ -93,7 +94,8 @@ export const defaultItems: DefaultItem[] = [
|
|||
emoji: '🍓',
|
||||
type: 'consumable',
|
||||
maxStack: 16,
|
||||
untradable: false
|
||||
untradable: false,
|
||||
behaviors: [{ behavior: 'heal', value: 4 }],
|
||||
},
|
||||
{
|
||||
id: -7,
|
||||
|
@ -129,7 +131,8 @@ export const defaultItems: DefaultItem[] = [
|
|||
emoji: '🪱',
|
||||
type: 'consumable',
|
||||
maxStack: 128,
|
||||
untradable: false
|
||||
untradable: false,
|
||||
behaviors: [{ behavior: 'heal', value: 1 }],
|
||||
},
|
||||
{
|
||||
id: -11,
|
||||
|
@ -282,7 +285,8 @@ export const defaultItems: DefaultItem[] = [
|
|||
emoji: '🍱',
|
||||
type: 'consumable',
|
||||
maxStack: 16,
|
||||
untradable: false
|
||||
untradable: false,
|
||||
behaviors: [{ behavior: 'heal', value: 35 }],
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -379,7 +383,7 @@ export function formatItemsArray(items: Items[], disableBold = false) {
|
|||
return items.map(i => formatItems(i.item, i.quantity, disableBold)).join(' ');
|
||||
}
|
||||
|
||||
function createItemAutocomplete(onlyCustom: boolean, filterType: 'plain' | 'weapon' | 'consumable' | null): Autocomplete {
|
||||
function createItemAutocomplete(onlyCustom: boolean, filterType: 'plain' | 'weapon' | 'consumable' | null, inventory: boolean = false): Autocomplete {
|
||||
return async (interaction: AutocompleteInteraction) => {
|
||||
const focused = interaction.options.getFocused();
|
||||
|
||||
|
@ -391,11 +395,16 @@ function createItemAutocomplete(onlyCustom: boolean, filterType: 'plain' | 'weap
|
|||
.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) {
|
||||
|
@ -412,4 +421,7 @@ 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 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);
|
Loading…
Reference in New Issue