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:
@@ -70,47 +70,82 @@ export const command: CommandMessage = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build select options (max 25)
|
// Determine which slots the user actually has items for (based on item.tags)
|
||||||
const options = inventory
|
const slotsSet = new Set<string>();
|
||||||
.slice(0, 25)
|
for (const inv of inventory) {
|
||||||
.map((inv) => ({
|
const tags = Array.isArray(inv.item.tags) ? inv.item.tags : [];
|
||||||
label: inv.item.name || inv.item.key,
|
if (tags.includes("weapon")) slotsSet.add("weapon");
|
||||||
value: inv.item.id,
|
if (tags.includes("armor")) slotsSet.add("armor");
|
||||||
description: inv.item.key,
|
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 = {
|
const slotSelect = {
|
||||||
type: ComponentType.StringSelect,
|
type: ComponentType.StringSelect,
|
||||||
custom_id: "equip_slot_select",
|
custom_id: "equip_slot_select",
|
||||||
placeholder: "Selecciona el slot (weapon / armor / cape)",
|
placeholder: "Selecciona el slot",
|
||||||
min_values: 1,
|
min_values: 1,
|
||||||
max_values: 1,
|
max_values: 1,
|
||||||
options: [
|
options: slotOptions,
|
||||||
{
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} as any;
|
} 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 = {
|
const itemSelect = {
|
||||||
type: ComponentType.StringSelect,
|
type: ComponentType.StringSelect,
|
||||||
custom_id: "equip_item_select",
|
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,
|
min_values: 1,
|
||||||
max_values: 1,
|
max_values: 1,
|
||||||
options,
|
options: initialItemOptions,
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
const channel = message.channel as TextBasedChannel & { send: Function };
|
const channel = message.channel as TextBasedChannel & { send: Function };
|
||||||
@@ -130,7 +165,7 @@ export const command: CommandMessage = {
|
|||||||
i.user.id === message.author.id,
|
i.user.id === message.author.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
let selectedSlot: string | null = null;
|
let selectedSlot: string | null = initialSelectedSlot;
|
||||||
let selectedItemId: string | null = null;
|
let selectedItemId: string | null = null;
|
||||||
|
|
||||||
collector.on("collect", async (i: MessageComponentInteraction) => {
|
collector.on("collect", async (i: MessageComponentInteraction) => {
|
||||||
@@ -138,11 +173,25 @@ export const command: CommandMessage = {
|
|||||||
await i.deferUpdate();
|
await i.deferUpdate();
|
||||||
if (i.customId === "equip_slot_select") {
|
if (i.customId === "equip_slot_select") {
|
||||||
selectedSlot = (i as StringSelectMenuInteraction).values[0];
|
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 {
|
try {
|
||||||
await prompt.edit({
|
await prompt.edit({
|
||||||
content: `Slot seleccionado: **${selectedSlot}**`,
|
content: `Slot seleccionado: **${selectedSlot}**`,
|
||||||
components: prompt.components,
|
components: [
|
||||||
|
{ type: ComponentType.ActionRow, components: [newSlotSelect] },
|
||||||
|
{ type: ComponentType.ActionRow, components: [newItemSelect] },
|
||||||
|
],
|
||||||
});
|
});
|
||||||
} catch {}
|
} catch {}
|
||||||
return;
|
return;
|
||||||
@@ -161,6 +210,24 @@ export const command: CommandMessage = {
|
|||||||
return;
|
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
|
// perform equip
|
||||||
try {
|
try {
|
||||||
await setEquipmentSlot(
|
await setEquipmentSlot(
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ export const command: CommandMessage = {
|
|||||||
providedTool ?? (await findBestToolKey(userId, guildId, "sword"));
|
providedTool ?? (await findBestToolKey(userId, guildId, "sword"));
|
||||||
|
|
||||||
try {
|
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, {
|
const result = await runMinigame(userId, guildId, area.key, level, {
|
||||||
toolKey: toolKey ?? undefined,
|
toolKey: toolKey ?? undefined,
|
||||||
});
|
});
|
||||||
@@ -206,6 +210,46 @@ export const command: CommandMessage = {
|
|||||||
const display = buildDisplay(FIGHT_ACCENT, blocks);
|
const display = buildDisplay(FIGHT_ACCENT, blocks);
|
||||||
await sendDisplayReply(message, display);
|
await sendDisplayReply(message, display);
|
||||||
} catch (e: any) {
|
} 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}`);
|
await message.reply(`❌ No se pudo pelear: ${e?.message ?? e}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user