diff --git a/app/commands/crime.js b/app/commands/crime.js index 597b0a9..568f9d6 100644 --- a/app/commands/crime.js +++ b/app/commands/crime.js @@ -34,11 +34,11 @@ module.exports = addCommand({ guildCondition: async (guildId) => { return new Promise((resolve) => { db.get( - "SELECT enabled FROM economy_config WHERE guild_id = ?", + "SELECT enabled, crime_enabled FROM economy_config WHERE guild_id = ?", [guildId], (err, row) => { if (err) return resolve(false); - resolve(!!row?.enabled); + resolve(!!row?.enabled && !!row?.crime_enabled); } ); }); diff --git a/app/commands/daily.js b/app/commands/daily.js index 64cad7b..5c1deb1 100644 --- a/app/commands/daily.js +++ b/app/commands/daily.js @@ -14,11 +14,11 @@ module.exports = addCommand({ guildCondition: async (guildId) => { return new Promise((resolve) => { db.get( - "SELECT enabled FROM economy_config WHERE guild_id = ?", + "SELECT enabled, daily_enabled FROM economy_config WHERE guild_id = ?", [guildId], (err, row) => { if (err) return resolve(false); - resolve(!!row?.enabled); + resolve(!!row?.enabled && !!row?.daily_enabled); } ); }); diff --git a/app/commands/steal.js b/app/commands/steal.js new file mode 100644 index 0000000..c626ad1 --- /dev/null +++ b/app/commands/steal.js @@ -0,0 +1,214 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder } = require("discord.js"); +const db = require("../db"); + +const stealSuccessMessages = [ + "Vous avez subtilement volé", + "Vous avez pickpocketé avec succès", + "Vous avez dérobé discrètement", + "Vous avez chapardé habilement", + "Main dans la poche, vous avez pris" +]; + +const stealFailMessages = [ + "Vous vous êtes fait attraper la main dans le sac !", + "La victime vous a surpris et a appelé la sécurité !", + "Un passant a crié au voleur !", + "Vous avez trébuché en tentant de fuir !", + "La victime connaissait vos intentions !" +]; + +module.exports = addCommand({ + name: "steal", + description: "Tentez de voler de l'argent à un autre utilisateur.", + aliases: ["voler", "pickpocket"], + permissions: [], + botOwnerOnly: false, + dm: false, + scope: "guild", + + guildCondition: async (guildId) => { + return new Promise((resolve) => { + db.get( + "SELECT enabled, steal_enabled FROM economy_config WHERE guild_id = ?", + [guildId], + (err, row) => { + if (err) return resolve(false); + resolve(!!row?.enabled && !!row?.steal_enabled); + } + ); + }); + }, + + slashOptions: [ + { + type: "USER", + name: "cible", + description: "L'utilisateur à voler.", + required: true, + }, + ], + + executePrefix: async (client, message, args) => { + const targetUser = message.mentions.users.first(); + if (!targetUser) return message.reply("Veuillez mentionner un utilisateur à voler."); + + await doSteal(message.guild.id, message.author, targetUser, (embed) => { + message.reply({ embeds: [embed] }); + }, (errMsg) => { + message.reply(errMsg); + }); + }, + + executeSlash: async (client, interaction) => { + const targetUser = interaction.options.getUser("cible"); + + await doSteal(interaction.guild.id, interaction.user, targetUser, (embed) => { + interaction.reply({ embeds: [embed] }); + }, (errMsg) => { + interaction.reply({ content: errMsg, ephemeral: true }); + }); + }, +}); + +async function doSteal(guildId, thief, victim, onSuccess, onError) { + if (thief.id === victim.id) { + return onError("Vous ne pouvez pas vous voler vous-même !"); + } + + if (victim.bot) { + return onError("Vous ne pouvez pas voler un bot !"); + } + + db.get( + `SELECT currency_name, currency_symbol, steal_success_rate, steal_max_percent, + steal_fine_percent, steal_cooldown_minutes + FROM economy_config WHERE guild_id = ?`, + [guildId], + (err, config) => { + if (err || !config) { + return onError("Le système d'économie n'est pas configuré."); + } + + db.get( + "SELECT balance, last_steal_timestamp FROM user_economy WHERE guild_id = ? AND user_id = ?", + [guildId, thief.id], + (err, thiefRow) => { + if (err) return onError("Erreur lors de la récupération des données."); + + const now = Date.now(); + const cooldownMs = config.steal_cooldown_minutes * 60 * 1000; + const lastSteal = thiefRow?.last_steal_timestamp || 0; + + if (now - lastSteal < cooldownMs) { + const timeLeft = cooldownMs - (now - lastSteal); + const minutes = Math.floor(timeLeft / (60 * 1000)); + const seconds = Math.floor((timeLeft % (60 * 1000)) / 1000); + return onError(`⏰ Vous devez attendre encore **${minutes}m ${seconds}s** avant de pouvoir voler quelqu'un.`); + } + + // Get victim's wallet balance (not bank!) + db.get( + "SELECT balance FROM user_economy WHERE guild_id = ? AND user_id = ?", + [guildId, victim.id], + (err, victimRow) => { + if (err) return onError("Erreur lors de la récupération des données."); + + const victimBalance = victimRow?.balance || 0; + + if (victimBalance <= 0) { + return onError(`**${victim.username}** n'a pas d'argent dans son portefeuille à voler ! (L'argent en banque est protégé)`); + } + + const thiefBalance = thiefRow?.balance || 0; + const success = Math.random() * 100 < config.steal_success_rate; + + let embed; + + if (success) { + // Calculate stolen amount (random % of victim's wallet, up to max_percent) + const maxStealPercent = config.steal_max_percent / 100; + const stealPercent = Math.random() * maxStealPercent; + const stolenAmount = Math.max(1, Math.floor(victimBalance * stealPercent)); + + const newThiefBalance = thiefBalance + stolenAmount; + const newVictimBalance = victimBalance - stolenAmount; + + const stealMsg = stealSuccessMessages[Math.floor(Math.random() * stealSuccessMessages.length)]; + + // Update thief + db.run( + `INSERT INTO user_economy (guild_id, user_id, balance, bank, last_steal_timestamp) + VALUES (?, ?, ?, 0, ?) + ON CONFLICT(guild_id, user_id) DO UPDATE SET + balance = ?, + last_steal_timestamp = ?`, + [guildId, thief.id, newThiefBalance, now, newThiefBalance, now], + (err) => { + if (err) return onError("Erreur lors de la sauvegarde."); + + // Update victim + db.run( + `UPDATE user_economy SET balance = ? WHERE guild_id = ? AND user_id = ?`, + [newVictimBalance, guildId, victim.id], + (err) => { + if (err) return onError("Erreur lors de la sauvegarde."); + + embed = new EmbedBuilder() + .setTitle(`${config.currency_symbol} Vol réussi !`) + .setColor("#00FF00") + .setDescription(`${stealMsg} **${stolenAmount.toLocaleString()} ${config.currency_name}** à ${victim} !`) + .setTimestamp(); + + onSuccess(embed); + } + ); + } + ); + } else { + // Failed - pay fine to victim + const fine = Math.floor(thiefBalance * (config.steal_fine_percent / 100)); + const newThiefBalance = Math.max(0, thiefBalance - fine); + const newVictimBalance = victimBalance + fine; + + const failMsg = stealFailMessages[Math.floor(Math.random() * stealFailMessages.length)]; + + // Update thief + db.run( + `INSERT INTO user_economy (guild_id, user_id, balance, bank, last_steal_timestamp) + VALUES (?, ?, ?, 0, ?) + ON CONFLICT(guild_id, user_id) DO UPDATE SET + balance = ?, + last_steal_timestamp = ?`, + [guildId, thief.id, newThiefBalance, now, newThiefBalance, now], + (err) => { + if (err) return onError("Erreur lors de la sauvegarde."); + + // Give fine to victim + db.run( + `INSERT INTO user_economy (guild_id, user_id, balance, bank) + VALUES (?, ?, ?, 0) + ON CONFLICT(guild_id, user_id) DO UPDATE SET balance = ?`, + [guildId, victim.id, newVictimBalance, newVictimBalance], + (err) => { + if (err) return onError("Erreur lors de la sauvegarde."); + + embed = new EmbedBuilder() + .setTitle("🚔 Vol échoué !") + .setColor("#FF0000") + .setDescription(`${failMsg}\n\nVous avez dû payer **${fine.toLocaleString()} ${config.currency_name}** à ${victim} en dédommagement.`) + .setTimestamp(); + + onSuccess(embed); + } + ); + } + ); + } + } + ); + } + ); + } + ); +} diff --git a/app/commands/work.js b/app/commands/work.js index 8253796..91558f4 100644 --- a/app/commands/work.js +++ b/app/commands/work.js @@ -29,11 +29,11 @@ module.exports = addCommand({ guildCondition: async (guildId) => { return new Promise((resolve) => { db.get( - "SELECT enabled FROM economy_config WHERE guild_id = ?", + "SELECT enabled, work_enabled FROM economy_config WHERE guild_id = ?", [guildId], (err, row) => { if (err) return resolve(false); - resolve(!!row?.enabled); + resolve(!!row?.enabled && !!row?.work_enabled); } ); }); diff --git a/app/db.js b/app/db.js index d72b739..15c3fda 100644 --- a/app/db.js +++ b/app/db.js @@ -99,16 +99,32 @@ db.exec(` enabled INTEGER NOT NULL DEFAULT 0, currency_name TEXT NOT NULL DEFAULT 'coins', currency_symbol TEXT NOT NULL DEFAULT '💰', + daily_enabled INTEGER NOT NULL DEFAULT 1, daily_amount INTEGER NOT NULL DEFAULT 100, daily_cooldown_hours INTEGER NOT NULL DEFAULT 24, + work_enabled INTEGER NOT NULL DEFAULT 1, work_min_amount INTEGER NOT NULL DEFAULT 50, work_max_amount INTEGER NOT NULL DEFAULT 150, work_cooldown_minutes INTEGER NOT NULL DEFAULT 60, + crime_enabled INTEGER NOT NULL DEFAULT 1, crime_min_amount INTEGER NOT NULL DEFAULT 100, crime_max_amount INTEGER NOT NULL DEFAULT 500, crime_success_rate INTEGER NOT NULL DEFAULT 50, crime_fine_percent INTEGER NOT NULL DEFAULT 30, crime_cooldown_minutes INTEGER NOT NULL DEFAULT 120, + steal_enabled INTEGER NOT NULL DEFAULT 1, + steal_success_rate INTEGER NOT NULL DEFAULT 40, + steal_max_percent INTEGER NOT NULL DEFAULT 50, + steal_fine_percent INTEGER NOT NULL DEFAULT 25, + steal_cooldown_minutes INTEGER NOT NULL DEFAULT 180, + message_money_enabled INTEGER NOT NULL DEFAULT 0, + message_money_min INTEGER NOT NULL DEFAULT 1, + message_money_max INTEGER NOT NULL DEFAULT 5, + message_money_cooldown_seconds INTEGER NOT NULL DEFAULT 60, + voice_money_enabled INTEGER NOT NULL DEFAULT 0, + voice_money_min INTEGER NOT NULL DEFAULT 5, + voice_money_max INTEGER NOT NULL DEFAULT 15, + voice_money_interval_minutes INTEGER NOT NULL DEFAULT 5, starting_balance INTEGER NOT NULL DEFAULT 0 ); @@ -120,6 +136,8 @@ db.exec(` last_daily_timestamp INTEGER, last_work_timestamp INTEGER, last_crime_timestamp INTEGER, + last_steal_timestamp INTEGER, + last_message_money_timestamp INTEGER, PRIMARY KEY (guild_id, user_id) ); diff --git a/app/events/messageCreate.js b/app/events/messageCreate.js index ce7f767..f9727cd 100644 --- a/app/events/messageCreate.js +++ b/app/events/messageCreate.js @@ -7,7 +7,8 @@ module.exports = { if (message.author.bot) return; const guildId = message.guild.id; - + + // ===== XP SYSTEM ===== db.get( `SELECT enabled, @@ -34,28 +35,28 @@ module.exports = { const userRoles = message.member.roles.cache; const requiredRoles = JSON.parse(row.role_with_without_xp || "[]"); if (!requiredRoles.some(roleId => userRoles.has(roleId))) { - return; // User has an excluded role + return; } } else if (row.role_with_without_type === "without") { const userRoles = message.member.roles.cache; const excludedRoles = JSON.parse(row.role_with_without_xp || "[]"); if (excludedRoles.some(roleId => userRoles.has(roleId))) { - return; // User does not have any of the required roles + return; } } else if (row.salon_with_without_type === "with") { const channelId = message.channel.id; const requiredChannels = JSON.parse(row.salon_with_without_xp || "[]"); if (!requiredChannels.includes(channelId)) { - return; // Message not in a required channel + return; } } else if (row.salon_with_without_type === "without") { const channelId = message.channel.id; const excludedChannels = JSON.parse(row.salon_with_without_xp || "[]"); if (excludedChannels.includes(channelId)) { - return; // Message in an excluded channel + return; } } - // Logic to award XP for message goes here + const now = Date.now(); db.get( `SELECT xp, level, last_xp_message_timestamp FROM user_levels WHERE guild_id = ? AND user_id = ?`, @@ -65,7 +66,7 @@ module.exports = { const lastTimestamp = userRow ? userRow.last_xp_message_timestamp || 0 : 0; if (now - lastTimestamp < row.cooldown_xp_message_seconds * 1000) { - return; // Still in cooldown + return; } const minXp = row.gain_xp_message_lower_bound; @@ -83,7 +84,6 @@ module.exports = { newLevel = 1; } - // Level up logic based on xp_courbe_type and multiplier goes here const multiplier = row.multiplier_courbe_for_level; let fonction_courbe; @@ -103,10 +103,8 @@ module.exports = { newLevel += 1; xpForNextLevel = fonction_courbe(newLevel); - // Announce level up if enabled and meets the criteria if (row.level_announcements_enabled && (newLevel % row.level_annoncement_every_level === 0)) { const channel = message.guild.channels.cache.get(row.level_announcements_channel_id); - console.log("Channel for level announcement:", channel); if (channel) { let announcementMsg = row.level_announcements_message; announcementMsg = announcementMsg @@ -132,5 +130,43 @@ module.exports = { ); } ); + + // ===== ECONOMY MESSAGE MONEY ===== + db.get( + `SELECT enabled, message_money_enabled, message_money_min, message_money_max, message_money_cooldown_seconds + FROM economy_config WHERE guild_id = ?`, + [guildId], + (err, ecoRow) => { + if (err || !ecoRow || !ecoRow.enabled || !ecoRow.message_money_enabled) return; + + const now = Date.now(); + db.get( + `SELECT balance, last_message_money_timestamp FROM user_economy WHERE guild_id = ? AND user_id = ?`, + [guildId, message.author.id], + (err, userEcoRow) => { + if (err) return; + + const lastTimestamp = userEcoRow?.last_message_money_timestamp || 0; + if (now - lastTimestamp < ecoRow.message_money_cooldown_seconds * 1000) { + return; // Still in cooldown + } + + const minMoney = ecoRow.message_money_min; + const maxMoney = ecoRow.message_money_max; + const moneyToAdd = Math.floor(Math.random() * (maxMoney - minMoney + 1)) + minMoney; + const newBalance = (userEcoRow?.balance || 0) + moneyToAdd; + + db.run( + `INSERT INTO user_economy (guild_id, user_id, balance, bank, last_message_money_timestamp) + VALUES (?, ?, ?, 0, ?) + ON CONFLICT(guild_id, user_id) DO UPDATE SET + balance = ?, + last_message_money_timestamp = ?`, + [guildId, message.author.id, newBalance, now, newBalance, now] + ); + } + ); + } + ); }, }; diff --git a/app/events/voiceStateUpdate.js b/app/events/voiceStateUpdate.js index 7da298c..146d9a8 100644 --- a/app/events/voiceStateUpdate.js +++ b/app/events/voiceStateUpdate.js @@ -1,13 +1,20 @@ const { Events } = require("discord.js"); const db = require("../db"); +// Store voice join times and intervals for economy +const voiceJoinTimes = new Map(); // guildId_oderId -> timestamp +const voiceMoneyIntervals = new Map(); // guildId_userId -> intervalId + module.exports = { name: Events.VoiceStateUpdate, async execute(client, oldState, newState) { if (newState.member.user.bot) return; const guildId = newState.guild.id; - + const oderId = newState.member.id; + const key = `${guildId}_${oderId}`; + + // ===== AUTOROLE VOCAL ===== db.get( "SELECT enabled, role_id, exclude_channel_ids FROM autorole_vocal_config WHERE guild_id = ?", [guildId], @@ -27,15 +34,83 @@ module.exports = { const role = newState.guild.roles.cache.get(row.role_id); if (!role) return; - // User joins a voice channel and it's not excluded et a pas déjà le rôle if (newState.channelId && !excludeChannelIds.includes(newState.channelId) && !newState.member.roles.cache.has(role.id)) { newState.member.roles.add(role); } - // User leaves a voice channel or joins an excluded one else if (!newState.channelId || excludeChannelIds.includes(newState.channelId)) { newState.member.roles.remove(role); } } ); + + // ===== ECONOMY VOICE MONEY ===== + db.get( + `SELECT enabled, voice_money_enabled, voice_money_min, voice_money_max, voice_money_interval_minutes + FROM economy_config WHERE guild_id = ?`, + [guildId], + (err, ecoRow) => { + if (err || !ecoRow || !ecoRow.enabled || !ecoRow.voice_money_enabled) { + // Clear interval if economy is disabled + if (voiceMoneyIntervals.has(key)) { + clearInterval(voiceMoneyIntervals.get(key)); + voiceMoneyIntervals.delete(key); + voiceJoinTimes.delete(key); + } + return; + } + + const intervalMs = ecoRow.voice_money_interval_minutes * 60 * 1000; + const minMoney = ecoRow.voice_money_min; + const maxMoney = ecoRow.voice_money_max; + + // User joined a voice channel + if (newState.channelId && !oldState.channelId) { + voiceJoinTimes.set(key, Date.now()); + + // Start interval for giving money + const intervalId = setInterval(() => { + // Check if user is still in voice + const member = newState.guild.members.cache.get(oderId); + if (!member || !member.voice.channelId) { + clearInterval(intervalId); + voiceMoneyIntervals.delete(key); + voiceJoinTimes.delete(key); + return; + } + + // Check if user is deafened or muted (optional: don't give money if AFK) + // You can customize this behavior + + const moneyToAdd = Math.floor(Math.random() * (maxMoney - minMoney + 1)) + minMoney; + + db.get( + `SELECT balance FROM user_economy WHERE guild_id = ? AND user_id = ?`, + [guildId, oderId], + (err, userRow) => { + if (err) return; + const newBalance = (userRow?.balance || 0) + moneyToAdd; + + db.run( + `INSERT INTO user_economy (guild_id, user_id, balance, bank) + VALUES (?, ?, ?, 0) + ON CONFLICT(guild_id, user_id) DO UPDATE SET balance = ?`, + [guildId, oderId, newBalance, newBalance] + ); + } + ); + }, intervalMs); + + voiceMoneyIntervals.set(key, intervalId); + } + // User left voice channel + else if (!newState.channelId && oldState.channelId) { + if (voiceMoneyIntervals.has(key)) { + clearInterval(voiceMoneyIntervals.get(key)); + voiceMoneyIntervals.delete(key); + } + voiceJoinTimes.delete(key); + } + } + ); }, }; diff --git a/app/public/guild.html b/app/public/guild.html index c69d409..b03aa44 100644 --- a/app/public/guild.html +++ b/app/public/guild.html @@ -291,7 +291,12 @@ -