feat: Mejorar el flujo de equipamiento de items, añadiendo validaciones y opciones interactivas para seleccionar slots y items del inventario.

This commit is contained in:
Shni
2025-10-14 15:27:52 -05:00
parent 7a080f9b71
commit 8f5beee709
2 changed files with 142 additions and 31 deletions

View File

@@ -70,8 +70,28 @@ export const command: CommandMessage = {
return;
}
// Build select options (max 25)
const options = inventory
// Determine which slots the user actually has items for (based on item.tags)
const slotsSet = new Set<string>();
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,
@@ -79,38 +99,53 @@ export const command: CommandMessage = {
description: inv.item.key,
}));
const slotSelect = {
type: ComponentType.StringSelect,
custom_id: "equip_slot_select",
placeholder: "Selecciona el slot (weapon / armor / cape)",
min_values: 1,
max_values: 1,
options: [
{
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",
min_values: 1,
max_values: 1,
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(

View File

@@ -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 <toolKey>` con una herramienta válida."
);
return;
}
// Fallback genérico
await message.reply(`❌ No se pudo pelear: ${e?.message ?? e}`);
}
},