feat(economy): enhance minigame commands to track stats, quest progress, and achievements
This commit is contained in:
109
src/commands/messages/game/cooldowns.ts
Normal file
109
src/commands/messages/game/cooldowns.ts
Normal file
@@ -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<string, string> = {
|
||||
'mine': '⛏️',
|
||||
'fish': '🎣',
|
||||
'fight': '⚔️',
|
||||
'farm': '🌾',
|
||||
'craft': '🛠️',
|
||||
'smelt': '🔥',
|
||||
'shop': '🛒',
|
||||
'daily': '🎁',
|
||||
'consume': '🍖'
|
||||
};
|
||||
|
||||
// Traducción de acciones
|
||||
const actionNames: Record<string, string> = {
|
||||
'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.');
|
||||
}
|
||||
}
|
||||
};
|
||||
117
src/commands/messages/game/logros.ts
Normal file
117
src/commands/messages/game/logros.ts
Normal file
@@ -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<string, string> = {
|
||||
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.');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
73
src/commands/messages/game/misionReclamar.ts
Normal file
73
src/commands/messages/game/misionReclamar.ts
Normal file
@@ -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 <numero>',
|
||||
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 <numero>\`\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.'}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
157
src/commands/messages/game/misiones.ts
Normal file
157
src/commands/messages/game/misiones.ts
Normal file
@@ -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<string, string> = {
|
||||
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 <id>\` 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.');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
221
src/game/achievements/seed.ts
Normal file
221
src/game/achievements/seed.ts
Normal file
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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 [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user