From 8f5beee70983399f23aa3cc712883346606a4fa5 Mon Sep 17 00:00:00 2001 From: Shni Date: Tue, 14 Oct 2025 15:27:52 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20Mejorar=20el=20flujo=20de=20equipamient?= =?UTF-8?q?o=20de=20items,=20a=C3=B1adiendo=20validaciones=20y=20opciones?= =?UTF-8?q?=20interactivas=20para=20seleccionar=20slots=20y=20items=20del?= =?UTF-8?q?=20inventario.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/commands/messages/game/equipar.ts | 129 +++++++++++++++++++------- src/commands/messages/game/pelear.ts | 44 +++++++++ 2 files changed, 142 insertions(+), 31 deletions(-) diff --git a/src/commands/messages/game/equipar.ts b/src/commands/messages/game/equipar.ts index 2faa390..4a5950c 100644 --- a/src/commands/messages/game/equipar.ts +++ b/src/commands/messages/game/equipar.ts @@ -70,47 +70,82 @@ export const command: CommandMessage = { return; } - // Build select options (max 25) - const options = inventory - .slice(0, 25) - .map((inv) => ({ - label: inv.item.name || inv.item.key, - value: inv.item.id, - description: inv.item.key, - })); + // Determine which slots the user actually has items for (based on item.tags) + const slotsSet = new Set(); + for (const inv of inventory) { + const tags = Array.isArray(inv.item.tags) ? inv.item.tags : []; + if (tags.includes("weapon")) slotsSet.add("weapon"); + if (tags.includes("armor")) slotsSet.add("armor"); + if (tags.includes("cape")) slotsSet.add("cape"); + } + const availableSlots = Array.from(slotsSet); + if (availableSlots.length === 0) { + await message.reply( + "❌ No tienes items equipables en el inventario (weapon/armor/cape)." + ); + return; + } + + const buildItemOptionsForSlot = (slot: string) => + inventory + .filter((inv) => { + const tags = Array.isArray(inv.item.tags) ? inv.item.tags : []; + return tags.includes(slot); + }) + .slice(0, 25) + .map((inv) => ({ + label: inv.item.name || inv.item.key, + value: inv.item.id, + description: inv.item.key, + })); + + const slotOptions = availableSlots.map((s) => { + if (s === "weapon") + return { + label: "Weapon (arma)", + value: "weapon", + description: "Equipar como arma", + }; + if (s === "armor") + return { + label: "Armor (armadura)", + value: "armor", + description: "Equipar como armadura", + }; + return { + label: "Cape (capa)", + value: "cape", + description: "Equipar como capa", + }; + }); const slotSelect = { type: ComponentType.StringSelect, custom_id: "equip_slot_select", - placeholder: "Selecciona el slot (weapon / armor / cape)", + placeholder: "Selecciona el slot", min_values: 1, max_values: 1, - options: [ - { - label: "Weapon (arma)", - value: "weapon", - description: "Equipar como arma", - }, - { - label: "Armor (armadura)", - value: "armor", - description: "Equipar como armadura", - }, - { - label: "Cape (capa)", - value: "cape", - description: "Equipar como capa", - }, - ], + options: slotOptions, } as any; + // If only one slot available, preselect it and build item options for it. + let initialSelectedSlot: string | null = null; + if (availableSlots.length === 1) initialSelectedSlot = availableSlots[0]; + + const initialItemOptions = initialSelectedSlot + ? buildItemOptionsForSlot(initialSelectedSlot) + : // default to first slot's items so the select is populated + buildItemOptionsForSlot(availableSlots[0]); + const itemSelect = { type: ComponentType.StringSelect, custom_id: "equip_item_select", - placeholder: "Selecciona el item a equipar", + placeholder: initialSelectedSlot + ? "Selecciona el item a equipar" + : "Selecciona primero el slot (o usa el slot disponible)", min_values: 1, max_values: 1, - options, + options: initialItemOptions, } as any; const channel = message.channel as TextBasedChannel & { send: Function }; @@ -130,7 +165,7 @@ export const command: CommandMessage = { i.user.id === message.author.id, }); - let selectedSlot: string | null = null; + let selectedSlot: string | null = initialSelectedSlot; let selectedItemId: string | null = null; collector.on("collect", async (i: MessageComponentInteraction) => { @@ -138,11 +173,25 @@ export const command: CommandMessage = { await i.deferUpdate(); if (i.customId === "equip_slot_select") { selectedSlot = (i as StringSelectMenuInteraction).values[0]; - // inform user visually by editing the prompt to show selection + // rebuild item select options for the chosen slot + const newItemOptions = buildItemOptionsForSlot(selectedSlot); + const newSlotSelect = + prompt.components[0]?.components?.[0] ?? slotSelect; + const newItemSelect = { + type: ComponentType.StringSelect, + custom_id: "equip_item_select", + placeholder: "Selecciona el item a equipar", + min_values: 1, + max_values: 1, + options: newItemOptions, + } as any; try { await prompt.edit({ content: `Slot seleccionado: **${selectedSlot}**`, - components: prompt.components, + components: [ + { type: ComponentType.ActionRow, components: [newSlotSelect] }, + { type: ComponentType.ActionRow, components: [newItemSelect] }, + ], }); } catch {} return; @@ -161,6 +210,24 @@ export const command: CommandMessage = { return; } + // validate that the selected item belongs to the chosen slot + const chosenItem = await prisma.economyItem.findUnique({ + where: { id: selectedItemId }, + }); + const chosenTags = Array.isArray(chosenItem?.tags) + ? chosenItem!.tags + : []; + if (!chosenTags.includes(selectedSlot)) { + try { + await prompt.edit({ + content: `❌ Ese ítem no puede equiparse en el slot **${selectedSlot}**.`, + components: [], + }); + } catch {} + collector.stop(); + return; + } + // perform equip try { await setEquipmentSlot( diff --git a/src/commands/messages/game/pelear.ts b/src/commands/messages/game/pelear.ts index 4d4aa15..00145aa 100644 --- a/src/commands/messages/game/pelear.ts +++ b/src/commands/messages/game/pelear.ts @@ -66,6 +66,10 @@ export const command: CommandMessage = { providedTool ?? (await findBestToolKey(userId, guildId, "sword")); try { + // Pre-check: si el nivel tiene requirements con herramienta obligatoria + // intentamos validar rápidamente si existe alguna herramienta equipada o en inventario + // usando validateRequirements indirectamente sería duplicar lógica; hacemos una ligera comprobación + // basada en `toolKey` detectado y si findBestToolKey falla, informaremos. const result = await runMinigame(userId, guildId, area.key, level, { toolKey: toolKey ?? undefined, }); @@ -206,6 +210,46 @@ export const command: CommandMessage = { const display = buildDisplay(FIGHT_ACCENT, blocks); await sendDisplayReply(message, display); } catch (e: any) { + const msg = (e?.message || String(e)).toLowerCase(); + // Mapear errores conocidos a mensajes más amigables + if ( + msg.includes("area no encontrada") || + msg.includes("nivel no encontrado") + ) { + await message.reply( + "⚠️ El área o nivel especificado no existe para este servidor." + ); + return; + } + if (msg.includes("cooldown activo")) { + await message.reply( + "⏳ Estás en cooldown para esta actividad. Intenta más tarde." + ); + return; + } + if ( + msg.includes("se requiere una herramienta adecuada") || + msg.includes("no tienes la herramienta") || + msg.includes("tipo de herramienta incorrecto") || + msg.includes("tier de herramienta insuficiente") + ) { + // Mensaje más específico: si no hay arma equipada y el área requiere saberlo, sugerir equipar o conseguir herramienta + await message.reply( + "⚠️ No tienes una herramienta válida para esta actividad. Equipa una herramienta adecuada (ej: espada) o especifica `toolKey`." + ); + return; + } + if ( + msg.includes("no puede infligir daño") || + msg.includes("autoDefeatNoWeapon") || + msg.includes("auto defeat") + ) { + await message.reply( + "⚠️ No tienes un arma equipada o válida para pelear. Equipa un arma para poder infligir daño o usa `pelear ` con una herramienta válida." + ); + return; + } + // Fallback genérico await message.reply(`❌ No se pudo pelear: ${e?.message ?? e}`); } },