feat(economy): add commands for opening chests, consuming items, purchasing from shops, crafting, enchanting, equipping items, smelting, and claiming smelting jobs
This commit is contained in:
39
src/commands/messages/game/abrir.ts
Normal file
39
src/commands/messages/game/abrir.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type { CommandMessage } from '../../../core/types/commands';
|
||||
import type Amayo from '../../../core/client';
|
||||
import { openChestByKey } from '../../../game/economy/service';
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'abrir',
|
||||
type: 'message',
|
||||
aliases: ['open'],
|
||||
cooldown: 3,
|
||||
description: 'Abre un cofre (item) por key y recibe sus recompensas/roles.',
|
||||
usage: 'abrir <itemKey>',
|
||||
run: async (message, args, _client: Amayo) => {
|
||||
const itemKey = args[0]?.trim();
|
||||
if (!itemKey) { await message.reply('Uso: `!abrir <itemKey>`'); return; }
|
||||
try {
|
||||
const res = await openChestByKey(message.author.id, message.guild!.id, itemKey);
|
||||
const coins = res.coinsDelta ? `🪙 +${res.coinsDelta}` : '';
|
||||
const items = res.itemsToAdd.length ? res.itemsToAdd.map(i => `${i.itemKey ?? i.itemId} x${i.qty}`).join(' · ') : '';
|
||||
let rolesGiven: string[] = [];
|
||||
let rolesFailed: string[] = [];
|
||||
if (res.rolesToGrant.length && message.member) {
|
||||
for (const r of res.rolesToGrant) {
|
||||
try { await message.member.roles.add(r); rolesGiven.push(r); } catch { rolesFailed.push(r); }
|
||||
}
|
||||
}
|
||||
const lines = [
|
||||
`🎁 Abriste ${itemKey}${res.consumed ? ' (consumido 1)' : ''}`,
|
||||
coins && `Monedas: ${coins}`,
|
||||
items && `Ítems: ${items}`,
|
||||
rolesGiven.length ? `Roles otorgados: ${rolesGiven.map(id=>`<@&${id}>`).join(', ')}` : '',
|
||||
rolesFailed.length ? `Roles fallidos: ${rolesFailed.join(', ')}` : '',
|
||||
].filter(Boolean);
|
||||
await message.reply(lines.join('\n'));
|
||||
} catch (e: any) {
|
||||
await message.reply(`❌ No se pudo abrir ${itemKey}: ${e?.message ?? e}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
23
src/commands/messages/game/comer.ts
Normal file
23
src/commands/messages/game/comer.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { CommandMessage } from '../../../core/types/commands';
|
||||
import type Amayo from '../../../core/client';
|
||||
import { useConsumableByKey } from '../../../game/consumables/service';
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'comer',
|
||||
type: 'message',
|
||||
aliases: ['usar-comida','usar'],
|
||||
cooldown: 3,
|
||||
description: 'Usa un ítem consumible (comida/poción) para curarte. Respeta cooldowns.',
|
||||
usage: 'comer <itemKey>',
|
||||
run: async (message, args, _client: Amayo) => {
|
||||
const itemKey = args[0]?.trim();
|
||||
if (!itemKey) { await message.reply('Uso: `!comer <itemKey>`'); return; }
|
||||
try {
|
||||
const res = await useConsumableByKey(message.author.id, message.guild!.id, itemKey);
|
||||
await message.reply(`🍽️ Usaste ${itemKey}. Curado: +${res.healed} HP.`);
|
||||
} catch (e: any) {
|
||||
await message.reply(`❌ No se pudo usar ${itemKey}: ${e?.message ?? e}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
24
src/commands/messages/game/comprar.ts
Normal file
24
src/commands/messages/game/comprar.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { CommandMessage } from '../../../core/types/commands';
|
||||
import type Amayo from '../../../core/client';
|
||||
import { buyFromOffer } from '../../../game/economy/service';
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'comprar',
|
||||
type: 'message',
|
||||
aliases: ['buy'],
|
||||
cooldown: 3,
|
||||
description: 'Compra una oferta de la tienda por su ID. Respeta límites y stock.',
|
||||
usage: 'comprar <offerId> [qty] (ej: comprar off_123 2)',
|
||||
run: async (message, args, _client: Amayo) => {
|
||||
const offerId = args[0]?.trim();
|
||||
const qty = Math.max(1, parseInt(args[1] || '1', 10) || 1);
|
||||
if (!offerId) { await message.reply('Uso: `!comprar <offerId> [qty]`'); return; }
|
||||
try {
|
||||
const res = await buyFromOffer(message.author.id, message.guild!.id, offerId, qty);
|
||||
await message.reply(`🛒 Comprado: ${res.item.key} x${res.qty}`);
|
||||
} catch (e: any) {
|
||||
await message.reply(`❌ No se pudo comprar: ${e?.message ?? e}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
35
src/commands/messages/game/craftear.ts
Normal file
35
src/commands/messages/game/craftear.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { CommandMessage } from '../../../core/types/commands';
|
||||
import type Amayo from '../../../core/client';
|
||||
import { craftByProductKey } from '../../../game/economy/service';
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'craftear',
|
||||
type: 'message',
|
||||
aliases: ['craft'],
|
||||
cooldown: 3,
|
||||
description: 'Craftea un ítem por su productKey, consumiendo ingredientes según la receta.',
|
||||
usage: 'craftear <productKey> [veces] (ej: craftear ingot.iron 3)',
|
||||
run: async (message, args, _client: Amayo) => {
|
||||
const productKey = args[0]?.trim();
|
||||
const times = Math.max(1, parseInt(args[1] || '1', 10) || 1);
|
||||
if (!productKey) { await message.reply('Uso: `!craftear <productKey> [veces]`'); return; }
|
||||
|
||||
let crafted = 0;
|
||||
let lastError: any = null;
|
||||
for (let i = 0; i < times; i++) {
|
||||
try {
|
||||
const res = await craftByProductKey(message.author.id, message.guild!.id, productKey);
|
||||
crafted += res.added;
|
||||
} catch (e: any) {
|
||||
lastError = e; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (crafted > 0) {
|
||||
await message.reply(`🛠️ Crafteado ${productKey} x${crafted * 1}.`);
|
||||
} else {
|
||||
await message.reply(`❌ No se pudo craftear: ${lastError?.message ?? 'revise ingredientes/receta'}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
24
src/commands/messages/game/encantar.ts
Normal file
24
src/commands/messages/game/encantar.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { CommandMessage } from '../../../core/types/commands';
|
||||
import type Amayo from '../../../core/client';
|
||||
import { applyMutationToInventory } from '../../../game/mutations/service';
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'encantar',
|
||||
type: 'message',
|
||||
aliases: ['mutar','enchant'],
|
||||
cooldown: 3,
|
||||
description: 'Aplica una mutación/encantamiento a un ítem por su itemKey y mutationKey, respetando mutationPolicy.',
|
||||
usage: 'encantar <itemKey> <mutationKey>',
|
||||
run: async (message, args, _client: Amayo) => {
|
||||
const itemKey = args[0]?.trim();
|
||||
const mutationKey = args[1]?.trim();
|
||||
if (!itemKey || !mutationKey) { await message.reply('Uso: `!encantar <itemKey> <mutationKey>`'); return; }
|
||||
try {
|
||||
await applyMutationToInventory(message.author.id, message.guild!.id, itemKey, mutationKey);
|
||||
await message.reply(`✨ Aplicada mutación ${mutationKey} a ${itemKey}.`);
|
||||
} catch (e: any) {
|
||||
await message.reply(`❌ No se pudo encantar: ${e?.message ?? e}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
32
src/commands/messages/game/equipar.ts
Normal file
32
src/commands/messages/game/equipar.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { CommandMessage } from '../../../core/types/commands';
|
||||
import type Amayo from '../../../core/client';
|
||||
import { setEquipmentSlot } from '../../../game/combat/equipmentService';
|
||||
import { prisma } from '../../../core/database/prisma';
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'equipar',
|
||||
type: 'message',
|
||||
aliases: ['equip'],
|
||||
cooldown: 3,
|
||||
description: 'Equipa un item en un slot (weapon|armor|cape) por su key, si lo tienes en inventario.',
|
||||
usage: 'equipar <weapon|armor|cape> <itemKey>',
|
||||
run: async (message, args, _client: Amayo) => {
|
||||
const slot = (args[0]?.trim()?.toLowerCase() as 'weapon'|'armor'|'cape'|undefined);
|
||||
const itemKey = args[1]?.trim();
|
||||
if (!slot || !['weapon','armor','cape'].includes(slot) || !itemKey) {
|
||||
await message.reply('Uso: `!equipar <weapon|armor|cape> <itemKey>`');
|
||||
return;
|
||||
}
|
||||
const guildId = message.guild!.id;
|
||||
const userId = message.author.id;
|
||||
|
||||
const item = await prisma.economyItem.findFirst({ where: { key: itemKey, OR: [{ guildId }, { guildId: null }] }, orderBy: [{ guildId: 'desc' }] });
|
||||
if (!item) { await message.reply('❌ Item no encontrado.'); return; }
|
||||
const inv = await prisma.inventoryEntry.findUnique({ where: { userId_guildId_itemId: { userId, guildId, itemId: item.id } } });
|
||||
if (!inv || inv.quantity <= 0) { await message.reply('❌ No tienes este item en tu inventario.'); return; }
|
||||
|
||||
await setEquipmentSlot(userId, guildId, slot, item.id);
|
||||
await message.reply(`🧰 Equipado en ${slot}: ${item.key}`);
|
||||
}
|
||||
};
|
||||
|
||||
40
src/commands/messages/game/fundir.ts
Normal file
40
src/commands/messages/game/fundir.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { CommandMessage } from '../../../core/types/commands';
|
||||
import type Amayo from '../../../core/client';
|
||||
import { createSmeltJob } from '../../../game/smelting/service';
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'fundir',
|
||||
type: 'message',
|
||||
aliases: ['smelt'],
|
||||
cooldown: 5,
|
||||
description: 'Crea un job de fundición: descuenta insumos y estará listo tras el tiempo indicado.',
|
||||
usage: 'fundir <outputKey> <outputQty> <segundos> <inputKey1>:<qty> [inputKey2>:<qty> ...]'
|
||||
+ '\nEj: fundir ingot.iron 1 60 ore.iron:3',
|
||||
run: async (message, args, _client: Amayo) => {
|
||||
const [outputKey, qtyStr, secsStr, ...rest] = args;
|
||||
if (!outputKey || !qtyStr || !secsStr || rest.length === 0) {
|
||||
await message.reply('Uso: `!fundir <outputKey> <outputQty> <segundos> <inputKey1>:<qty> [...]`');
|
||||
return;
|
||||
}
|
||||
const outputQty = parseInt(qtyStr, 10);
|
||||
const seconds = parseInt(secsStr, 10);
|
||||
if (!Number.isFinite(outputQty) || outputQty <= 0 || !Number.isFinite(seconds) || seconds <= 0) {
|
||||
await message.reply('❌ Cantidades/segundos inválidos.');
|
||||
return;
|
||||
}
|
||||
const inputs = rest.map((tok) => {
|
||||
const [k, q] = tok.split(':');
|
||||
return { itemKey: (k || '').trim(), qty: Math.max(1, parseInt((q || '1'), 10) || 1) };
|
||||
}).filter(x => x.itemKey);
|
||||
if (!inputs.length) { await message.reply('❌ Debes especificar al menos un insumo como key:qty'); return; }
|
||||
|
||||
try {
|
||||
const res = await createSmeltJob(message.author.id, message.guild!.id, inputs, outputKey, outputQty, seconds);
|
||||
const when = new Date(res.readyAt).toLocaleTimeString('es-ES', { hour12: false });
|
||||
await message.reply(`🔥 Fundición creada (job: ${res.jobId}). Estará lista a las ${when}.`);
|
||||
} catch (e: any) {
|
||||
await message.reply(`❌ No se pudo crear la fundición: ${e?.message ?? e}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
28
src/commands/messages/game/fundirReclamar.ts
Normal file
28
src/commands/messages/game/fundirReclamar.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { CommandMessage } from '../../../core/types/commands';
|
||||
import type Amayo from '../../../core/client';
|
||||
import { claimSmeltJob, claimNextReadyJob } from '../../../game/smelting/service';
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'fundir-reclamar',
|
||||
type: 'message',
|
||||
aliases: ['smelt-claim','reclamar-fundicion'],
|
||||
cooldown: 3,
|
||||
description: 'Reclama una fundición lista por jobId o la más antigua lista si no especificas id.',
|
||||
usage: 'fundir-reclamar [jobId]'
|
||||
+ '\nSin argumentos intenta reclamar la más antigua lista.',
|
||||
run: async (message, args, _client: Amayo) => {
|
||||
const jobId = args[0]?.trim();
|
||||
try {
|
||||
if (jobId) {
|
||||
await claimSmeltJob(message.author.id, message.guild!.id, jobId);
|
||||
await message.reply(`✅ Fundición reclamada (job ${jobId}).`);
|
||||
} else {
|
||||
const res = await claimNextReadyJob(message.author.id, message.guild!.id);
|
||||
await message.reply(`✅ Fundición reclamada (job ${res.jobId}).`);
|
||||
}
|
||||
} catch (e: any) {
|
||||
await message.reply(`❌ No se pudo reclamar: ${e?.message ?? e}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -41,7 +41,6 @@ export const command: CommandMessage = {
|
||||
|
||||
const editorMsg = await message.channel.send({
|
||||
content: `👾 Editor de Mob (editar): \`${key}\``,
|
||||
flags: MessageFlags.IsComponentsV2,
|
||||
components: [ { type: 1, components: [
|
||||
{ type: 2, style: ButtonStyle.Primary, label: 'Base', custom_id: 'mb_base' },
|
||||
{ type: 2, style: ButtonStyle.Secondary, label: 'Stats (JSON)', custom_id: 'mb_stats' },
|
||||
@@ -98,4 +97,3 @@ async function showJsonModal(i: ButtonInteraction, state: MobEditorState, field:
|
||||
else { state[field] = field==='stats' ? { attack: 5 } : {}; await sub.reply({ content: 'ℹ️ Limpio.', flags: MessageFlags.Ephemeral }); }
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
||||
17
src/commands/messages/game/monedas.ts
Normal file
17
src/commands/messages/game/monedas.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { CommandMessage } from '../../../core/types/commands';
|
||||
import type Amayo from '../../../core/client';
|
||||
import { getOrCreateWallet } from '../../../game/economy/service';
|
||||
|
||||
export const command: CommandMessage = {
|
||||
name: 'monedas',
|
||||
type: 'message',
|
||||
aliases: ['coins','saldo'],
|
||||
cooldown: 2,
|
||||
description: 'Muestra tu saldo de monedas en este servidor.',
|
||||
usage: 'monedas',
|
||||
run: async (message, _args, _client: Amayo) => {
|
||||
const wallet = await getOrCreateWallet(message.author.id, message.guild!.id);
|
||||
await message.reply(`💰 Monedas: ${wallet.coins}`);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user