feat: agregar comando para mostrar la durabilidad de items no-apilables en el inventario

This commit is contained in:
2025-10-09 02:58:47 -05:00
parent 79ece13420
commit a2bf5550f9
5 changed files with 238 additions and 4 deletions

View File

@@ -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.

View File

@@ -38,6 +38,12 @@ export const command: CommandMessage = {
output += `• Quantity: ${entry.quantity}\n`; output += `• Quantity: ${entry.quantity}\n`;
output += `• Instances: ${instances.length}\n`; output += `• Instances: ${instances.length}\n`;
if (props?.tool) {
output += `• Tool: type=${props.tool.type}, tier=${
props.tool.tier ?? 0
}\n`;
}
if (props?.breakable) { if (props?.breakable) {
output += `• Breakable: enabled=${ output += `• Breakable: enabled=${
props.breakable.enabled !== false props.breakable.enabled !== false

View File

@@ -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);
}
},
};

View File

@@ -133,8 +133,26 @@ export const command: CommandMessage = {
const props = parseItemProps(entry.item.props); const props = parseItemProps(entry.item.props);
const tool = fmtTool(props); const tool = fmtTool(props);
const st = fmtStats(props); const st = fmtStats(props);
const label = formatItemLabel(entry.item); const label = formatItemLabel(entry.item);
blocks.push(textBlock(`${label} — x${entry.quantity}${tool ? ` ${tool}` : ''}${st}`));
// 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) { if (index < pageItems.length - 1) {
blocks.push(dividerBlock({ divider: false, spacing: 1 })); blocks.push(dividerBlock({ divider: false, spacing: 1 }));
} }

View File

@@ -59,7 +59,7 @@ async function findBestToolKey(
where: { userId, guildId, quantity: { gt: 0 } }, where: { userId, guildId, quantity: { gt: 0 } },
include: { item: true }, 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) { for (const e of entries) {
const props = parseItemProps(e.item.props); const props = parseItemProps(e.item.props);
const t = props.tool; const t = props.tool;
@@ -72,7 +72,16 @@ async function findBestToolKey(
!opts.allowedKeys.includes(e.item.key) !opts.allowedKeys.includes(e.item.key)
) )
continue; 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; return best?.key ?? null;
} }