From a2bf5550f9c4bc7ceedb2d78250e065b11632e40 Mon Sep 17 00:00:00 2001 From: shni Date: Thu, 9 Oct 2025 02:58:47 -0500 Subject: [PATCH] feat: agregar comando para mostrar la durabilidad de items no-apilables en el inventario --- README/FIX_TOOL_SELECTION_PRIORITY.md | 105 ++++++++++++++++++++++ src/commands/messages/admin/debugInv.ts | 6 ++ src/commands/messages/game/durabilidad.ts | 96 ++++++++++++++++++++ src/commands/messages/game/inventario.ts | 22 ++++- src/game/minigames/service.ts | 13 ++- 5 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 README/FIX_TOOL_SELECTION_PRIORITY.md create mode 100644 src/commands/messages/game/durabilidad.ts diff --git a/README/FIX_TOOL_SELECTION_PRIORITY.md b/README/FIX_TOOL_SELECTION_PRIORITY.md new file mode 100644 index 0000000..a9227a9 --- /dev/null +++ b/README/FIX_TOOL_SELECTION_PRIORITY.md @@ -0,0 +1,105 @@ +# 🔧 Fix Final: Tool Selection Priority + +**Problema Identificado:** `findBestToolKey` selecciona espada en lugar de pico para minar. + +**Causa Raíz Posible:** +1. La espada en DB podría tener `tool.type: "pickaxe"` (datos corruptos) +2. O ambos tienen tier similar y el algoritmo no diferenciaba herramientas primarias de armas + +**Solución Implementada:** + +### Cambio en `findBestToolKey` (línea 51-76) + +```typescript +// ANTES: Solo comparaba tier +if (!best || tier > best.tier) best = { key: e.item.key, tier }; + +// DESPUÉS: Prioriza items con key "tool.*" sobre "weapon.*" +const isPrimaryTool = e.item.key.startsWith('tool.'); +if (!best || tier > best.tier || (tier === best.tier && isPrimaryTool && !best.isPrimaryTool)) { + best = { key: e.item.key, tier, isPrimaryTool }; +} +``` + +**Lógica:** +- Si tier es mayor → selecciona independientemente del tipo +- Si tier es igual → prioriza `tool.*` (pico, caña) sobre `weapon.*` (espada) + +--- + +## 🔍 Diagnóstico Requerido + +**Ejecuta en Discord:** +``` +a!debug-inv +``` + +**Busca en la salida:** + +### ✅ Configuración Correcta: +``` +**Pico Normal** (`tool.pickaxe.basic`) +• Tool: type=pickaxe, tier=1 +• Breakable: enabled=true, max=100 + +**Espada Normal** (`weapon.sword.iron`) +• Tool: type=sword, tier=1 +• Breakable: enabled=true, max=150 +``` + +### ❌ Configuración Corrupta: +``` +**Espada Normal** (`weapon.sword.iron`) +• Tool: type=pickaxe, tier=1 <-- ⚠️ PROBLEMA: debería ser "sword" +``` + +--- + +## 🛠️ Solución si Datos Corruptos + +Si la espada tiene `tool.type: "pickaxe"`, re-ejecuta el seed: + +```bash +XATA_DB="..." npm run seed:minigames +``` + +O actualiza manualmente en Prisma Studio: +1. Abrir item `weapon.sword.iron` +2. Editar props.tool.type → cambiar a `"sword"` +3. Guardar + +--- + +## ✅ Validación Final + +Después de reiniciar el bot: + +``` +a!minar +``` + +**Resultado esperado:** +``` +Herramienta: ⛏️ Pico Normal (95/100) [🔧 Auto] +Arma (defensa): ⚔️ Espada Normal (149/150) [⚔️ Equipado] +``` + +**NO debe mostrar:** +``` +Herramienta: ⚔️ Espada Normal (x4) (-2 dur.) (provided) +``` + +--- + +## 📊 Resumen de Todos los Fixes + +| # | Problema | Causa | Solución | Estado | +|---|----------|-------|----------|--------| +| 1 | Items stackable | Datos antiguos | Migración SQL + script TS | ✅ Completo | +| 2 | Combate sin arma ganado | Condición ambigua | `hasWeapon` explícito | ✅ Completo | +| 3 | Espada usada para minar | Sin priorización de tool.* | Algoritmo de prioridad | ✅ Completo | +| 4 | Cantidad en lugar de durabilidad | Display formateado mal | Pendiente verificar en UI | + +--- + +**Siguiente Paso:** Reiniciar bot y ejecutar `a!debug-inv` para confirmar tool types correctos. diff --git a/src/commands/messages/admin/debugInv.ts b/src/commands/messages/admin/debugInv.ts index 0429d1c..c287c12 100644 --- a/src/commands/messages/admin/debugInv.ts +++ b/src/commands/messages/admin/debugInv.ts @@ -38,6 +38,12 @@ export const command: CommandMessage = { output += `• Quantity: ${entry.quantity}\n`; output += `• Instances: ${instances.length}\n`; + if (props?.tool) { + output += `• Tool: type=${props.tool.type}, tier=${ + props.tool.tier ?? 0 + }\n`; + } + if (props?.breakable) { output += `• Breakable: enabled=${ props.breakable.enabled !== false diff --git a/src/commands/messages/game/durabilidad.ts b/src/commands/messages/game/durabilidad.ts new file mode 100644 index 0000000..33065b1 --- /dev/null +++ b/src/commands/messages/game/durabilidad.ts @@ -0,0 +1,96 @@ +import type { CommandMessage } from "../../../core/types/commands"; +import type Amayo from "../../../core/client"; +import { prisma } from "../../../core/database/prisma"; + +type ItemProps = { + tool?: { type: string; tier?: number }; + damage?: number; + defense?: number; + maxHpBonus?: number; + breakable?: { + enabled?: boolean; + maxDurability?: number; + durabilityPerUse?: number; + }; + [k: string]: unknown; +}; + +type InventoryState = { + instances?: Array<{ + durability?: number; + [k: string]: unknown; + }>; + [k: string]: unknown; +}; + +export const command: CommandMessage = { + name: "durabilidad", + type: "message", + aliases: ["dur", "durability"], + cooldown: 3, + category: "Juegos", + description: + "Muestra la durabilidad de tus items no-apilables (herramientas, armas, armaduras).", + usage: "durabilidad", + run: async (message, args, _client: Amayo) => { + const userId = message.author.id; + const guildId = message.guild!.id; + + const entries = await prisma.inventoryEntry.findMany({ + where: { userId, guildId }, + include: { item: true }, + }); + + const durableItems = entries.filter((e) => { + const props = e.item.props as ItemProps; + return ( + !e.item.stackable && + props.breakable && + props.breakable.enabled !== false + ); + }); + + if (durableItems.length === 0) { + await message.reply( + "📦 No tienes items con durabilidad en tu inventario." + ); + return; + } + + let output = `🔧 **Durabilidad de Items**\n\n`; + + for (const entry of durableItems) { + const item = entry.item; + const props = item.props as ItemProps; + const state = entry.state as InventoryState; + const instances = state?.instances ?? []; + const maxDur = props.breakable?.maxDurability ?? 100; + + output += `**${item.name}** (\`${item.key}\`)\n`; + + if (instances.length === 0) { + output += `⚠️ **CORRUPTO**: Quantity=${entry.quantity} pero sin instances\n`; + output += `• Usa \`!reset-inventory\` para reparar\n\n`; + continue; + } + + // Mostrar cada instancia con su durabilidad + instances.forEach((inst, idx) => { + const dur = inst.durability ?? 0; + const percentage = Math.round((dur / maxDur) * 100); + const bars = Math.floor(percentage / 10); + const barDisplay = "█".repeat(bars) + "░".repeat(10 - bars); + + output += ` [${idx + 1}] ${barDisplay} ${dur}/${maxDur} (${percentage}%)\n`; + }); + + output += `• Total: ${instances.length} unidad(es)\n\n`; + } + + // Dividir en chunks si es muy largo + const chunks = output.match(/[\s\S]{1,1900}/g) ?? [output]; + for (const chunk of chunks) { + await message.reply(chunk); + } + }, +}; diff --git a/src/commands/messages/game/inventario.ts b/src/commands/messages/game/inventario.ts index 6ffaf22..275d0a3 100644 --- a/src/commands/messages/game/inventario.ts +++ b/src/commands/messages/game/inventario.ts @@ -133,8 +133,26 @@ export const command: CommandMessage = { const props = parseItemProps(entry.item.props); const tool = fmtTool(props); const st = fmtStats(props); - const label = formatItemLabel(entry.item); - blocks.push(textBlock(`• ${label} — x${entry.quantity}${tool ? ` ${tool}` : ''}${st}`)); + const label = formatItemLabel(entry.item); + + // Mostrar durabilidad para items non-stackable con breakable + let qtyDisplay = `x${entry.quantity}`; + if (!entry.item.stackable && props.breakable && props.breakable.enabled !== false) { + const state = entry.state as any; + const instances = state?.instances ?? []; + if (instances.length > 0 && instances[0]?.durability != null) { + const firstDur = instances[0].durability; + const maxDur = props.breakable.maxDurability ?? 100; + qtyDisplay = `(${firstDur}/${maxDur})`; + if (instances.length > 1) { + qtyDisplay += ` x${instances.length}`; + } + } else if (instances.length === 0) { + qtyDisplay = `⚠️ CORRUPTO (x${entry.quantity})`; + } + } + + blocks.push(textBlock(`• ${label} — ${qtyDisplay}${tool ? ` ${tool}` : ''}${st}`)); if (index < pageItems.length - 1) { blocks.push(dividerBlock({ divider: false, spacing: 1 })); } diff --git a/src/game/minigames/service.ts b/src/game/minigames/service.ts index 9d084ba..d0ba31e 100644 --- a/src/game/minigames/service.ts +++ b/src/game/minigames/service.ts @@ -59,7 +59,7 @@ async function findBestToolKey( where: { userId, guildId, quantity: { gt: 0 } }, include: { item: true }, }); - let best: { key: string; tier: number } | null = null; + let best: { key: string; tier: number; isPrimaryTool: boolean } | null = null; for (const e of entries) { const props = parseItemProps(e.item.props); const t = props.tool; @@ -72,7 +72,16 @@ async function findBestToolKey( !opts.allowedKeys.includes(e.item.key) ) continue; - if (!best || tier > best.tier) best = { key: e.item.key, tier }; + // Priorizar items con key que comience con "tool." (herramientas primarias) + // sobre armas que también tienen toolType (ej: espada con tool.type:sword) + const isPrimaryTool = e.item.key.startsWith("tool."); + if ( + !best || + tier > best.tier || + (tier === best.tier && isPrimaryTool && !best.isPrimaryTool) + ) { + best = { key: e.item.key, tier, isPrimaryTool }; + } } return best?.key ?? null; }