/** * Fonctions utilitaires pour le système XP */ const db = require('../database/db.js'); // Formule de calcul de l'XP nécessaire pour atteindre un niveau // XP total nécessaire = 5 * (level^2) + (50 * level) + 100 // Exemples : // - Niveau 1 : 5 * 1^2 + 50 * 1 + 100 = 155 XP total // - Niveau 2 : 5 * 2^2 + 50 * 2 + 100 = 220 XP total // - Niveau 3 : 5 * 3^2 + 50 * 3 + 100 = 295 XP total function getXPForLevel(level) { if (level <= 0) return 0; return Math.floor(5 * Math.pow(level, 2) + 50 * level + 100); } // Calculer le niveau à partir de l'XP total function getLevelFromXP(xp) { if (xp < 0) return 0; let level = 0; let requiredXP = getXPForLevel(level + 1); // Trouver le niveau en vérifiant l'XP nécessaire while (xp >= requiredXP) { level++; requiredXP = getXPForLevel(level + 1); } return level; } // Calculer l'XP nécessaire pour le prochain niveau function getXPForNextLevel(level) { return getXPForLevel(level + 1); } // Calculer l'XP progress dans le niveau actuel function getXPProgress(xp, level) { // XP nécessaire pour atteindre le niveau actuel const xpForCurrentLevel = getXPForLevel(level); // XP nécessaire pour atteindre le prochain niveau const xpForNextLevel = getXPForLevel(level + 1); // XP dans le niveau actuel (différence entre l'XP total et l'XP du niveau actuel) const xpInLevel = Math.max(0, xp - xpForCurrentLevel); // XP nécessaire pour passer au niveau suivant const xpNeeded = xpForNextLevel - xpForCurrentLevel; // Éviter la division par zéro if (xpNeeded <= 0) { return { current: xpInLevel, needed: 100, percentage: 0 }; } const percentage = Math.max(0, Math.min(100, Math.floor((xpInLevel / xpNeeded) * 100))); return { current: xpInLevel, needed: xpNeeded, percentage: percentage }; } // Obtenir ou créer un utilisateur dans la table XP async function getUserXP(userId, guildId) { try { const [rows] = await db.query( 'SELECT * FROM user_xp WHERE userId = ? AND guildId = ?', [userId, guildId] ); if (rows.length === 0) { // Créer l'utilisateur avec 0 XP await db.query( 'INSERT INTO user_xp (userId, guildId, xp, level) VALUES (?, ?, 0, 0)', [userId, guildId] ); return { userId, guildId, xp: 0, level: 0, lastMessageTime: 0, totalMessages: 0, totalVoiceTime: 0, lastVoiceJoin: 0, lastBumpTime: 0 }; } return rows[0]; } catch (err) { console.error('Erreur lors de la récupération de l\'XP:', err); return null; } } // Ajouter de l'XP à un utilisateur async function addXP(userId, guildId, xpGained, source = 'message', multiplier = 1.0) { try { const userXP = await getUserXP(userId, guildId); if (!userXP) return null; const finalXP = Math.floor(xpGained * multiplier); const newXP = userXP.xp + finalXP; const newLevel = getLevelFromXP(newXP); // Mettre à jour l'XP et le niveau await db.query( 'UPDATE user_xp SET xp = ?, level = ? WHERE userId = ? AND guildId = ?', [newXP, newLevel, userId, guildId] ); // Logger le gain d'XP (optionnel, pour debugging) try { await db.query( 'INSERT INTO xp_logs (userId, guildId, xpGained, source, multiplier, timestamp) VALUES (?, ?, ?, ?, ?, ?)', [userId, guildId, finalXP, source, multiplier, Date.now()] ); } catch (logErr) { // Ignorer les erreurs de log } return { oldXP: userXP.xp, newXP, oldLevel: userXP.level, newLevel, xpGained: finalXP, levelUp: newLevel > userXP.level }; } catch (err) { console.error('Erreur lors de l\'ajout d\'XP:', err); return null; } } // Vérifier si un salon est exclus de l'XP async function isChannelExcluded(channelId, guildId) { try { const [rows] = await db.query( 'SELECT * FROM xp_excluded_channels WHERE channelId = ? AND guildId = ?', [channelId, guildId] ); return rows.length > 0; } catch (err) { console.error('Erreur lors de la vérification de l\'exclusion:', err); return false; } } // Obtenir le multiplicateur XP d'un utilisateur (booster, vocal, etc.) function getXPMultiplier(member, source = 'message') { let multiplier = 1.0; // Vérifier si l'utilisateur est booster (1.2x) if (member.premiumSince) { multiplier *= 1.2; } // Multiplicateur vocal : 1.0x après 5 minutes de vocal (selon Issue #11) // Note: Cette fonction est appelée toutes les 10 minutes, donc on applique déjà le bonus if (source === 'voice') { // Le bonus vocal est déjà appliqué car on gagne de l'XP toutes les 10 minutes // Donc on considère qu'on a déjà passé plus de 5 minutes multiplier *= 1.0; // Pas de changement, mais on pourrait ajouter un bonus ici } return multiplier; } // Obtenir le classement des utilisateurs par XP async function getLeaderboard(guildId, limit = 10) { try { const [rows] = await db.query( 'SELECT * FROM user_xp WHERE guildId = ? ORDER BY xp DESC LIMIT ?', [guildId, limit] ); return rows; } catch (err) { console.error('Erreur lors de la récupération du classement:', err); return []; } } // Obtenir la position d'un utilisateur dans le classement async function getUserRank(userId, guildId) { try { const userXP = await getUserXP(userId, guildId); if (!userXP) return 0; const [rows] = await db.query( 'SELECT COUNT(*) as count FROM user_xp WHERE guildId = ? AND xp > ?', [guildId, userXP.xp] ); return (rows[0]?.count || 0) + 1; } catch (err) { console.error('Erreur lors de la récupération du rang:', err); return 0; } } module.exports = { getXPForLevel, getLevelFromXP, getXPForNextLevel, getXPProgress, getUserXP, addXP, isChannelExcluded, getXPMultiplier, getLeaderboard, getUserRank, };