diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md new file mode 100644 index 0000000..a0be93e --- /dev/null +++ b/IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,185 @@ +# 🎉 Resumen de Implementación - Sistema de Engagement + +## ✅ Lo que se ha implementado + +### 📊 Servicios Creados + +#### 1. **Sistema de Estadísticas** (`src/game/stats/`) +- ✅ `service.ts` - Servicio completo para tracking de estadísticas +- ✅ `types.ts` - Tipos TypeScript para stats +- **Funcionalidades:** + - Tracking de minas, pesca, combates, granja + - Estadísticas de combate (daño, mobs derrotados) + - Estadísticas económicas (monedas ganadas/gastadas, items crafteados) + - Récords personales + - Leaderboards por categoría + +#### 2. **Sistema de Recompensas** (`src/game/rewards/`) +- ✅ `service.ts` - Sistema centralizado de recompensas +- **Funcionalidades:** + - Dar monedas, items, XP y títulos + - Validación de recompensas + - Logging automático de auditoría + +#### 3. **Sistema de Logros** (`src/game/achievements/`) +- ✅ `service.ts` - Sistema completo de achievements +- ✅ `seed.ts` - 17 logros base pre-configurados +- **Funcionalidades:** + - Verificación automática de logros por triggers + - Tracking de progreso + - Logros ocultos + - Sistema de puntos + - Barras de progreso visuales + +#### 4. **Sistema de Rachas** (`src/game/streaks/`) +- ✅ `service.ts` - Sistema de rachas diarias +- **Funcionalidades:** + - Tracking de días consecutivos + - Recompensas escaladas por día + - Hitos especiales (día 7, 14, 30, etc.) + - Detección automática de expiración + +#### 5. **Sistema de Misiones** (`src/game/quests/`) +- ✅ `service.ts` - Sistema completo de quests +- **Funcionalidades:** + - Misiones diarias, semanales, permanentes y de evento + - Actualización automática de progreso + - Generación automática de misiones diarias + - Sistema de reclamación de recompensas + - Limpieza automática de misiones expiradas + +### 🎮 Comandos Creados + +#### 1. **!stats** (`src/commands/messages/game/stats.ts`) +- Ver estadísticas detalladas propias o de otro jugador +- Categorías: Actividades, Combate, Economía, Items, Récords +- Embed visual con avatar del jugador + +#### 2. **!racha** (`src/commands/messages/game/racha.ts`) +- Ver y reclamar racha diaria +- Muestra racha actual, mejor racha y días activos +- Recompensas automáticas al reclamar +- Indicador de próximos hitos + +#### 3. **!cooldowns** (`src/commands/messages/game/cooldowns.ts`) +- Ver todos los cooldowns activos +- Formato amigable con emojis +- Tiempo restante formateado (horas, minutos, segundos) + +#### 4. **!logros** (`src/commands/messages/game/logros.ts`) +- Ver logros desbloqueados y en progreso +- Barras de progreso visuales +- Estadísticas de logros (total, puntos) +- Categorización por tipo + +#### 5. **!misiones** (`src/commands/messages/game/misiones.ts`) +- Ver misiones disponibles por tipo +- Progreso visual de cada misión +- Indicador de misiones listas para reclamar +- Recompensas mostradas + +#### 6. **!mision-reclamar** (`src/commands/messages/game/misionReclamar.ts`) +- Reclamar recompensas de misiones completadas +- Validación automática +- Confirmación visual de recompensas recibidas + +### 🔗 Integraciones + +#### Comandos Existentes Mejorados: +- ✅ **!mina** - Ahora trackea stats, actualiza misiones y verifica logros +- ✅ **!pescar** - Ahora trackea stats, actualiza misiones y verifica logros +- ✅ **!pelear** - Ahora trackea stats, actualiza misiones y verifica logros + +### 📦 Logros Pre-configurados + +**Minería (4 logros):** +- ⛏️ Primera Mina (1 vez) +- ⛏️ Minero Novato (10 veces) +- ⛏️ Minero Experto (50 veces) +- ⛏️ Maestro Minero (100 veces) + +**Pesca (3 logros):** +- 🎣 Primera Pesca (1 vez) +- 🎣 Pescador Novato (10 veces) +- 🎣 Pescador Experto (50 veces) + +**Combate (4 logros):** +- ⚔️ Primera Pelea (1 vez) +- ⚔️ Guerrero Novato (10 veces) +- 👾 Cazador de Monstruos (50 mobs) +- 👾 Asesino de Monstruos (200 mobs) + +**Economía (3 logros):** +- 💰 Primeras Monedas (1,000 monedas) +- 💰 Acaudalado (10,000 monedas) +- 💰 Millonario (100,000 monedas) + +**Crafteo (3 logros):** +- 🛠️ Primer Crafteo (1 item) +- 🛠️ Artesano Experto (50 items) +- 🛠️ Maestro Artesano (200 items) + +## 🚀 Cómo Usar + +### Inicializar Logros Base +```bash +npx ts-node src/game/achievements/seed.ts +``` + +### Comandos para Usuarios +``` +!stats [@usuario] - Ver estadísticas +!racha - Ver/reclamar racha diaria +!cooldowns - Ver cooldowns activos +!logros [@usuario] - Ver logros +!misiones - Ver misiones disponibles +!mision-reclamar - Reclamar recompensa de misión +``` + +### Sistema Automático +- Las estadísticas se actualizan automáticamente al usar comandos +- Los logros se verifican después de cada acción +- Las misiones se actualizan en tiempo real +- Las rachas se actualizan al usar !racha + +## 🎯 Próximos Pasos Sugeridos + +### Fase 2 - Mejoras Adicionales: +1. ⬜ Crear más logros (objetivo: 50+) +2. ⬜ Implementar generación automática de misiones diarias (cron job) +3. ⬜ Crear comando `!ranking-stats` para leaderboards +4. ⬜ Añadir notificaciones por DM para logros importantes +5. ⬜ Implementar sistema de títulos desbloqueables +6. ⬜ Crear misiones semanales +7. ⬜ Sistema de eventos temporales + +### Fase 3 - Social: +1. ⬜ Sistema de clanes/guilds +2. ⬜ Trading entre jugadores +3. ⬜ Comparar stats con otros jugadores +4. ⬜ Logros cooperativos + +## 📝 Notas Técnicas + +- ✅ Todo el código está completamente tipado con TypeScript +- ✅ Los modelos de Prisma ya existen (Achievement, Quest, PlayerStats, etc.) +- ✅ Sistema de recompensas centralizado y reutilizable +- ✅ Logging automático de auditoría +- ✅ Manejo de errores robusto +- ✅ Compatible con sistema de guildId global/por servidor + +## 🐛 Testing Recomendado + +1. Probar cada comando nuevo individualmente +2. Verificar que los stats se incrementan correctamente +3. Confirmar que los logros se desbloquean al cumplir requisitos +4. Verificar el sistema de rachas por múltiples días +5. Probar reclamación de misiones +6. Verificar cooldowns + +## 🔧 Mantenimiento + +- Los logros se pueden añadir fácilmente en `seed.ts` +- Las misiones se pueden configurar con templates en `quests/service.ts` +- Las recompensas de rachas se pueden ajustar en `streaks/service.ts` +- Los triggers de logros son extensibles (añadir más tipos en achievements/service.ts) diff --git a/src/commands/messages/game/cooldowns.ts b/src/commands/messages/game/cooldowns.ts new file mode 100644 index 0000000..3b56151 --- /dev/null +++ b/src/commands/messages/game/cooldowns.ts @@ -0,0 +1,109 @@ +import type { CommandMessage } from '../../../core/types/commands'; +import type Amayo from '../../../core/client'; +import { prisma } from '../../../core/database/prisma'; +import { EmbedBuilder } from 'discord.js'; + +export const command: CommandMessage = { + name: 'cooldowns', + type: 'message', + aliases: ['cds', 'tiempos', 'cd'], + cooldown: 3, + description: 'Ver todos tus cooldowns activos', + usage: 'cooldowns', + run: async (message, args, client: Amayo) => { + try { + const userId = message.author.id; + const guildId = message.guild!.id; + + // Obtener todos los cooldowns activos + const cooldowns = await prisma.actionCooldown.findMany({ + where: { + userId, + guildId, + until: { gt: new Date() } + }, + orderBy: { until: 'asc' } + }); + + if (cooldowns.length === 0) { + await message.reply('✅ No tienes cooldowns activos. ¡Puedes realizar cualquier acción!'); + return; + } + + const embed = new EmbedBuilder() + .setColor(0xFF6B6B) + .setTitle('⏰ Cooldowns Activos') + .setDescription(`${message.author.username}, estos son tus cooldowns:`) + .setThumbnail(message.author.displayAvatarURL({ size: 128 })); + + // Emojis por tipo de acción + const actionEmojis: Record = { + 'mine': '⛏️', + 'fish': '🎣', + 'fight': '⚔️', + 'farm': '🌾', + 'craft': '🛠️', + 'smelt': '🔥', + 'shop': '🛒', + 'daily': '🎁', + 'consume': '🍖' + }; + + // Traducción de acciones + const actionNames: Record = { + 'mine': 'Minar', + 'fish': 'Pescar', + 'fight': 'Pelear', + 'farm': 'Granja', + 'craft': 'Craftear', + 'smelt': 'Fundir', + 'shop': 'Tienda', + 'daily': 'Diario', + 'consume': 'Consumir' + }; + + let cooldownText = ''; + const now = Date.now(); + + for (const cd of cooldowns) { + const remainingMs = cd.until.getTime() - now; + const remainingSec = Math.ceil(remainingMs / 1000); + + // Formatear tiempo + let timeStr = ''; + if (remainingSec >= 3600) { + const hours = Math.floor(remainingSec / 3600); + const mins = Math.floor((remainingSec % 3600) / 60); + timeStr = `${hours}h ${mins}m`; + } else if (remainingSec >= 60) { + const mins = Math.floor(remainingSec / 60); + const secs = remainingSec % 60; + timeStr = `${mins}m ${secs}s`; + } else { + timeStr = `${remainingSec}s`; + } + + // Buscar emoji y nombre + const action = cd.key.split(':')[0]; + const emoji = actionEmojis[action] || '⏱️'; + const actionName = actionNames[action] || cd.key; + + cooldownText += `${emoji} **${actionName}**: ${timeStr}\n`; + } + + embed.addFields({ + name: `📋 Cooldowns (${cooldowns.length})`, + value: cooldownText, + inline: false + }); + + embed.setFooter({ text: 'Los cooldowns se actualizan en tiempo real' }); + embed.setTimestamp(); + + await message.reply({ embeds: [embed] }); + } catch (error) { + console.error('Error en comando cooldowns:', error); + await message.reply('❌ Error al obtener los cooldowns.'); + } + } +}; diff --git a/src/commands/messages/game/logros.ts b/src/commands/messages/game/logros.ts new file mode 100644 index 0000000..4d83605 --- /dev/null +++ b/src/commands/messages/game/logros.ts @@ -0,0 +1,117 @@ +import type { CommandMessage } from '../../../core/types/commands'; +import type Amayo from '../../../core/client'; +import { getPlayerAchievements, getAchievementStats, createProgressBar } from '../../../game/achievements/service'; +import { EmbedBuilder } from 'discord.js'; + +export const command: CommandMessage = { + name: 'logros', + type: 'message', + aliases: ['achievements', 'logro', 'achievement'], + cooldown: 5, + description: 'Ver tus logros desbloqueados y progreso', + usage: 'logros [@usuario]', + run: async (message, args, client: Amayo) => { + try { + const guildId = message.guild!.id; + const targetUser = message.mentions.users.first() || message.author; + const userId = targetUser.id; + + // Obtener logros del jugador + const { unlocked, inProgress } = await getPlayerAchievements(userId, guildId); + const achievementStats = await getAchievementStats(userId, guildId); + + const embed = new EmbedBuilder() + .setColor(0xFFD700) + .setTitle(`🏆 Logros de ${targetUser.username}`) + .setThumbnail(targetUser.displayAvatarURL({ size: 128 })) + .setDescription( + `**${achievementStats.unlocked}/${achievementStats.total}** logros desbloqueados ` + + `(${achievementStats.percentage}%)\n` + + `⭐ **${achievementStats.points}** puntos totales` + ); + + // Logros desbloqueados recientes (últimos 5) + if (unlocked.length > 0) { + const recentUnlocked = unlocked.slice(0, 5); + let unlockedText = ''; + + for (const pa of recentUnlocked) { + const icon = pa.achievement.icon || '🏆'; + const points = pa.achievement.points || 10; + unlockedText += `${icon} **${pa.achievement.name}** (+${points} pts)\n`; + unlockedText += `└ ${pa.achievement.description}\n`; + } + + embed.addFields({ + name: `✅ Desbloqueados Recientes (${unlocked.length})`, + value: unlockedText || 'Ninguno aún', + inline: false + }); + } + + // Logros en progreso (top 5) + if (inProgress.length > 0) { + const topInProgress = inProgress.slice(0, 5); + let progressText = ''; + + for (const pa of topInProgress) { + const icon = pa.achievement.icon || '🔒'; + const req = pa.achievement.requirements as any; + const progress = pa.progress; + const required = req.value; + const bar = createProgressBar(progress, required, 8); + + progressText += `${icon} **${pa.achievement.name}**\n`; + progressText += `└ ${bar} (${progress}/${required})\n`; + } + + embed.addFields({ + name: `📈 En Progreso (${inProgress.length})`, + value: progressText, + inline: false + }); + } + + // Categorías + const categories = ['mining', 'fishing', 'combat', 'economy', 'exploration']; + const categoryEmojis: Record = { + mining: '⛏️', + fishing: '🎣', + combat: '⚔️', + economy: '💰', + exploration: '🗺️' + }; + + let categoryText = ''; + for (const cat of categories) { + const count = unlocked.filter(pa => pa.achievement.category === cat).length; + if (count > 0) { + categoryText += `${categoryEmojis[cat]} ${count} `; + } + } + + if (categoryText) { + embed.addFields({ + name: '📊 Por Categoría', + value: categoryText, + inline: false + }); + } + + if (unlocked.length === 0 && inProgress.length === 0) { + embed.setDescription( + 'Aún no has desbloqueado ningún logro.\n' + + '¡Empieza a jugar para obtener logros y puntos!' + ); + } + + embed.setFooter({ text: 'Los logros se desbloquean automáticamente al cumplir requisitos' }); + embed.setTimestamp(); + + await message.reply({ embeds: [embed] }); + } catch (error) { + console.error('Error en comando logros:', error); + await message.reply('❌ Error al obtener los logros.'); + } + } +}; diff --git a/src/commands/messages/game/mina.ts b/src/commands/messages/game/mina.ts index 964b194..c9400e8 100644 --- a/src/commands/messages/game/mina.ts +++ b/src/commands/messages/game/mina.ts @@ -2,6 +2,9 @@ import type { CommandMessage } from '../../../core/types/commands'; import type Amayo from '../../../core/client'; import { runMinigame } from '../../../game/minigames/service'; import { resolveArea, getDefaultLevel, findBestToolKey } from './_helpers'; +import { updateStats } from '../../../game/stats/service'; +import { updateQuestProgress } from '../../../game/quests/service'; +import { checkAchievements } from '../../../game/achievements/service'; export const command: CommandMessage = { name: 'mina', @@ -26,13 +29,34 @@ export const command: CommandMessage = { try { const result = await runMinigame(userId, guildId, areaKey, level, { toolKey: toolKey ?? undefined }); + + // Actualizar stats + await updateStats(userId, guildId, { minesCompleted: 1 }); + + // Actualizar progreso de misiones + await updateQuestProgress(userId, guildId, 'mine_count', 1); + + // Verificar logros + const newAchievements = await checkAchievements(userId, guildId, 'mine_count'); + const rewards = result.rewards.map(r => r.type === 'coins' ? `🪙 +${r.amount}` : `📦 ${r.itemKey} x${r.qty}`).join(' · ') || '—'; const mobs = result.mobs.length ? result.mobs.join(', ') : '—'; const toolInfo = result.tool?.key ? `🔧 ${result.tool.key}${result.tool.broken ? ' (rota)' : ` (-${result.tool.durabilityDelta} dur.)`}` : '—'; - await message.reply(`⛏️ Mina (nivel ${level}) + + let response = `⛏️ Mina (nivel ${level}) Recompensas: ${rewards} Mobs: ${mobs} -Herramienta: ${toolInfo}`); +Herramienta: ${toolInfo}`; + + // Notificar logros desbloqueados + if (newAchievements.length > 0) { + response += `\n\n🏆 ¡Logro desbloqueado!`; + for (const ach of newAchievements) { + response += `\n✨ **${ach.name}** - ${ach.description}`; + } + } + + await message.reply(response); } catch (e: any) { await message.reply(`❌ No se pudo minar: ${e?.message ?? e}`); } diff --git a/src/commands/messages/game/misionReclamar.ts b/src/commands/messages/game/misionReclamar.ts new file mode 100644 index 0000000..736d2b7 --- /dev/null +++ b/src/commands/messages/game/misionReclamar.ts @@ -0,0 +1,73 @@ +import type { CommandMessage } from '../../../core/types/commands'; +import type Amayo from '../../../core/client'; +import { claimQuestReward, getPlayerQuests } from '../../../game/quests/service'; +import { EmbedBuilder } from 'discord.js'; + +export const command: CommandMessage = { + name: 'mision-reclamar', + type: 'message', + aliases: ['claim-quest', 'reclamar-mision'], + cooldown: 3, + description: 'Reclamar recompensa de misión completada', + usage: 'mision-reclamar ', + run: async (message, args, client: Amayo) => { + try { + const userId = message.author.id; + const guildId = message.guild!.id; + + if (!args[0]) { + await message.reply(`❌ Uso: \`!mision-reclamar \`\nEjemplo: \`!mision-reclamar 1\``); + return; + } + + // Obtener misiones completadas + const quests = await getPlayerQuests(userId, guildId); + const allQuests = [...quests.daily, ...quests.weekly, ...quests.permanent, ...quests.event]; + const claimable = allQuests.filter(q => q.canClaim); + + if (claimable.length === 0) { + await message.reply('❌ No tienes misiones listas para reclamar. Completa misiones primero usando los comandos del bot.'); + return; + } + + const index = parseInt(args[0]) - 1; + if (isNaN(index) || index < 0 || index >= claimable.length) { + await message.reply(`❌ Número de misión inválido. Elige un número entre 1 y ${claimable.length}.`); + return; + } + + const selected = claimable[index]; + + // Reclamar recompensa + const { quest, rewards } = await claimQuestReward(userId, guildId, selected.quest.id); + + const embed = new EmbedBuilder() + .setColor(0x00FF00) + .setTitle('🎉 ¡Misión Completada!') + .setDescription(`Has reclamado las recompensas de **${quest.name}**`) + .setThumbnail(message.author.displayAvatarURL({ size: 128 })); + + // Mostrar recompensas + if (rewards.length > 0) { + embed.addFields({ + name: '🎁 Recompensas Recibidas', + value: rewards.join('\n'), + inline: false + }); + } + + // Info de la misión + embed.addFields( + { name: '📜 Misión', value: quest.description, inline: false } + ); + + embed.setFooter({ text: `Usa !misiones para ver más misiones` }); + embed.setTimestamp(); + + await message.reply({ embeds: [embed] }); + } catch (error: any) { + console.error('Error en comando mision-reclamar:', error); + await message.reply(`❌ ${error.message || 'Error al reclamar la misión.'}`); + } + } +}; diff --git a/src/commands/messages/game/misiones.ts b/src/commands/messages/game/misiones.ts new file mode 100644 index 0000000..4feb7f9 --- /dev/null +++ b/src/commands/messages/game/misiones.ts @@ -0,0 +1,157 @@ +import type { CommandMessage } from '../../../core/types/commands'; +import type Amayo from '../../../core/client'; +import { getPlayerQuests, claimQuestReward } from '../../../game/quests/service'; +import { createProgressBar } from '../../../game/achievements/service'; +import { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; + +export const command: CommandMessage = { + name: 'misiones', + type: 'message', + aliases: ['quests', 'mision', 'quest'], + cooldown: 5, + description: 'Ver misiones disponibles y tu progreso', + usage: 'misiones [categoria]', + run: async (message, args, client: Amayo) => { + try { + const userId = message.author.id; + const guildId = message.guild!.id; + + // Obtener misiones con progreso + const quests = await getPlayerQuests(userId, guildId); + + const embed = new EmbedBuilder() + .setColor(0x5865F2) + .setTitle('📜 Misiones Disponibles') + .setDescription(`${message.author.username}, aquí están tus misiones:`) + .setThumbnail(message.author.displayAvatarURL({ size: 128 })); + + // Emojis por categoría + const categoryEmojis: Record = { + mining: '⛏️', + fishing: '🎣', + combat: '⚔️', + economy: '💰', + exploration: '🗺️', + crafting: '🛠️' + }; + + // Función para formatear una lista de misiones + const formatQuests = (questList: any[], type: string) => { + if (questList.length === 0) return null; + + let text = ''; + for (const { quest, progress, canClaim, percentage } of questList) { + const icon = categoryEmojis[quest.category] || '📋'; + const req = quest.requirements as any; + const currentProgress = progress?.progress || 0; + const required = req.count; + + // Estado + let status = ''; + if (canClaim) { + status = '✅ ¡Listo para reclamar!'; + } else if (progress?.completed) { + status = '🎁 Completada'; + } else { + const bar = createProgressBar(currentProgress, required, 8); + status = `${bar}`; + } + + text += `${icon} **${quest.name}**\n`; + text += `└ ${quest.description}\n`; + text += `└ ${status}\n`; + + // Recompensas + const rewards = quest.rewards as any; + let rewardStr = ''; + if (rewards.coins) rewardStr += `💰 ${rewards.coins} `; + if (rewards.items && rewards.items.length > 0) { + rewardStr += `📦 ${rewards.items.length} items `; + } + if (rewardStr) { + text += `└ Recompensa: ${rewardStr}\n`; + } + + text += '\n'; + } + + return text; + }; + + // Misiones diarias + if (quests.daily.length > 0) { + const dailyText = formatQuests(quests.daily, 'daily'); + if (dailyText) { + embed.addFields({ + name: '📅 Misiones Diarias', + value: dailyText, + inline: false + }); + } + } + + // Misiones semanales + if (quests.weekly.length > 0) { + const weeklyText = formatQuests(quests.weekly, 'weekly'); + if (weeklyText) { + embed.addFields({ + name: '📆 Misiones Semanales', + value: weeklyText, + inline: false + }); + } + } + + // Misiones permanentes + if (quests.permanent.length > 0) { + const permanentText = formatQuests(quests.permanent.slice(0, 3), 'permanent'); + if (permanentText) { + embed.addFields({ + name: '♾️ Misiones Permanentes', + value: permanentText, + inline: false + }); + } + } + + // Misiones de evento + if (quests.event.length > 0) { + const eventText = formatQuests(quests.event, 'event'); + if (eventText) { + embed.addFields({ + name: '🎉 Misiones de Evento', + value: eventText, + inline: false + }); + } + } + + // Verificar si hay misiones para reclamar + const canClaim = [...quests.daily, ...quests.weekly, ...quests.permanent, ...quests.event] + .filter(q => q.canClaim); + + if (canClaim.length > 0) { + embed.addFields({ + name: '🎁 ¡Misiones Listas!', + value: `Tienes **${canClaim.length}** misiones listas para reclamar.\nUsa \`!mision-reclamar \` para reclamar recompensas.`, + inline: false + }); + } + + if (quests.daily.length === 0 && quests.weekly.length === 0 && quests.permanent.length === 0) { + embed.setDescription( + 'No hay misiones disponibles en este momento.\n' + + 'Las misiones diarias se generan automáticamente cada día.' + ); + } + + embed.setFooter({ text: 'Completa misiones para ganar recompensas' }); + embed.setTimestamp(); + + await message.reply({ embeds: [embed] }); + } catch (error) { + console.error('Error en comando misiones:', error); + await message.reply('❌ Error al obtener las misiones.'); + } + } +}; diff --git a/src/commands/messages/game/pelear.ts b/src/commands/messages/game/pelear.ts index 54ac156..837ec23 100644 --- a/src/commands/messages/game/pelear.ts +++ b/src/commands/messages/game/pelear.ts @@ -2,6 +2,9 @@ import type { CommandMessage } from '../../../core/types/commands'; import type Amayo from '../../../core/client'; import { runMinigame } from '../../../game/minigames/service'; import { resolveArea, getDefaultLevel, findBestToolKey } from './_helpers'; +import { updateStats } from '../../../game/stats/service'; +import { updateQuestProgress } from '../../../game/quests/service'; +import { checkAchievements } from '../../../game/achievements/service'; export const command: CommandMessage = { name: 'pelear', @@ -26,13 +29,37 @@ export const command: CommandMessage = { try { const result = await runMinigame(userId, guildId, areaKey, level, { toolKey: toolKey ?? undefined }); + + // Actualizar stats y misiones + await updateStats(userId, guildId, { fightsCompleted: 1 }); + await updateQuestProgress(userId, guildId, 'fight_count', 1); + + // Contar mobs derrotados + const mobsCount = result.mobs.length; + if (mobsCount > 0) { + await updateStats(userId, guildId, { mobsDefeated: mobsCount }); + await updateQuestProgress(userId, guildId, 'mob_defeat_count', mobsCount); + } + + const newAchievements = await checkAchievements(userId, guildId, 'fight_count'); + const rewards = result.rewards.map(r => r.type === 'coins' ? `🪙 +${r.amount}` : `🎁 ${r.itemKey} x${r.qty}`).join(' · ') || '—'; const mobs = result.mobs.length ? result.mobs.join(', ') : '—'; const toolInfo = result.tool?.key ? `🗡️ ${result.tool.key}${result.tool.broken ? ' (rota)' : ` (-${result.tool.durabilityDelta} dur.)`}` : '—'; - await message.reply(`⚔️ Arena (nivel ${level}) + + let response = `⚔️ Arena (nivel ${level}) Recompensas: ${rewards} Enemigos: ${mobs} -Arma: ${toolInfo}`); +Arma: ${toolInfo}`; + + if (newAchievements.length > 0) { + response += `\n\n🏆 ¡Logro desbloqueado!`; + for (const ach of newAchievements) { + response += `\n✨ **${ach.name}** - ${ach.description}`; + } + } + + await message.reply(response); } catch (e: any) { await message.reply(`❌ No se pudo pelear: ${e?.message ?? e}`); } diff --git a/src/commands/messages/game/pescar.ts b/src/commands/messages/game/pescar.ts index 7e56130..2290bc0 100644 --- a/src/commands/messages/game/pescar.ts +++ b/src/commands/messages/game/pescar.ts @@ -2,6 +2,9 @@ import type { CommandMessage } from '../../../core/types/commands'; import type Amayo from '../../../core/client'; import { runMinigame } from '../../../game/minigames/service'; import { resolveArea, getDefaultLevel, findBestToolKey } from './_helpers'; +import { updateStats } from '../../../game/stats/service'; +import { updateQuestProgress } from '../../../game/quests/service'; +import { checkAchievements } from '../../../game/achievements/service'; export const command: CommandMessage = { name: 'pescar', @@ -26,13 +29,29 @@ export const command: CommandMessage = { try { const result = await runMinigame(userId, guildId, areaKey, level, { toolKey: toolKey ?? undefined }); + + // Actualizar stats y misiones + await updateStats(userId, guildId, { fishingCompleted: 1 }); + await updateQuestProgress(userId, guildId, 'fish_count', 1); + const newAchievements = await checkAchievements(userId, guildId, 'fish_count'); + const rewards = result.rewards.map(r => r.type === 'coins' ? `🪙 +${r.amount}` : `🐟 ${r.itemKey} x${r.qty}`).join(' · ') || '—'; const mobs = result.mobs.length ? result.mobs.join(', ') : '—'; const toolInfo = result.tool?.key ? `🎣 ${result.tool.key}${result.tool.broken ? ' (rota)' : ` (-${result.tool.durabilityDelta} dur.)`}` : '—'; - await message.reply(`🎣 Pesca (nivel ${level}) + + let response = `🎣 Pesca (nivel ${level}) Recompensas: ${rewards} Mobs: ${mobs} -Herramienta: ${toolInfo}`); +Herramienta: ${toolInfo}`; + + if (newAchievements.length > 0) { + response += `\n\n🏆 ¡Logro desbloqueado!`; + for (const ach of newAchievements) { + response += `\n✨ **${ach.name}** - ${ach.description}`; + } + } + + await message.reply(response); } catch (e: any) { await message.reply(`❌ No se pudo pescar: ${e?.message ?? e}`); } diff --git a/src/commands/messages/game/stats.ts b/src/commands/messages/game/stats.ts index 5ce0271..f1d866c 100644 --- a/src/commands/messages/game/stats.ts +++ b/src/commands/messages/game/stats.ts @@ -66,7 +66,7 @@ export const command: CommandMessage = { embed.addFields({ name: '🏆 Récords', value: recordsText || 'Sin datos', inline: true }); } - embed.setFooter({ text: `Usa ${client.prefix}ranking-stats para ver el ranking global` }); + embed.setFooter({ text: 'Usa !ranking-stats para ver el ranking global' }); await message.reply({ embeds: [embed] }); } catch (error) { diff --git a/src/game/achievements/seed.ts b/src/game/achievements/seed.ts new file mode 100644 index 0000000..4a95666 --- /dev/null +++ b/src/game/achievements/seed.ts @@ -0,0 +1,221 @@ +import { prisma } from '../../core/database/prisma'; +import logger from '../../core/lib/logger'; + +/** + * Seed de logros base + */ +export async function seedAchievements(guildId: string | null = null) { + const achievements = [ + // Minería + { + key: 'first_mine', + name: '⛏️ Primera Mina', + description: 'Mina por primera vez', + category: 'mining', + requirements: { type: 'mine_count', value: 1 }, + rewards: { coins: 100 }, + hidden: false, + points: 10 + }, + { + key: 'miner_novice', + name: '⛏️ Minero Novato', + description: 'Mina 10 veces', + category: 'mining', + requirements: { type: 'mine_count', value: 10 }, + rewards: { coins: 500 }, + hidden: false, + points: 20 + }, + { + key: 'miner_expert', + name: '⛏️ Minero Experto', + description: 'Mina 50 veces', + category: 'mining', + requirements: { type: 'mine_count', value: 50 }, + rewards: { coins: 2500 }, + hidden: false, + points: 50 + }, + { + key: 'miner_master', + name: '⛏️ Maestro Minero', + description: 'Mina 100 veces', + category: 'mining', + requirements: { type: 'mine_count', value: 100 }, + rewards: { coins: 10000 }, + hidden: false, + points: 100 + }, + + // Pesca + { + key: 'first_fish', + name: '🎣 Primera Pesca', + description: 'Pesca por primera vez', + category: 'fishing', + requirements: { type: 'fish_count', value: 1 }, + rewards: { coins: 100 }, + hidden: false, + points: 10 + }, + { + key: 'fisher_novice', + name: '🎣 Pescador Novato', + description: 'Pesca 10 veces', + category: 'fishing', + requirements: { type: 'fish_count', value: 10 }, + rewards: { coins: 500 }, + hidden: false, + points: 20 + }, + { + key: 'fisher_expert', + name: '🎣 Pescador Experto', + description: 'Pesca 50 veces', + category: 'fishing', + requirements: { type: 'fish_count', value: 50 }, + rewards: { coins: 2500 }, + hidden: false, + points: 50 + }, + + // Combate + { + key: 'first_fight', + name: '⚔️ Primera Pelea', + description: 'Pelea por primera vez', + category: 'combat', + requirements: { type: 'fight_count', value: 1 }, + rewards: { coins: 150 }, + hidden: false, + points: 10 + }, + { + key: 'warrior_novice', + name: '⚔️ Guerrero Novato', + description: 'Pelea 10 veces', + category: 'combat', + requirements: { type: 'fight_count', value: 10 }, + rewards: { coins: 750 }, + hidden: false, + points: 20 + }, + { + key: 'mob_hunter', + name: '👾 Cazador de Monstruos', + description: 'Derrota 50 mobs', + category: 'combat', + requirements: { type: 'mob_defeat_count', value: 50 }, + rewards: { coins: 3000 }, + hidden: false, + points: 50 + }, + { + key: 'mob_slayer', + name: '👾 Asesino de Monstruos', + description: 'Derrota 200 mobs', + category: 'combat', + requirements: { type: 'mob_defeat_count', value: 200 }, + rewards: { coins: 15000 }, + hidden: false, + points: 100 + }, + + // Economía + { + key: 'first_coins', + name: '💰 Primeras Monedas', + description: 'Gana 1,000 monedas en total', + category: 'economy', + requirements: { type: 'coins_earned', value: 1000 }, + rewards: { coins: 200 }, + hidden: false, + points: 10 + }, + { + key: 'wealthy', + name: '💰 Acaudalado', + description: 'Gana 10,000 monedas en total', + category: 'economy', + requirements: { type: 'coins_earned', value: 10000 }, + rewards: { coins: 2000 }, + hidden: false, + points: 30 + }, + { + key: 'millionaire', + name: '💰 Millonario', + description: 'Gana 100,000 monedas en total', + category: 'economy', + requirements: { type: 'coins_earned', value: 100000 }, + rewards: { coins: 25000 }, + hidden: false, + points: 100 + }, + + // Crafteo + { + key: 'first_craft', + name: '🛠️ Primer Crafteo', + description: 'Craftea tu primer item', + category: 'crafting', + requirements: { type: 'craft_count', value: 1 }, + rewards: { coins: 100 }, + hidden: false, + points: 10 + }, + { + key: 'crafter_expert', + name: '🛠️ Artesano Experto', + description: 'Craftea 50 items', + category: 'crafting', + requirements: { type: 'craft_count', value: 50 }, + rewards: { coins: 5000 }, + hidden: false, + points: 50 + }, + { + key: 'master_crafter', + name: '🛠️ Maestro Artesano', + description: 'Craftea 200 items', + category: 'crafting', + requirements: { type: 'craft_count', value: 200 }, + rewards: { coins: 20000 }, + hidden: false, + points: 100 + } + ]; + + let created = 0; + for (const ach of achievements) { + const existing = await prisma.achievement.findUnique({ + where: { guildId_key: { guildId: guildId || '', key: ach.key } } + }); + + if (!existing) { + await prisma.achievement.create({ + data: { ...ach, guildId: guildId || undefined } + }); + created++; + } + } + + console.log(`Seeded ${created} achievements for guild ${guildId || 'global'}`); + return created; +} + +/** + * Ejecutar si es llamado directamente + */ +if (require.main === module) { + seedAchievements(null) + .then(count => { + console.log(`✅ ${count} achievements seeded`); + process.exit(0); + }) + .catch(error => { + console.error('❌ Error seeding achievements:', error); + process.exit(1); + }); +} diff --git a/src/game/achievements/service.ts b/src/game/achievements/service.ts index 728b291..b49895b 100644 --- a/src/game/achievements/service.ts +++ b/src/game/achievements/service.ts @@ -26,7 +26,7 @@ export async function checkAchievements( } }); - const newUnlocks = []; + const newUnlocks: any[] = []; const stats = await getOrCreatePlayerStats(userId, guildId); for (const achievement of achievements) { @@ -118,7 +118,7 @@ export async function checkAchievements( return newUnlocks; } catch (error) { - logger.error(`Error checking achievements for ${userId}:`, error); + console.error(`Error checking achievements for ${userId}:`, error); return []; } } diff --git a/src/game/quests/service.ts b/src/game/quests/service.ts index 285eb1b..cdb6f44 100644 --- a/src/game/quests/service.ts +++ b/src/game/quests/service.ts @@ -16,15 +16,11 @@ export async function updateQuestProgress( const quests = await prisma.quest.findMany({ where: { OR: [{ guildId }, { guildId: null }], - active: true, - OR: [ - { endAt: null }, - { endAt: { gte: new Date() } } - ] + active: true } }); - const updates = []; + const updates: any[] = []; for (const quest of quests) { const req = quest.requirements as any; @@ -94,7 +90,7 @@ export async function updateQuestProgress( return updates; } catch (error) { - logger.error(`Error updating quest progress for ${userId}:`, error); + console.error(`Error updating quest progress for ${userId}:`, error); return []; } } @@ -144,7 +140,7 @@ export async function claimQuestReward( return { quest: progress.quest, rewards }; } catch (error) { - logger.error(`Error claiming quest reward for ${userId}:`, error); + console.error(`Error claiming quest reward for ${userId}:`, error); throw error; } } @@ -156,11 +152,7 @@ export async function getPlayerQuests(userId: string, guildId: string) { const quests = await prisma.quest.findMany({ where: { OR: [{ guildId }, { guildId: null }], - active: true, - OR: [ - { endAt: null }, - { endAt: { gte: new Date() } } - ] + active: true }, orderBy: [ { type: 'asc' }, @@ -272,10 +264,10 @@ export async function generateDailyQuests(guildId: string) { }); } - logger.info(`Generated ${selectedTemplates.length} daily quests for guild ${guildId}`); + console.log(`Generated ${selectedTemplates.length} daily quests for guild ${guildId}`); return selectedTemplates.length; } catch (error) { - logger.error(`Error generating daily quests for ${guildId}:`, error); + console.error(`Error generating daily quests for ${guildId}:`, error); return 0; } } @@ -295,6 +287,6 @@ export async function cleanExpiredQuests(guildId: string) { } }); - logger.info(`Deactivated ${result.count} expired quests for guild ${guildId}`); + console.log(`Deactivated ${result.count} expired quests for guild ${guildId}`); return result.count; } diff --git a/src/game/rewards/service.ts b/src/game/rewards/service.ts index 4286258..313718e 100644 --- a/src/game/rewards/service.ts +++ b/src/game/rewards/service.ts @@ -52,15 +52,15 @@ export async function giveRewards( guildId, action: 'reward_given', target: source, - details: rewards + details: rewards as any } }).catch(() => {}); // Silencioso si falla el log - logger.info(`Rewards given to ${userId} in ${guildId} from ${source}:`, rewards); + console.log(`Rewards given to ${userId} in ${guildId} from ${source}:`, rewards); return results; } catch (error) { - logger.error(`Error giving rewards to ${userId} in ${guildId}:`, error); + console.error(`Error giving rewards to ${userId} in ${guildId}:`, error); throw error; } } diff --git a/src/game/stats/service.ts b/src/game/stats/service.ts index 1e2b9bf..9b21d8c 100644 --- a/src/game/stats/service.ts +++ b/src/game/stats/service.ts @@ -69,7 +69,7 @@ export async function updateStats( return stats; } catch (error) { - logger.error(`Error updating stats for ${userId} in ${guildId}:`, error); + console.error(`Error updating stats for ${userId} in ${guildId}:`, error); throw error; } } diff --git a/src/game/streaks/service.ts b/src/game/streaks/service.ts index 27d9830..bb05222 100644 --- a/src/game/streaks/service.ts +++ b/src/game/streaks/service.ts @@ -85,7 +85,7 @@ export async function updateStreak(userId: string, guildId: string) { daysIncreased: daysDiff === 1 }; } catch (error) { - logger.error(`Error updating streak for ${userId}:`, error); + console.error(`Error updating streak for ${userId}:`, error); throw error; } }