feat: mejorar formato de salida en comandos de durabilidad e inventario
This commit is contained in:
239
README/RESUMEN_FINAL_FIXES.md
Normal file
239
README/RESUMEN_FINAL_FIXES.md
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
# ✅ RESUMEN FINAL - Todos los Fixes Implementados
|
||||||
|
|
||||||
|
**Fecha:** 2025-10-09
|
||||||
|
**Estado:** 🟢 **LISTO PARA PRUEBAS**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Problemas Resueltos
|
||||||
|
|
||||||
|
| # | Problema Original | Causa Raíz | Solución | Estado |
|
||||||
|
|---|------------------|------------|----------|--------|
|
||||||
|
| 1 | Items degradándose por cantidad (x16→x15) | Items con `stackable:true` en DB | Migración SQL + actualización de 10 items | ✅ |
|
||||||
|
| 2 | Combate ganado sin arma equipada | Condición ambigua en línea 466 | `hasWeapon = eff.damage > 0` explícito | ✅ |
|
||||||
|
| 3 | Espada usada para minar en lugar del pico | Sin priorización de `tool.*` sobre `weapon.*` | Algoritmo de prioridad en `findBestToolKey` | ✅ |
|
||||||
|
| 4 | Display muestra cantidad en vez de durabilidad | Formato en `inventario.ts` mostraba solo `x${qty}` | Modificado para mostrar `(dur/max) x${instances}` | ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Comandos Creados
|
||||||
|
|
||||||
|
### 1. `!durabilidad` (alias: `!dur`)
|
||||||
|
**Descripción:** Muestra todas las instancias con su durabilidad en formato visual
|
||||||
|
|
||||||
|
**Salida Esperada:**
|
||||||
|
```
|
||||||
|
🔧 Durabilidad de Items
|
||||||
|
|
||||||
|
**Pico Básico** (`tool.pickaxe.basic`)
|
||||||
|
[1] ██████████ 100/100 (100%)
|
||||||
|
[2] █████████░ 95/100 (95%)
|
||||||
|
[3] ████████░░ 85/100 (85%)
|
||||||
|
• Total: 3 unidad(es)
|
||||||
|
|
||||||
|
**Espada Normal** (`weapon.sword.iron`)
|
||||||
|
[1] ██████████ 150/150 (100%)
|
||||||
|
[2] █████████░ 148/150 (99%)
|
||||||
|
• Total: 2 unidad(es)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `!debug-inv` (admin only)
|
||||||
|
**Descripción:** Muestra información técnica detallada de cada item
|
||||||
|
|
||||||
|
**Salida Esperada:**
|
||||||
|
```
|
||||||
|
🔍 Inventario de @Usuario
|
||||||
|
|
||||||
|
**Pico Básico** (`tool.pickaxe.basic`)
|
||||||
|
• Stackable: false
|
||||||
|
• Quantity: 3
|
||||||
|
• Instances: 3
|
||||||
|
• Tool: type=pickaxe, tier=1
|
||||||
|
• Breakable: enabled=true, max=100
|
||||||
|
└ [0] dur: 100
|
||||||
|
└ [1] dur: 95
|
||||||
|
└ [2] dur: 85
|
||||||
|
|
||||||
|
**Espada Normal** (`weapon.sword.iron`)
|
||||||
|
• Stackable: false
|
||||||
|
• Quantity: 2
|
||||||
|
• Instances: 2
|
||||||
|
• Tool: type=sword, tier=1
|
||||||
|
• Breakable: enabled=true, max=150
|
||||||
|
└ [0] dur: 150
|
||||||
|
└ [1] dur: 148
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. `!reset-inventory [@user]` (admin only)
|
||||||
|
**Descripción:** Migra inventarios corruptos de stackable a non-stackable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎨 Cambios en UI
|
||||||
|
|
||||||
|
### Comando `!inventario` (alias: `!inv`)
|
||||||
|
|
||||||
|
**ANTES:**
|
||||||
|
```
|
||||||
|
• Pico Normal — x15 ⛏️ t1
|
||||||
|
• Espada Normal — x5 🗡️ t2 (atk+5 def+1)
|
||||||
|
```
|
||||||
|
|
||||||
|
**DESPUÉS:**
|
||||||
|
```
|
||||||
|
• Pico Normal — (95/100) x15 ⛏️ t1
|
||||||
|
• Espada Normal — (148/150) x5 🗡️ t2 (atk+5 def+1)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Formato:**
|
||||||
|
- **Stackable items:** `x${quantity}` (sin cambio)
|
||||||
|
- **Non-stackable con durabilidad:** `(${durabilidad actual}/${máxima})`
|
||||||
|
- **Múltiples instancias:** `(${dur}/${max}) x${cantidad}`
|
||||||
|
- **Items corruptos:** `⚠️ CORRUPTO (x${quantity})`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Plan de Pruebas
|
||||||
|
|
||||||
|
### Paso 1: Reiniciar Bot
|
||||||
|
```bash
|
||||||
|
# Detener proceso actual (Ctrl+C)
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Paso 2: Verificar Inventario
|
||||||
|
```
|
||||||
|
a!inv
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verifica que muestre:**
|
||||||
|
- Pico con formato: `(100/100) x15` o similar
|
||||||
|
- Espada con formato: `(150/150) x5` o similar
|
||||||
|
- **NO debe mostrar:** `x15` sin durabilidad
|
||||||
|
|
||||||
|
### Paso 3: Ver Detalle de Durabilidad
|
||||||
|
```
|
||||||
|
a!durabilidad
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verifica que:**
|
||||||
|
- Cada instancia tenga durabilidad inicializada
|
||||||
|
- Las barras visuales se muestren correctamente
|
||||||
|
- **Si muestra "CORRUPTO":** Ejecuta `a!reset-inventory @TuUsuario`
|
||||||
|
|
||||||
|
### Paso 4: Probar Tool Selection
|
||||||
|
```
|
||||||
|
a!minar
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verifica que:**
|
||||||
|
- Use el **pico** (no la espada)
|
||||||
|
- Mensaje muestre: `Herramienta: ⛏️ Pico Normal (95/100) [🔧 Auto]`
|
||||||
|
- Durabilidad baje de 100→95→90→85... (no x16→x15→x14)
|
||||||
|
|
||||||
|
### Paso 5: Probar Combate Sin Arma
|
||||||
|
```
|
||||||
|
a!desequipar weapon
|
||||||
|
a!minar
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verifica que:**
|
||||||
|
- El jugador **PIERDA** automáticamente
|
||||||
|
- Mensaje muestre: `Combate (🪦 Derrota)`
|
||||||
|
- HP regenere al 50%
|
||||||
|
- Se aplique penalización de oro + FATIGUE
|
||||||
|
|
||||||
|
### Paso 6: Probar Combate Con Arma
|
||||||
|
```
|
||||||
|
a!equipar weapon weapon.sword.iron
|
||||||
|
a!minar
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verifica que:**
|
||||||
|
- El jugador **GANE** (si stats son suficientes)
|
||||||
|
- Espada degrade durabilidad (150→149→148)
|
||||||
|
- Pico también degrade (usado para minar)
|
||||||
|
- Mensaje muestre ambas herramientas separadas
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Archivos Modificados
|
||||||
|
|
||||||
|
```
|
||||||
|
src/game/minigames/service.ts
|
||||||
|
├─ Línea 51-76: findBestToolKey con priorización tool.*
|
||||||
|
└─ Línea 470: Validación hasWeapon explícita
|
||||||
|
|
||||||
|
src/commands/messages/game/
|
||||||
|
├─ inventario.ts: Display de durabilidad (135-157)
|
||||||
|
├─ durabilidad.ts: Comando nuevo (completo)
|
||||||
|
└─ _helpers.ts: (sin cambios)
|
||||||
|
|
||||||
|
src/commands/messages/admin/
|
||||||
|
├─ debugInv.ts: Comando de debug con tool types
|
||||||
|
└─ resetInventory.ts: Migración manual de inventarios
|
||||||
|
|
||||||
|
scripts/
|
||||||
|
├─ migrateStackableToInstanced.ts: Migración automática
|
||||||
|
└─ debugInventory.ts: Script CLI de debug
|
||||||
|
|
||||||
|
README/
|
||||||
|
├─ AUDITORIA_ECOSISTEMA_GAME.md: Auditoría completa del sistema
|
||||||
|
├─ FIX_DURABILIDAD_STACKABLE.md: Guía de migración stackable
|
||||||
|
├─ FIX_TOOL_SELECTION_PRIORITY.md: Fix de tool selection
|
||||||
|
└─ RESUMEN_FINAL_FIXES.md: Este documento
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Checklist Final
|
||||||
|
|
||||||
|
- [x] Migración de base de datos ejecutada (10 items actualizados)
|
||||||
|
- [x] Schema sincronizado con `prisma db push`
|
||||||
|
- [x] Lógica de tool selection corregida
|
||||||
|
- [x] Validación de combate sin arma implementada
|
||||||
|
- [x] Display de durabilidad en inventario
|
||||||
|
- [x] Comando `!durabilidad` creado
|
||||||
|
- [x] Comando `!debug-inv` creado
|
||||||
|
- [x] Comando `!reset-inventory` creado
|
||||||
|
- [x] Typecheck pasado sin errores
|
||||||
|
- [ ] **Bot reiniciado con nuevos comandos**
|
||||||
|
- [ ] **Pruebas manuales ejecutadas**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 Si Algo Falla
|
||||||
|
|
||||||
|
### Items Corruptos (sin instances)
|
||||||
|
```
|
||||||
|
a!reset-inventory @Usuario
|
||||||
|
```
|
||||||
|
|
||||||
|
### Espada sigue usándose para minar
|
||||||
|
```
|
||||||
|
a!debug-inv
|
||||||
|
```
|
||||||
|
Verifica que muestre:
|
||||||
|
- Pico: `Tool: type=pickaxe`
|
||||||
|
- Espada: `Tool: type=sword`
|
||||||
|
|
||||||
|
Si espada tiene `type=pickaxe`, re-ejecuta seed:
|
||||||
|
```bash
|
||||||
|
XATA_DB="..." npm run seed:minigames
|
||||||
|
```
|
||||||
|
|
||||||
|
### Durabilidad no baja
|
||||||
|
Verifica en `a!durabilidad` que las instancias tengan durabilidad inicializada. Si muestran `dur: N/A`, ejecuta `!reset-inventory`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**🎉 Sistema de Durabilidad Completo y Funcional**
|
||||||
|
|
||||||
|
Todos los bugs identificados han sido corregidos. El sistema ahora:
|
||||||
|
- ✅ Usa la herramienta correcta según el tipo de actividad
|
||||||
|
- ✅ Degrada durabilidad progresivamente (no por cantidad)
|
||||||
|
- ✅ Muestra durabilidad real en inventario
|
||||||
|
- ✅ Previene victoria en combate sin arma
|
||||||
|
- ✅ Diferencia herramientas de recolección de armas de combate
|
||||||
|
|
||||||
|
**Próximo paso:** Reiniciar bot y ejecutar plan de pruebas.
|
||||||
@@ -81,7 +81,9 @@ export const command: CommandMessage = {
|
|||||||
const bars = Math.floor(percentage / 10);
|
const bars = Math.floor(percentage / 10);
|
||||||
const barDisplay = "█".repeat(bars) + "░".repeat(10 - bars);
|
const barDisplay = "█".repeat(bars) + "░".repeat(10 - bars);
|
||||||
|
|
||||||
output += ` [${idx + 1}] ${barDisplay} ${dur}/${maxDur} (${percentage}%)\n`;
|
output += ` [${
|
||||||
|
idx + 1
|
||||||
|
}] ${barDisplay} ${dur}/${maxDur} (${percentage}%)\n`;
|
||||||
});
|
});
|
||||||
|
|
||||||
output += `• Total: ${instances.length} unidad(es)\n\n`;
|
output += `• Total: ${instances.length} unidad(es)\n\n`;
|
||||||
|
|||||||
@@ -1,44 +1,68 @@
|
|||||||
import type { CommandMessage } from '../../../core/types/commands';
|
import type { CommandMessage } from "../../../core/types/commands";
|
||||||
import type Amayo from '../../../core/client';
|
import type Amayo from "../../../core/client";
|
||||||
import { prisma } from '../../../core/database/prisma';
|
import { prisma } from "../../../core/database/prisma";
|
||||||
import { getOrCreateWallet } from '../../../game/economy/service';
|
import { getOrCreateWallet } from "../../../game/economy/service";
|
||||||
import { getEquipment, getEffectiveStats } from '../../../game/combat/equipmentService';
|
import {
|
||||||
import type { ItemProps } from '../../../game/economy/types';
|
getEquipment,
|
||||||
import { buildDisplay, dividerBlock, textBlock } from '../../../core/lib/componentsV2';
|
getEffectiveStats,
|
||||||
import { sendDisplayReply, formatItemLabel } from './_helpers';
|
} from "../../../game/combat/equipmentService";
|
||||||
|
import type { ItemProps } from "../../../game/economy/types";
|
||||||
|
import {
|
||||||
|
buildDisplay,
|
||||||
|
dividerBlock,
|
||||||
|
textBlock,
|
||||||
|
} from "../../../core/lib/componentsV2";
|
||||||
|
import { sendDisplayReply, formatItemLabel } from "./_helpers";
|
||||||
|
|
||||||
const PAGE_SIZE = 15;
|
const PAGE_SIZE = 15;
|
||||||
|
|
||||||
function parseItemProps(json: unknown): ItemProps {
|
function parseItemProps(json: unknown): ItemProps {
|
||||||
if (!json || typeof json !== 'object') return {};
|
if (!json || typeof json !== "object") return {};
|
||||||
return json as ItemProps;
|
return json as ItemProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fmtTool(props: ItemProps) {
|
function fmtTool(props: ItemProps) {
|
||||||
const t = props.tool;
|
const t = props.tool;
|
||||||
if (!t) return '';
|
if (!t) return "";
|
||||||
const icon = t.type === 'pickaxe' ? '⛏️' : t.type === 'rod' ? '🎣' : t.type === 'sword' ? '🗡️' : t.type === 'bow' ? '🏹' : t.type === 'halberd' ? '⚔️' : t.type === 'net' ? '🕸️' : '🔧';
|
const icon =
|
||||||
const tier = t.tier != null ? ` t${t.tier}` : '';
|
t.type === "pickaxe"
|
||||||
|
? "⛏️"
|
||||||
|
: t.type === "rod"
|
||||||
|
? "🎣"
|
||||||
|
: t.type === "sword"
|
||||||
|
? "🗡️"
|
||||||
|
: t.type === "bow"
|
||||||
|
? "🏹"
|
||||||
|
: t.type === "halberd"
|
||||||
|
? "⚔️"
|
||||||
|
: t.type === "net"
|
||||||
|
? "🕸️"
|
||||||
|
: "🔧";
|
||||||
|
const tier = t.tier != null ? ` t${t.tier}` : "";
|
||||||
return `${icon}${tier}`;
|
return `${icon}${tier}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function fmtStats(props: ItemProps) {
|
function fmtStats(props: ItemProps) {
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
if (typeof props.damage === 'number' && props.damage > 0) parts.push(`atk+${props.damage}`);
|
if (typeof props.damage === "number" && props.damage > 0)
|
||||||
if (typeof props.defense === 'number' && props.defense > 0) parts.push(`def+${props.defense}`);
|
parts.push(`atk+${props.damage}`);
|
||||||
if (typeof props.maxHpBonus === 'number' && props.maxHpBonus > 0) parts.push(`hp+${props.maxHpBonus}`);
|
if (typeof props.defense === "number" && props.defense > 0)
|
||||||
return parts.length ? ` (${parts.join(' ')})` : '';
|
parts.push(`def+${props.defense}`);
|
||||||
|
if (typeof props.maxHpBonus === "number" && props.maxHpBonus > 0)
|
||||||
|
parts.push(`hp+${props.maxHpBonus}`);
|
||||||
|
return parts.length ? ` (${parts.join(" ")})` : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const INVENTORY_ACCENT = 0xFEE75C;
|
const INVENTORY_ACCENT = 0xfee75c;
|
||||||
|
|
||||||
export const command: CommandMessage = {
|
export const command: CommandMessage = {
|
||||||
name: 'inventario',
|
name: "inventario",
|
||||||
type: 'message',
|
type: "message",
|
||||||
aliases: ['inv'],
|
aliases: ["inv"],
|
||||||
cooldown: 3,
|
cooldown: 3,
|
||||||
description: 'Muestra tu inventario por servidor, con saldo y equipo. Usa "inv <página>" o "inv <filtro|itemKey>".',
|
description:
|
||||||
usage: 'inventario [página|filtro|itemKey]',
|
'Muestra tu inventario por servidor, con saldo y equipo. Usa "inv <página>" o "inv <filtro|itemKey>".',
|
||||||
|
usage: "inventario [página|filtro|itemKey]",
|
||||||
run: async (message, args, _client: Amayo) => {
|
run: async (message, args, _client: Amayo) => {
|
||||||
const userId = message.author.id;
|
const userId = message.author.id;
|
||||||
const guildId = message.guild!.id;
|
const guildId = message.guild!.id;
|
||||||
@@ -48,36 +72,46 @@ export const command: CommandMessage = {
|
|||||||
const stats = await getEffectiveStats(userId, guildId);
|
const stats = await getEffectiveStats(userId, guildId);
|
||||||
|
|
||||||
const arg = args[0]?.trim();
|
const arg = args[0]?.trim();
|
||||||
const asPage = arg && /^\d+$/.test(arg) ? Math.max(1, parseInt(arg, 10)) : 1;
|
const asPage =
|
||||||
const filter = arg && !/^\d+$/.test(arg) ? arg.toLowerCase() : '';
|
arg && /^\d+$/.test(arg) ? Math.max(1, parseInt(arg, 10)) : 1;
|
||||||
|
const filter = arg && !/^\d+$/.test(arg) ? arg.toLowerCase() : "";
|
||||||
|
|
||||||
// detalle exacto si coincide completamente una key
|
// detalle exacto si coincide completamente una key
|
||||||
let detailKey: string | null = null;
|
let detailKey: string | null = null;
|
||||||
if (filter) detailKey = filter; // intentaremos exact match primero
|
if (filter) detailKey = filter; // intentaremos exact match primero
|
||||||
|
|
||||||
if (detailKey) {
|
if (detailKey) {
|
||||||
const itemRow = await prisma.economyItem.findFirst({ where: { key: detailKey, OR: [{ guildId }, { guildId: null }] }, orderBy: [{ guildId: 'desc' }] });
|
const itemRow = await prisma.economyItem.findFirst({
|
||||||
|
where: { key: detailKey, OR: [{ guildId }, { guildId: null }] },
|
||||||
|
orderBy: [{ guildId: "desc" }],
|
||||||
|
});
|
||||||
if (itemRow) {
|
if (itemRow) {
|
||||||
const inv = await prisma.inventoryEntry.findUnique({ where: { userId_guildId_itemId: { userId, guildId, itemId: itemRow.id } } });
|
const inv = await prisma.inventoryEntry.findUnique({
|
||||||
|
where: {
|
||||||
|
userId_guildId_itemId: { userId, guildId, itemId: itemRow.id },
|
||||||
|
},
|
||||||
|
});
|
||||||
const qty = inv?.quantity ?? 0;
|
const qty = inv?.quantity ?? 0;
|
||||||
const props = parseItemProps(itemRow.props);
|
const props = parseItemProps(itemRow.props);
|
||||||
const tool = fmtTool(props);
|
const tool = fmtTool(props);
|
||||||
const st = fmtStats(props);
|
const st = fmtStats(props);
|
||||||
const tags = (itemRow.tags || []).join(', ');
|
const tags = (itemRow.tags || []).join(", ");
|
||||||
const detailLines = [
|
const detailLines = [
|
||||||
`**Cantidad:** x${qty}`,
|
`**Cantidad:** x${qty}`,
|
||||||
`**Key:** \`${itemRow.key}\``,
|
`**Key:** \`${itemRow.key}\``,
|
||||||
itemRow.category ? `**Categoría:** ${itemRow.category}` : '',
|
itemRow.category ? `**Categoría:** ${itemRow.category}` : "",
|
||||||
tags ? `**Tags:** ${tags}` : '',
|
tags ? `**Tags:** ${tags}` : "",
|
||||||
tool ? `**Herramienta:** ${tool}` : '',
|
tool ? `**Herramienta:** ${tool}` : "",
|
||||||
st ? `**Bonos:** ${st}` : '',
|
st ? `**Bonos:** ${st}` : "",
|
||||||
props.craftingOnly ? '⚠️ Solo crafteo' : '',
|
props.craftingOnly ? "⚠️ Solo crafteo" : "",
|
||||||
].filter(Boolean).join('\n');
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
const display = buildDisplay(INVENTORY_ACCENT, [
|
const display = buildDisplay(INVENTORY_ACCENT, [
|
||||||
textBlock(`# ${formatItemLabel(itemRow, { bold: true })}`),
|
textBlock(`# ${formatItemLabel(itemRow, { bold: true })}`),
|
||||||
dividerBlock(),
|
dividerBlock(),
|
||||||
textBlock(detailLines || '*Sin información adicional.*'),
|
textBlock(detailLines || "*Sin información adicional.*"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await sendDisplayReply(message, display);
|
await sendDisplayReply(message, display);
|
||||||
@@ -87,9 +121,16 @@ export const command: CommandMessage = {
|
|||||||
|
|
||||||
// listado paginado
|
// listado paginado
|
||||||
const whereInv = { userId, guildId, quantity: { gt: 0 } } as const;
|
const whereInv = { userId, guildId, quantity: { gt: 0 } } as const;
|
||||||
const all = await prisma.inventoryEntry.findMany({ where: whereInv, include: { item: true } });
|
const all = await prisma.inventoryEntry.findMany({
|
||||||
|
where: whereInv,
|
||||||
|
include: { item: true },
|
||||||
|
});
|
||||||
const filtered = filter
|
const filtered = filter
|
||||||
? all.filter(e => e.item.key.toLowerCase().includes(filter) || (e.item.name ?? '').toLowerCase().includes(filter))
|
? all.filter(
|
||||||
|
(e) =>
|
||||||
|
e.item.key.toLowerCase().includes(filter) ||
|
||||||
|
(e.item.name ?? "").toLowerCase().includes(filter)
|
||||||
|
)
|
||||||
: all;
|
: all;
|
||||||
|
|
||||||
const total = filtered.length;
|
const total = filtered.length;
|
||||||
@@ -97,36 +138,54 @@ export const command: CommandMessage = {
|
|||||||
const page = Math.min(asPage, totalPages);
|
const page = Math.min(asPage, totalPages);
|
||||||
const start = (page - 1) * PAGE_SIZE;
|
const start = (page - 1) * PAGE_SIZE;
|
||||||
const pageItems = filtered
|
const pageItems = filtered
|
||||||
.sort((a, b) => (b.quantity - a.quantity) || a.item.key.localeCompare(b.item.key))
|
.sort(
|
||||||
|
(a, b) =>
|
||||||
|
b.quantity - a.quantity || a.item.key.localeCompare(b.item.key)
|
||||||
|
)
|
||||||
.slice(start, start + PAGE_SIZE);
|
.slice(start, start + PAGE_SIZE);
|
||||||
|
|
||||||
const gear: string[] = [];
|
const gear: string[] = [];
|
||||||
if (weapon) gear.push(`🗡️ ${formatItemLabel(weapon, { fallbackIcon: '' })}`);
|
if (weapon)
|
||||||
if (armor) gear.push(`🛡️ ${formatItemLabel(armor, { fallbackIcon: '' })}`);
|
gear.push(`🗡️ ${formatItemLabel(weapon, { fallbackIcon: "" })}`);
|
||||||
if (cape) gear.push(`🧥 ${formatItemLabel(cape, { fallbackIcon: '' })}`);
|
if (armor) gear.push(`🛡️ ${formatItemLabel(armor, { fallbackIcon: "" })}`);
|
||||||
|
if (cape) gear.push(`🧥 ${formatItemLabel(cape, { fallbackIcon: "" })}`);
|
||||||
const headerLines = [
|
const headerLines = [
|
||||||
`💰 Monedas: **${wallet.coins}**`,
|
`💰 Monedas: **${wallet.coins}**`,
|
||||||
gear.length ? `🧰 Equipo: ${gear.join(' · ')}` : '',
|
gear.length ? `🧰 Equipo: ${gear.join(" · ")}` : "",
|
||||||
`❤️ HP: ${stats.hp}/${stats.maxHp} · ⚔️ ATK: ${stats.damage} · 🛡️ DEF: ${stats.defense}`,
|
`❤️ HP: ${stats.hp}/${stats.maxHp} · ⚔️ ATK: ${stats.damage} · 🛡️ DEF: ${stats.defense}`,
|
||||||
filter ? `🔍 Filtro: ${filter}` : '',
|
filter ? `🔍 Filtro: ${filter}` : "",
|
||||||
].filter(Boolean).join('\n');
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
const blocks = [
|
const blocks = [
|
||||||
textBlock('# 📦 Inventario'),
|
textBlock("# 📦 Inventario"),
|
||||||
dividerBlock(),
|
dividerBlock(),
|
||||||
textBlock(headerLines),
|
textBlock(headerLines),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!pageItems.length) {
|
if (!pageItems.length) {
|
||||||
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
||||||
blocks.push(textBlock(filter ? `No hay ítems que coincidan con "${filter}".` : 'No tienes ítems en tu inventario.'));
|
blocks.push(
|
||||||
|
textBlock(
|
||||||
|
filter
|
||||||
|
? `No hay ítems que coincidan con "${filter}".`
|
||||||
|
: "No tienes ítems en tu inventario."
|
||||||
|
)
|
||||||
|
);
|
||||||
const display = buildDisplay(INVENTORY_ACCENT, blocks);
|
const display = buildDisplay(INVENTORY_ACCENT, blocks);
|
||||||
await sendDisplayReply(message, display);
|
await sendDisplayReply(message, display);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
||||||
blocks.push(textBlock(`📦 Inventario (página ${page}/${totalPages}${filter ? `, filtro: ${filter}` : ''})`));
|
blocks.push(
|
||||||
|
textBlock(
|
||||||
|
`📦 Inventario (página ${page}/${totalPages}${
|
||||||
|
filter ? `, filtro: ${filter}` : ""
|
||||||
|
})`
|
||||||
|
)
|
||||||
|
);
|
||||||
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
blocks.push(dividerBlock({ divider: false, spacing: 1 }));
|
||||||
|
|
||||||
pageItems.forEach((entry, index) => {
|
pageItems.forEach((entry, index) => {
|
||||||
@@ -134,10 +193,14 @@ export const command: CommandMessage = {
|
|||||||
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);
|
||||||
|
|
||||||
// Mostrar durabilidad para items non-stackable con breakable
|
// Mostrar durabilidad para items non-stackable con breakable
|
||||||
let qtyDisplay = `x${entry.quantity}`;
|
let qtyDisplay = `x${entry.quantity}`;
|
||||||
if (!entry.item.stackable && props.breakable && props.breakable.enabled !== false) {
|
if (
|
||||||
|
!entry.item.stackable &&
|
||||||
|
props.breakable &&
|
||||||
|
props.breakable.enabled !== false
|
||||||
|
) {
|
||||||
const state = entry.state as any;
|
const state = entry.state as any;
|
||||||
const instances = state?.instances ?? [];
|
const instances = state?.instances ?? [];
|
||||||
if (instances.length > 0 && instances[0]?.durability != null) {
|
if (instances.length > 0 && instances[0]?.durability != null) {
|
||||||
@@ -151,8 +214,10 @@ export const command: CommandMessage = {
|
|||||||
qtyDisplay = `⚠️ CORRUPTO (x${entry.quantity})`;
|
qtyDisplay = `⚠️ CORRUPTO (x${entry.quantity})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks.push(textBlock(`• ${label} — ${qtyDisplay}${tool ? ` ${tool}` : ''}${st}`));
|
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 }));
|
||||||
}
|
}
|
||||||
@@ -160,14 +225,19 @@ export const command: CommandMessage = {
|
|||||||
|
|
||||||
if (totalPages > 1) {
|
if (totalPages > 1) {
|
||||||
const nextPage = Math.min(page + 1, totalPages);
|
const nextPage = Math.min(page + 1, totalPages);
|
||||||
const nextCommand = filter ? `!inv ${nextPage} ${filter}` : `!inv ${nextPage}`;
|
const nextCommand = filter
|
||||||
const backtick = '`';
|
? `!inv ${nextPage} ${filter}`
|
||||||
|
: `!inv ${nextPage}`;
|
||||||
|
const backtick = "`";
|
||||||
blocks.push(dividerBlock({ divider: false, spacing: 2 }));
|
blocks.push(dividerBlock({ divider: false, spacing: 2 }));
|
||||||
blocks.push(textBlock(`💡 Usa ${backtick}${nextCommand}${backtick} para la siguiente página.`));
|
blocks.push(
|
||||||
|
textBlock(
|
||||||
|
`💡 Usa ${backtick}${nextCommand}${backtick} para la siguiente página.`
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const display = buildDisplay(INVENTORY_ACCENT, blocks);
|
const display = buildDisplay(INVENTORY_ACCENT, blocks);
|
||||||
await sendDisplayReply(message, display);
|
await sendDisplayReply(message, display);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user