feat: agregar comando para mostrar la durabilidad de items no-apilables en el inventario
This commit is contained in:
105
README/FIX_TOOL_SELECTION_PRIORITY.md
Normal file
105
README/FIX_TOOL_SELECTION_PRIORITY.md
Normal 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.
|
||||||
@@ -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
|
||||||
|
|||||||
96
src/commands/messages/game/durabilidad.ts
Normal file
96
src/commands/messages/game/durabilidad.ts
Normal 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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -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 }));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user